001: /*
002: This source file is part of Smyle, a database library.
003: For up-to-date information, see http://www.drjava.de/smyle
004: Copyright (C) 2001 Stefan Reich (doc@drjava.de)
005:
006: This library is free software; you can redistribute it and/or
007: modify it under the terms of the GNU Lesser General Public
008: License as published by the Free Software Foundation; either
009: version 2.1 of the License, or (at your option) any later version.
010:
011: This library is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public
017: License along with this library; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019:
020: For full license text, see doc/license/lgpl.txt in this distribution
021: */
022:
023: package drjava.smyle.core;
024:
025: import java.io.*;
026: import java.util.*;
027: import org.artsProject.mcop.*;
028: import drjava.smyle.*;
029: import drjava.smyle.core.*;
030:
031: // TODO: add another abstraction layer between this and raw disk storage
032: // (without master handling) to be able to test this class against
033: // disk failures
034:
035: public class FileSystemDisk implements Disk {
036: long idCounter, master, bytesWritten = 0;
037: File dir, inUse;
038: //String prefix = createPrefix();
039: int diskId, diskIdRange;
040: int clusterSize = DEFAULT_CLUSTERSIZE;
041: ArrayList<NormalFileOutputStream> openStreams = new ArrayList<NormalFileOutputStream>();
042:
043: public static final String EXT = ".smy", MASTER = "master";
044:
045: static final int DEFAULT_CLUSTERSIZE = 32768;
046:
047: /*static String createPrefix() {
048: return new String(System.currentTimeMillis()/100)
049: +new String((random.nextInt() & 0x7fffffff) % 1000)
050: +"_";
051: }*/
052:
053: class NormalFileOutputStream extends FileOutputStream {
054: int length = 0;
055:
056: NormalFileOutputStream(File file) throws IOException {
057: super (file);
058: openStreams.add(this );
059: }
060:
061: public void write(int b) throws IOException {
062: super .write(b);
063: ++length;
064: }
065:
066: public void write(byte[] b) throws IOException {
067: super .write(b);
068: length += b.length;
069: }
070:
071: public void write(byte[] b, int ofs, int len)
072: throws IOException {
073: super .write(b, ofs, len);
074: length += len;
075: }
076:
077: public void close() throws IOException {
078: super .close();
079: synchronized (FileSystemDisk.this ) {
080: openStreams.remove(this );
081: }
082: bytesWritten += (length + clusterSize - 1) / clusterSize
083: * clusterSize;
084: }
085: }
086:
087: class FileImpl implements Disk.NewFile {
088: long id = (++idCounter) * diskIdRange + diskId;
089: OutputStream out;
090:
091: FileImpl() {
092: try {
093: out = new NormalFileOutputStream(getFile(id));
094: } catch (IOException e) {
095: throw new SmyleIOException(e);
096: }
097: }
098:
099: public long getId() {
100: return id;
101: }
102:
103: public synchronized OutputStream getOutputStream() {
104: if (out == null)
105: throw new BadUseException(
106: "getOutputStream may only be called once");
107: OutputStream result = out;
108: out = null;
109: return result;
110: }
111: }
112:
113: public FileSystemDisk(File dir, boolean readOnly) {
114: this (dir, 0, 1, readOnly);
115: }
116:
117: public FileSystemDisk(File dir, int diskId, int diskIdRange,
118: boolean readOnly) {
119: try {
120: this .diskId = diskId;
121: this .diskIdRange = diskIdRange;
122: this .dir = dir;
123: if (!this .dir.isDirectory())
124: if (readOnly)
125: throw new StoreNotFoundException(dir
126: + " doesn't exist");
127: else if (!this .dir.mkdir())
128: throw new SmyleIOException("Can't create "
129: + this .dir);
130:
131: // check and set in-use flag
132: if (!readOnly) {
133: File inUse = new File(dir, "in-use.flag");
134: if (!inUse.createNewFile()) {
135: throw new DiskInUseException(
136: "The directory\n "
137: + this .dir.getAbsolutePath()
138: + "\nis already in use by another process or an instance of "
139: + "Smyle loaded by a different class loader.\nIf you think this "
140: + "is not the case, please delete the file\n "
141: + inUse.getAbsolutePath());
142: }
143: this .inUse = inUse;
144: inUse.deleteOnExit();
145: }
146:
147: // find number to count on from and new-style master file
148: scanIds();
149:
150: // load master file if existent
151: File f = new File(dir, MASTER + EXT);
152: if (f.exists()) {
153: DataInputStream in = new DataInputStream(
154: new FileInputStream(f));
155: int l = (int) f.length();
156: if (l != 4 && l != 8)
157: throw new SmyleIOException(f
158: + ": wrong file size (" + l + ")");
159: byte[] data = new byte[l];
160: in.readFully(data);
161: in.close();
162: if (l == 4)
163: master = new Buffer(data).readLong();
164: else
165: master = new Buffer(data).readLongLong();
166: }
167: } catch (IOException e) {
168: throw new SmyleIOException(e);
169: }
170: }
171:
172: synchronized File getFile(long id) {
173: return new File(dir, String.valueOf(id) + EXT);
174: }
175:
176: synchronized File getMasterFile(long id) {
177: return new File(dir, "m" + id + EXT);
178: }
179:
180: /** finds the file with the given id whether it's a master file
181: or not */
182: private File smartGetFile(long id) {
183: File f = getFile(id);
184: return f.exists() ? f : getMasterFile(id);
185: }
186:
187: public synchronized Disk.NewFile createFile() {
188: return new FileImpl();
189: }
190:
191: public synchronized long saveMaster(Buffer data) {
192: FileImpl file = new FileImpl();
193: DiskUtil.bufferToFile(file, data);
194: setMaster(file.getId());
195: return file.getId();
196: }
197:
198: synchronized void setMaster(long id) {
199: File f1 = getFile(id);
200: File f2 = getMasterFile(id);
201: if (!f1.renameTo(f2))
202: throw new SmyleIOException("Couldn't rename " + f1 + " to "
203: + f2 + "(" + f1.exists() + "/" + f2.exists() + ")");
204: master = id;
205: }
206:
207: public synchronized long getMasterFile() {
208: return master;
209: }
210:
211: public synchronized int getFileLength(long id) {
212: try {
213: File file = smartGetFile(id);
214: int result = (int) file.length();
215: if (result == 0 && !file.isFile())
216: throw new FileNotFoundException(file.getPath());
217: return result;
218: } catch (IOException e) {
219: throw new SmyleIOException(e);
220: }
221: }
222:
223: public synchronized InputStream readFile(long id)
224: throws IOException {
225: return new FileInputStream(smartGetFile(id));
226: }
227:
228: private void closeOpenStreams() {
229: for (int i = 0; i < openStreams.size(); i++)
230: try {
231: openStreams.get(i).close();
232: } catch (IOException e) {
233: throw new SmyleIOException(e);
234: }
235: openStreams.clear();
236: }
237:
238: public synchronized void deleteEverything() {
239: closeOpenStreams();
240:
241: deleteEverythingBut(new HashSet<FileRef>());
242: File masterFile = new File(dir, MASTER + EXT);
243: if (masterFile.isFile())
244: if (!masterFile.delete())
245: throw new SmyleIOException("Couldn't delete " + master);
246: idCounter = master = 0;
247: }
248:
249: public synchronized void deleteEverythingBut(Set<FileRef> whitelist) {
250: File[] files = dir.listFiles();
251: for (int i = 0; i < files.length; i++) {
252: String name = files[i].getName().toLowerCase();
253: if (files[i].isFile() && name.endsWith(EXT))
254: try {
255: long id = Long.parseLong(name
256: .substring(name.startsWith("m") ? 1 : 0,
257: name.indexOf('.')));
258: if (!whitelist.contains(new FileRef(id))) {
259: if (!files[i].delete())
260: throw new SmyleIOException(
261: "Couldn't delete " + files[i]);
262: }
263: } catch (NumberFormatException e) {
264: e.printStackTrace();
265: // Couldn't parse number, ignore
266: }
267: }
268: }
269:
270: synchronized void scanIds() {
271: idCounter = master = 0;
272: File[] files = dir.listFiles();
273: for (int i = 0; i < files.length; i++) {
274: String name = files[i].getName().toLowerCase();
275: if (files[i].isFile() && name.endsWith(EXT))
276: try {
277: long id;
278: if (name.startsWith("m")) {
279: // master file
280: id = Long.parseLong(name.substring(1, name
281: .indexOf('.')));
282: if (id > master)
283: master = id;
284: } else {
285: // regular file
286: id = Long.parseLong(name.substring(0, name
287: .indexOf('.')));
288: }
289: if (id > idCounter)
290: idCounter = id;
291: } catch (Exception e) {
292: // Couldn't parse number, ignore
293: }
294: }
295: }
296:
297: public synchronized void setIdCounter(long counter) {
298: this .idCounter = counter;
299: }
300:
301: public synchronized long totalBytesWritten() {
302: return bytesWritten;
303: }
304:
305: public synchronized void setClusterSize(int bytes) {
306: clusterSize = bytes;
307: }
308:
309: public synchronized void release() {
310: closeOpenStreams();
311: if (inUse != null) {
312: inUse.delete();
313: inUse = null;
314: }
315: }
316:
317: public synchronized void finalize() {
318: if (inUse != null)
319: release();
320: }
321:
322: public synchronized String toString() {
323: return dir.toString();
324: }
325:
326: public synchronized boolean inUse() {
327: return new File(dir, "in-use.flag").exists();
328: }
329: }
|