001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store.fs;
007:
008: import java.io.File;
009: import java.io.FileInputStream;
010: import java.io.FileOutputStream;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.OutputStream;
014: import java.net.URL;
015: import java.sql.SQLException;
016:
017: import org.h2.constant.ErrorCode;
018: import org.h2.constant.SysProperties;
019: import org.h2.engine.Constants;
020: import org.h2.message.Message;
021: import org.h2.util.FileUtils;
022: import org.h2.util.IOUtils;
023: import org.h2.util.StringUtils;
024:
025: /**
026: * This file system stores files on disk.
027: * This is the most common file system.
028: */
029: public class FileSystemDisk extends FileSystem {
030:
031: private static final FileSystemDisk INSTANCE = new FileSystemDisk();
032: // TODO detection of 'case in sensitive filesystem'
033: // could maybe implemented using some other means
034: private static final boolean IS_FILE_SYSTEM_CASE_INSENSITIVE = (File.separatorChar == '\\');
035:
036: public static FileSystemDisk getInstance() {
037: return INSTANCE;
038: }
039:
040: protected FileSystemDisk() {
041: }
042:
043: public long length(String fileName) {
044: fileName = translateFileName(fileName);
045: return new File(fileName).length();
046: }
047:
048: private String translateFileName(String fileName) {
049: if (fileName != null && fileName.startsWith("~")) {
050: String userDir = SysProperties.USER_HOME;
051: fileName = userDir + fileName.substring(1);
052: }
053: return fileName;
054: }
055:
056: public void rename(String oldName, String newName)
057: throws SQLException {
058: File oldFile = new File(oldName);
059: File newFile = new File(newName);
060: if (oldFile.getAbsolutePath().equals(newFile.getAbsolutePath())) {
061: throw Message.getInternalError("rename file old=new");
062: }
063: if (!oldFile.exists()) {
064: throw Message.getSQLException(
065: ErrorCode.FILE_RENAME_FAILED_2, new String[] {
066: oldName + " (not found)", newName });
067: }
068: if (newFile.exists()) {
069: throw Message.getSQLException(
070: ErrorCode.FILE_RENAME_FAILED_2, new String[] {
071: oldName, newName + " (exists)" });
072: }
073: for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) {
074: trace("rename", oldName + " >" + newName, null);
075: boolean ok = oldFile.renameTo(newFile);
076: if (ok) {
077: return;
078: }
079: wait(i);
080: }
081: throw Message.getSQLException(ErrorCode.FILE_RENAME_FAILED_2,
082: new String[] { oldName, newName });
083: }
084:
085: void trace(String method, String fileName, Object o) {
086: if (SysProperties.TRACE_IO) {
087: System.out.println("FileSystem." + method + " " + fileName
088: + " " + o);
089: }
090: }
091:
092: private static void wait(int i) {
093: if (i > 8) {
094: System.gc();
095: }
096: try {
097: // sleep at most 256 ms
098: long sleep = Math.min(256, i * i);
099: Thread.sleep(sleep);
100: } catch (InterruptedException e) {
101: // ignore
102: }
103: }
104:
105: public boolean createNewFile(String fileName) throws SQLException {
106: fileName = translateFileName(fileName);
107: File file = new File(fileName);
108: for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) {
109: try {
110: return file.createNewFile();
111: } catch (IOException e) {
112: // 'access denied' is really a concurrent access problem
113: wait(i);
114: }
115: }
116: return false;
117: }
118:
119: public boolean exists(String fileName) {
120: fileName = translateFileName(fileName);
121: return new File(fileName).exists();
122: }
123:
124: public void delete(String fileName) throws SQLException {
125: fileName = translateFileName(fileName);
126: File file = new File(fileName);
127: if (file.exists()) {
128: for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) {
129: trace("delete", fileName, null);
130: boolean ok = file.delete();
131: if (ok) {
132: return;
133: }
134: wait(i);
135: }
136: throw Message.getSQLException(
137: ErrorCode.FILE_DELETE_FAILED_1, fileName);
138: }
139: }
140:
141: public boolean tryDelete(String fileName) {
142: fileName = translateFileName(fileName);
143: trace("tryDelete", fileName, null);
144: return new File(fileName).delete();
145: }
146:
147: public String createTempFile(String name, String suffix,
148: boolean deleteOnExit, boolean inTempDir) throws IOException {
149: name = translateFileName(name);
150: name += ".";
151: String prefix = new File(name).getName();
152: File dir;
153: if (inTempDir) {
154: dir = null;
155: } else {
156: dir = new File(name).getAbsoluteFile().getParentFile();
157: dir.mkdirs();
158: }
159: File f = File.createTempFile(prefix, suffix, dir);
160: if (deleteOnExit) {
161: try {
162: f.deleteOnExit();
163: } catch (Throwable e) {
164: // sometimes this throws a NullPointerException
165: // at java.io.DeleteOnExitHook.add(DeleteOnExitHook.java:33)
166: // we can ignore it
167: }
168: }
169: return f.getCanonicalPath();
170: }
171:
172: public String[] listFiles(String path) throws SQLException {
173: path = translateFileName(path);
174: File f = new File(path);
175: try {
176: String[] list = f.list();
177: if (list == null) {
178: return new String[0];
179: }
180: String base = f.getCanonicalPath();
181: if (!base.endsWith(File.separator)) {
182: base += File.separator;
183: }
184: for (int i = 0; i < list.length; i++) {
185: list[i] = base + list[i];
186: }
187: return list;
188: } catch (IOException e) {
189: throw Message.convertIOException(e, path);
190: }
191: }
192:
193: public void deleteRecursive(String fileName) throws SQLException {
194: fileName = translateFileName(fileName);
195: if (FileUtils.isDirectory(fileName)) {
196: String[] list = listFiles(fileName);
197: for (int i = 0; list != null && i < list.length; i++) {
198: deleteRecursive(list[i]);
199: }
200: }
201: delete(fileName);
202: }
203:
204: public boolean isReadOnly(String fileName) {
205: fileName = translateFileName(fileName);
206: File f = new File(fileName);
207: return f.exists() && !f.canWrite();
208: }
209:
210: public String normalize(String fileName) throws SQLException {
211: fileName = translateFileName(fileName);
212: File f = new File(fileName);
213: try {
214: return f.getCanonicalPath();
215: } catch (IOException e) {
216: throw Message.convertIOException(e, fileName);
217: }
218: }
219:
220: public String getParent(String fileName) {
221: fileName = translateFileName(fileName);
222: return new File(fileName).getParent();
223: }
224:
225: public boolean isDirectory(String fileName) {
226: fileName = translateFileName(fileName);
227: return new File(fileName).isDirectory();
228: }
229:
230: public boolean isAbsolute(String fileName) {
231: fileName = translateFileName(fileName);
232: File file = new File(fileName);
233: return file.isAbsolute();
234: }
235:
236: public String getAbsolutePath(String fileName) {
237: fileName = translateFileName(fileName);
238: File parent = new File(fileName).getAbsoluteFile();
239: return parent.getAbsolutePath();
240: }
241:
242: public long getLastModified(String fileName) {
243: fileName = translateFileName(fileName);
244: return new File(fileName).lastModified();
245: }
246:
247: public boolean canWrite(String fileName) {
248: fileName = translateFileName(fileName);
249: return new File(fileName).canWrite();
250: }
251:
252: public void copy(String original, String copy) throws SQLException {
253: original = translateFileName(original);
254: copy = translateFileName(copy);
255: OutputStream out = null;
256: InputStream in = null;
257: try {
258: out = FileUtils.openFileOutputStream(copy, false);
259: in = FileUtils.openFileInputStream(original);
260: byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
261: while (true) {
262: int len = in.read(buffer);
263: if (len < 0) {
264: break;
265: }
266: out.write(buffer, 0, len);
267: }
268: out.close();
269: } catch (IOException e) {
270: throw Message.convertIOException(e, "original: " + original
271: + " copy: " + copy);
272: } finally {
273: IOUtils.closeSilently(in);
274: IOUtils.closeSilently(out);
275: }
276: }
277:
278: public void createDirs(String fileName) throws SQLException {
279: fileName = translateFileName(fileName);
280: File f = new File(fileName);
281: if (!f.exists()) {
282: String parent = f.getParent();
283: if (parent == null) {
284: return;
285: }
286: File dir = new File(parent);
287: for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) {
288: if (dir.exists() || dir.mkdirs()) {
289: return;
290: }
291: wait(i);
292: }
293: throw Message.getSQLException(
294: ErrorCode.FILE_CREATION_FAILED_1, parent);
295: }
296: }
297:
298: public String getFileName(String name) throws SQLException {
299: name = translateFileName(name);
300: String separator = SysProperties.FILE_SEPARATOR;
301: String path = getParent(name);
302: if (!path.endsWith(separator)) {
303: path += separator;
304: }
305: String fullFileName = normalize(name);
306: if (!fullFileName.startsWith(path)) {
307: throw Message.getInternalError("file utils error: "
308: + fullFileName + " does not start with " + path);
309: }
310: String fileName = fullFileName.substring(path.length());
311: return fileName;
312: }
313:
314: public boolean fileStartsWith(String fileName, String prefix) {
315: fileName = translateFileName(fileName);
316: if (IS_FILE_SYSTEM_CASE_INSENSITIVE) {
317: fileName = StringUtils.toUpperEnglish(fileName);
318: prefix = StringUtils.toUpperEnglish(prefix);
319: }
320: return fileName.startsWith(prefix);
321: }
322:
323: public OutputStream openFileOutputStream(String fileName,
324: boolean append) throws SQLException {
325: fileName = translateFileName(fileName);
326: try {
327: File file = new File(fileName);
328: createDirs(file.getAbsolutePath());
329: FileOutputStream out = new FileOutputStream(fileName,
330: append);
331: trace("openFileOutputStream", fileName, out);
332: return out;
333: } catch (IOException e) {
334: freeMemoryAndFinalize();
335: try {
336: return new FileOutputStream(fileName);
337: } catch (IOException e2) {
338: throw Message.convertIOException(e, fileName);
339: }
340: }
341: }
342:
343: public InputStream openFileInputStream(String fileName)
344: throws IOException {
345: if (fileName.indexOf(':') > 1) {
346: // if the : is in position 1, a windows file access is assumed: C:.. or D:
347: // otherwise a URL is assumed
348: URL url = new URL(fileName);
349: InputStream in = url.openStream();
350: return in;
351: }
352: fileName = translateFileName(fileName);
353: FileInputStream in = new FileInputStream(fileName);
354: trace("openFileInputStream", fileName, in);
355: return in;
356: }
357:
358: private void freeMemoryAndFinalize() {
359: trace("freeMemoryAndFinalize", null, null);
360: Runtime rt = Runtime.getRuntime();
361: long mem = rt.freeMemory();
362: for (int i = 0; i < 16; i++) {
363: rt.gc();
364: long now = rt.freeMemory();
365: rt.runFinalization();
366: if (now == mem) {
367: break;
368: }
369: mem = now;
370: }
371: }
372:
373: public FileObject openFileObject(String fileName, String mode)
374: throws IOException {
375: fileName = translateFileName(fileName);
376: FileObjectDisk f;
377: try {
378: f = new FileObjectDisk(fileName, mode);
379: trace("openRandomAccessFile", fileName, f);
380: } catch (IOException e) {
381: freeMemoryAndFinalize();
382: f = new FileObjectDisk(fileName, mode);
383: }
384: return f;
385: }
386:
387: }
|