提交 04767312 authored 作者: Thomas Mueller's avatar Thomas Mueller

Improved compatibility with the Java 7 FileSystem abstraction.

上级 1779fdf1
......@@ -250,9 +250,7 @@ public class TraceSystem implements TraceWriter {
closeWriter();
if (maxFileSize > 0 && FileUtils.size(fileName) > maxFileSize) {
String old = fileName + ".old";
if (FileUtils.exists(old)) {
FileUtils.delete(old);
}
FileUtils.moveTo(fileName, old);
}
}
......
......@@ -30,12 +30,6 @@ public class FileStore {
*/
public static final int HEADER_LENGTH = 3 * Constants.FILE_BLOCK_SIZE;
/**
* An empty buffer to speed up extending the file (it seems that writing 0
* bytes is faster then calling setLength).
*/
protected static byte[] empty;
/**
* The magic file header.
*/
......@@ -57,7 +51,6 @@ public class FileStore {
private long fileLength;
private Reference<?> autoDeleteReference;
private boolean checkedWriting = true;
private boolean synchronousMode;
private String mode;
private TempFileDeleter tempFileDeleter;
private boolean textMode;
......@@ -85,11 +78,8 @@ public class FileStore {
FileUtils.createDirectories(FileUtils.getParent(name));
}
file = FileUtils.openFileObject(name, mode);
if (mode.length() > 2) {
synchronousMode = true;
}
if (exists) {
fileLength = file.length();
fileLength = file.size();
}
} catch (IOException e) {
throw DbException.convertIOException(e, "name: " + name + " mode: " + mode);
......@@ -299,7 +289,7 @@ public class FileStore {
}
try {
if (pos != filePos) {
file.seek(pos);
file.position(pos);
filePos = pos;
}
} catch (IOException e) {
......@@ -356,24 +346,6 @@ public class FileStore {
return true;
}
private void extendByWriting(long newLength) throws IOException {
long pos = filePos;
file.seek(fileLength);
if (empty == null) {
empty = new byte[16 * 1024];
}
byte[] e = empty;
while (true) {
int p = (int) Math.min(newLength - fileLength, e.length);
if (p <= 0) {
break;
}
file.write(e, 0, p);
fileLength += p;
}
file.seek(pos);
}
/**
* Set the length of the file. This will expand or shrink the file.
*
......@@ -386,16 +358,19 @@ public class FileStore {
checkPowerOff();
checkWritingAllowed();
try {
if (synchronousMode && newLength > fileLength) {
extendByWriting(newLength);
if (newLength > fileLength) {
long pos = filePos;
file.position(newLength - 1);
file.write(new byte[1], 0, 1);
file.position(pos);
} else {
file.setFileLength(newLength);
file.truncate(newLength);
}
fileLength = newLength;
} catch (IOException e) {
if (newLength > fileLength && freeUpDiskSpace()) {
try {
file.setFileLength(newLength);
file.truncate(newLength);
} catch (IOException e2) {
throw DbException.convertIOException(e2, name);
}
......@@ -414,14 +389,14 @@ public class FileStore {
try {
long len = fileLength;
if (SysProperties.CHECK2) {
len = file.length();
len = file.size();
if (len != fileLength) {
DbException.throwInternalError("file " + name + " length " + len + " expected " + fileLength);
}
}
if (SysProperties.CHECK2 && len % Constants.FILE_BLOCK_SIZE != 0) {
long newLength = len + Constants.FILE_BLOCK_SIZE - (len % Constants.FILE_BLOCK_SIZE);
file.setFileLength(newLength);
file.truncate(newLength);
fileLength = newLength;
DbException.throwInternalError("unaligned file length " + name + " len " + len);
}
......@@ -439,7 +414,7 @@ public class FileStore {
public long getFilePointer() {
if (SysProperties.CHECK2) {
try {
if (file.getFilePointer() != filePos) {
if (file.position() != filePos) {
DbException.throwInternalError();
}
} catch (IOException e) {
......@@ -493,7 +468,7 @@ public class FileStore {
public void openFile() throws IOException {
if (file == null) {
file = FileUtils.openFileObject(name, mode);
file.seek(filePos);
file.position(filePos);
}
}
......
......@@ -63,7 +63,7 @@ public class RecoverTester implements Recorder {
}
public void log(int op, String fileName, byte[] data, long x) {
if (op != Recorder.WRITE && op != Recorder.SET_LENGTH) {
if (op != Recorder.WRITE && op != Recorder.TRUNCATE) {
return;
}
if (!fileName.endsWith(Constants.SUFFIX_PAGE_FILE)) {
......
......@@ -18,7 +18,7 @@ public interface FileObject {
*
* @return the length
*/
long length() throws IOException;
long size() throws IOException;
/**
* Close the file.
......@@ -38,7 +38,7 @@ public interface FileObject {
*
* @param pos the new position
*/
void seek(long pos) throws IOException;
void position(long pos) throws IOException;
/**
* Write to the file.
......@@ -54,7 +54,7 @@ public interface FileObject {
*
* @return the current file pointer
*/
long getFilePointer() throws IOException;
long position() throws IOException;
/**
* Force changes to the physical location.
......@@ -66,14 +66,7 @@ public interface FileObject {
*
* @param newLength the new length
*/
void setFileLength(long newLength) throws IOException;
/**
* Get the full qualified name of this file.
*
* @return the name
*/
String getName();
void truncate(long newLength) throws IOException;
/**
* Try to lock the file exclusively.
......
......@@ -11,18 +11,18 @@ import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import org.h2.constant.SysProperties;
import org.h2.util.IOUtils;
/**
* This class extends a java.io.RandomAccessFile.
*/
public class FileObjectDisk extends RandomAccessFile implements FileObject {
public class FileObjectDisk implements FileObject {
private final RandomAccessFile file;
private final String name;
private FileLock lock;
FileObjectDisk(String fileName, String mode) throws FileNotFoundException {
super(fileName, mode);
this.file = new RandomAccessFile(fileName, mode);
this.name = fileName;
}
......@@ -31,28 +31,27 @@ public class FileObjectDisk extends RandomAccessFile implements FileObject {
if ("".equals(m)) {
// do nothing
} else if ("sync".equals(m)) {
getFD().sync();
file.getFD().sync();
} else if ("force".equals(m)) {
getChannel().force(true);
file.getChannel().force(true);
} else if ("forceFalse".equals(m)) {
getChannel().force(false);
file.getChannel().force(false);
} else {
getFD().sync();
file.getFD().sync();
}
}
public void setFileLength(long newLength) throws IOException {
IOUtils.setLength(this, newLength);
public void truncate(long newLength) throws IOException {
if (newLength < file.length()) {
// some implementations actually only support truncate
file.setLength(newLength);
}
public String getName() {
return name;
}
public synchronized boolean tryLock() {
if (lock == null) {
try {
lock = getChannel().tryLock();
lock = file.getChannel().tryLock();
} catch (Exception e) {
// could not lock (OverlappingFileLockException)
}
......@@ -72,4 +71,32 @@ public class FileObjectDisk extends RandomAccessFile implements FileObject {
}
}
public void close() throws IOException {
file.close();
}
public long position() throws IOException {
return file.getFilePointer();
}
public long size() throws IOException {
return file.length();
}
public void readFully(byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
}
public void position(long pos) throws IOException {
file.seek(pos);
}
public void write(byte[] b, int off, int len) throws IOException {
file.write(b, off, len);
}
public String toString() {
return name;
}
}
......@@ -37,15 +37,11 @@ public class FileObjectDiskChannel implements FileObject {
file.close();
}
public long getFilePointer() throws IOException {
public long position() throws IOException {
return channel.position();
}
public String getName() {
return FileSystemDiskNio.PREFIX + name;
}
public long length() throws IOException {
public long size() throws IOException {
return channel.size();
}
......@@ -62,27 +58,24 @@ public class FileObjectDiskChannel implements FileObject {
channel.read(buf);
}
public void seek(long pos) throws IOException {
public void position(long pos) throws IOException {
channel.position(pos);
}
public void setFileLength(long newLength) throws IOException {
if (newLength <= channel.size()) {
long oldPos = channel.position();
public void truncate(long newLength) throws IOException {
if (newLength >= channel.size()) {
return;
}
long pos = channel.position();
try {
channel.truncate(newLength);
} catch (NonWritableChannelException e) {
throw new IOException("read only");
}
if (oldPos > newLength) {
oldPos = newLength;
}
channel.position(oldPos);
} else {
// extend by writing to the new location
ByteBuffer b = ByteBuffer.allocate(1);
channel.write(b, newLength - 1);
if (pos > newLength) {
pos = newLength;
}
channel.position(pos);
length = newLength;
}
......@@ -124,4 +117,8 @@ public class FileObjectDiskChannel implements FileObject {
}
}
public String toString() {
return name;
}
}
......@@ -16,7 +16,6 @@ import java.nio.MappedByteBuffer;
import java.nio.channels.FileLock;
import java.nio.channels.FileChannel.MapMode;
import org.h2.constant.SysProperties;
import org.h2.util.IOUtils;
/**
* FileObject which is using NIO MappedByteBuffer mapped to memory from file.
......@@ -129,15 +128,11 @@ public class FileObjectDiskMapped implements FileObject {
}
}
public long getFilePointer() {
public long position() {
return pos;
}
public String getName() {
return FileSystemDiskNioMapped.PREFIX + name;
}
public synchronized long length() throws IOException {
public synchronized long size() throws IOException {
return file.length();
}
......@@ -157,26 +152,18 @@ public class FileObjectDiskMapped implements FileObject {
}
}
public void seek(long pos) throws IOException {
public void position(long pos) throws IOException {
checkFileSizeLimit(pos);
this.pos = (int) pos;
}
public synchronized void setFileLength(long newLength) throws IOException {
checkFileSizeLimit(newLength);
public synchronized void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
int oldPos = pos;
unMap();
for (int i = 0;; i++) {
try {
IOUtils.setLength(file, newLength);
break;
} catch (IOException e) {
if (i > 16 || e.toString().indexOf("user-mapped section open") < 0) {
throw e;
}
}
System.gc();
}
file.setLength(newLength);
reMap();
pos = (int) Math.min(newLength, oldPos);
}
......@@ -189,7 +176,11 @@ public class FileObjectDiskMapped implements FileObject {
public synchronized void write(byte[] b, int off, int len) throws IOException {
// check if need to expand file
if (mapped.capacity() < pos + len) {
setFileLength(pos + len);
int oldPos = pos;
unMap();
file.setLength(pos + len);
reMap();
pos = oldPos;
}
mapped.position(pos);
mapped.put(b, off, len);
......@@ -219,4 +210,8 @@ public class FileObjectDiskMapped implements FileObject {
}
}
public String toString() {
return name;
}
}
......@@ -27,7 +27,7 @@ public class FileObjectInputStream extends InputStream {
}
public int read() throws IOException {
if (file.getFilePointer() >= file.length()) {
if (file.position() >= file.size()) {
return -1;
}
file.readFully(buffer, 0, 1);
......@@ -39,7 +39,7 @@ public class FileObjectInputStream extends InputStream {
}
public int read(byte[] b, int off, int len) throws IOException {
if (file.getFilePointer() + len < file.length()) {
if (file.position() + len < file.size()) {
file.readFully(b, off, len);
return len;
}
......
......@@ -22,19 +22,20 @@ public class FileObjectMemory implements FileObject {
this.readOnly = readOnly;
}
public long length() {
public long size() {
return data.length();
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
data.touch(readOnly);
if (newLength < length()) {
pos = Math.min(pos, newLength);
}
data.setFileLength(newLength);
data.truncate(newLength);
}
public void seek(long newPos) {
public void position(long newPos) {
this.pos = (int) newPos;
}
......@@ -47,7 +48,7 @@ public class FileObjectMemory implements FileObject {
pos = data.readWrite(pos, b, off, len, false);
}
public long getFilePointer() {
public long position() {
return pos;
}
......@@ -59,10 +60,6 @@ public class FileObjectMemory implements FileObject {
// do nothing
}
public String getName() {
return data.getName();
}
public boolean tryLock() {
return data.tryLock();
}
......
......@@ -166,12 +166,11 @@ class FileObjectMemoryData {
}
/**
* Change the file length.
* Truncate the file.
*
* @param newLength the new length
*/
void setFileLength(long newLength) {
if (newLength < length) {
void truncate(long newLength) {
changeLength(newLength);
long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE);
if (end != newLength) {
......@@ -185,9 +184,6 @@ class FileObjectMemoryData {
compressLater(data, lastPage);
}
}
} else {
changeLength(newLength);
}
}
private void changeLength(long len) {
......
......@@ -26,10 +26,10 @@ public class FileObjectOutputStream extends OutputStream {
public FileObjectOutputStream(FileObject file, boolean append) throws IOException {
this.file = file;
if (append) {
file.seek(file.length());
file.position(file.size());
} else {
file.seek(0);
file.setFileLength(0);
file.position(0);
file.truncate(0);
}
}
......
......@@ -36,11 +36,11 @@ public class FileObjectSplit implements FileObject {
}
}
public long getFilePointer() {
public long position() {
return filePointer;
}
public long length() {
public long size() {
return length;
}
......@@ -48,7 +48,7 @@ public class FileObjectSplit implements FileObject {
long offset = filePointer % maxLength;
int l = (int) Math.min(len, maxLength - offset);
FileObject fo = getFileObject();
fo.seek(offset);
fo.position(offset);
fo.readFully(b, off, l);
filePointer += l;
return l;
......@@ -68,7 +68,7 @@ public class FileObjectSplit implements FileObject {
}
}
public void seek(long pos) {
public void position(long pos) {
filePointer = pos;
}
......@@ -85,47 +85,31 @@ public class FileObjectSplit implements FileObject {
return list[id];
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
if (newLength >= length) {
return;
}
filePointer = Math.min(filePointer, newLength);
int newFileCount = 1 + (int) (newLength / maxLength);
if (newFileCount == list.length) {
long size = newLength - maxLength * (newFileCount - 1);
list[list.length - 1].setFileLength(size);
} else {
if (newFileCount < list.length) {
// delete some of the files
FileObject[] newList = new FileObject[newFileCount];
int max = Math.max(newFileCount, list.length);
long remaining = newLength;
// delete backwards, so that truncating is somewhat transactional
for (int i = list.length - 1; i >= newFileCount; i--) {
// verify the file is writable
list[i].setFileLength(0);
list[i].truncate(0);
list[i].close();
try {
FileUtils.delete(list[i].getName());
FileUtils.delete(FileSystemSplit.getFileName(name, i));
} catch (DbException e) {
throw DbException.convertToIOException(e);
}
}
for (int i = 0; i < max; i++) {
long fileSize = Math.min(remaining, maxLength);
remaining -= fileSize;
if (i >= newFileCount) {
// already closed and deleted
} else if (i >= list.length) {
String fileName = FileSystemSplit.getFileName(name, i);
FileObject o = FileSystem.getInstance(fileName).openFileObject(fileName, mode);
o.setFileLength(fileSize);
newList[i] = o;
} else {
FileObject o = list[i];
if (o.length() != fileSize) {
o.setFileLength(fileSize);
}
newList[i] = list[i];
}
}
System.arraycopy(list, 0, newList, 0, newList.length);
list = newList;
}
long size = newLength - maxLength * (newFileCount - 1);
list[list.length - 1].truncate(size);
this.length = newLength;
}
......@@ -136,6 +120,18 @@ public class FileObjectSplit implements FileObject {
}
public void write(byte[] b, int off, int len) throws IOException {
if (filePointer >= length && filePointer > maxLength) {
// may need to extend and create files
long oldFilePointer = filePointer;
long x = length - (length % maxLength) + maxLength;
for (; x < filePointer; x += maxLength) {
if (x > length) {
position(x - 1);
writePart(new byte[1], 0, 1);
}
filePointer = oldFilePointer;
}
}
while (true) {
int l = writePart(b, off, len);
len -= l;
......@@ -150,17 +146,13 @@ public class FileObjectSplit implements FileObject {
long offset = filePointer % maxLength;
int l = (int) Math.min(len, maxLength - offset);
FileObject fo = getFileObject();
fo.seek(offset);
fo.position(offset);
fo.write(b, off, l);
filePointer += l;
length = Math.max(length, filePointer);
return l;
}
public String getName() {
return FileSystemSplit.PREFIX + name;
}
public boolean tryLock() {
return list[0].tryLock();
}
......
......@@ -41,11 +41,11 @@ public class FileObjectZip implements FileObject {
// nothing to do
}
public long getFilePointer() {
public long position() {
return pos;
}
public long length() {
public long size() {
return length;
}
......@@ -87,11 +87,11 @@ public class FileObjectZip implements FileObject {
inPos += len;
}
public void seek(long newPos) {
public void position(long newPos) {
this.pos = newPos;
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
......@@ -103,10 +103,6 @@ public class FileObjectZip implements FileObject {
throw new IOException("File is read-only");
}
public String getName() {
return file.getName();
}
public boolean tryLock() {
return false;
}
......
......@@ -128,19 +128,12 @@ public abstract class FileSystem {
public abstract boolean exists(String fileName);
/**
* Delete a file.
* Delete a file or directory if it exists.
* Directories may only be deleted if they are empty.
*
* @param fileName the file name
*/
public abstract void delete(String fileName);
/**
* Try to delete a file.
*
* @param fileName the file name
* @return true if it could be deleted
* @param path the file or directory name
*/
public abstract boolean tryDelete(String fileName);
public abstract void delete(String path);
/**
* List the files in the given directory.
......
......@@ -138,26 +138,18 @@ public class FileSystemDisk extends FileSystem {
return new File(fileName).exists();
}
public void delete(String fileName) {
fileName = translateFileName(fileName);
File file = new File(fileName);
if (file.exists()) {
public void delete(String path) {
path = translateFileName(path);
File file = new File(path);
for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) {
IOUtils.trace("delete", fileName, null);
IOUtils.trace("delete", path, null);
boolean ok = file.delete();
if (ok) {
if (ok || !file.exists()) {
return;
}
wait(i);
}
throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, fileName);
}
}
public boolean tryDelete(String fileName) {
fileName = translateFileName(fileName);
IOUtils.trace("tryDelete", fileName, null);
return new File(fileName).delete();
throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, path);
}
public String createTempFile(String name, String suffix, boolean deleteOnExit, boolean inTempDir)
......
......@@ -68,7 +68,7 @@ public class FileSystemMemory extends FileSystem {
public boolean exists(String fileName) {
fileName = getCanonicalPath(fileName);
if (fileName.equals(PREFIX) || fileName.equals(PREFIX_LZF)) {
if (isRoot(fileName)) {
return true;
}
synchronized (MEMORY_FILES) {
......@@ -76,16 +76,14 @@ public class FileSystemMemory extends FileSystem {
}
}
public void delete(String fileName) {
fileName = getCanonicalPath(fileName);
synchronized (MEMORY_FILES) {
MEMORY_FILES.remove(fileName);
public void delete(String path) {
path = getCanonicalPath(path);
if (isRoot(path)) {
return;
}
synchronized (MEMORY_FILES) {
MEMORY_FILES.remove(path);
}
public boolean tryDelete(String fileName) {
delete(fileName);
return true;
}
public String[] listFiles(String path) {
......@@ -199,6 +197,10 @@ public class FileSystemMemory extends FileSystem {
}
}
private boolean isRoot(String path) {
return path.equals(PREFIX) || path.equals(PREFIX_LZF);
}
protected boolean accepts(String fileName) {
return fileName.startsWith(PREFIX) || fileName.startsWith(PREFIX_LZF);
}
......
......@@ -11,7 +11,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import org.h2.constant.SysProperties;
import org.h2.message.DbException;
import org.h2.util.New;
......@@ -49,12 +48,12 @@ public class FileSystemSplit extends FileSystemWrapper {
return result;
}
public void delete(String fileName) {
fileName = unwrap(fileName);
public void delete(String path) {
path = unwrap(path);
for (int i = 0;; i++) {
String f = getFileName(fileName, i);
if (getInstance(fileName).exists(f)) {
getInstance(fileName).delete(f);
String f = getFileName(path, i);
if (getInstance(path).exists(f)) {
getInstance(path).delete(f);
} else {
break;
}
......@@ -138,7 +137,7 @@ public class FileSystemSplit extends FileSystemWrapper {
}
FileObject[] array = new FileObject[list.size()];
list.toArray(array);
long maxLength = array[0].length();
long maxLength = array[0].size();
long length = maxLength;
if (array.length == 1) {
if (maxLength < defaultMaxSize) {
......@@ -146,29 +145,29 @@ public class FileSystemSplit extends FileSystemWrapper {
}
} else {
if (maxLength == 0) {
closeAndThrow(array, array[0], maxLength);
closeAndThrow(fileName, 0, array, array[0], maxLength);
}
for (int i = 1; i < array.length - 1; i++) {
o = array[i];
long l = o.length();
long l = o.size();
length += l;
if (l != maxLength) {
closeAndThrow(array, o, maxLength);
closeAndThrow(fileName, i, array, o, maxLength);
}
}
o = array[array.length - 1];
long l = o.length();
long l = o.size();
length += l;
if (l > maxLength) {
closeAndThrow(array, o, maxLength);
closeAndThrow(fileName, array.length - 1, array, o, maxLength);
}
}
FileObjectSplit fo = new FileObjectSplit(fileName, mode, array, length, maxLength);
return fo;
}
private static void closeAndThrow(FileObject[] array, FileObject o, long maxLength) throws IOException {
String message = "Expected file length: " + maxLength + " got: " + o.length() + " for " + o.getName();
private static void closeAndThrow(String fileName, int id, FileObject[] array, FileObject o, long maxLength) throws IOException {
String message = "Expected file length: " + maxLength + " got: " + o.size() + " for " + getFileName(fileName, id);
for (FileObject f : array) {
f.close();
}
......@@ -197,22 +196,6 @@ public class FileSystemSplit extends FileSystemWrapper {
}
}
public boolean tryDelete(String fileName) {
fileName = unwrap(fileName);
for (int i = 0;; i++) {
String f = getFileName(fileName, i);
if (getInstance(fileName).exists(f)) {
boolean ok = getInstance(fileName).tryDelete(f);
if (!ok) {
return false;
}
} else {
break;
}
}
return true;
}
public String unwrap(String fileName) {
if (!fileName.startsWith(PREFIX)) {
DbException.throwInternalError(fileName + " doesn't start with " + PREFIX);
......
......@@ -45,8 +45,8 @@ public abstract class FileSystemWrapper extends FileSystem {
return wrap(FileUtils.createTempFile(unwrap(prefix), suffix, deleteOnExit, inTempDir));
}
public void delete(String fileName) {
FileUtils.delete(unwrap(fileName));
public void delete(String path) {
FileUtils.delete(unwrap(path));
}
public boolean exists(String fileName) {
......@@ -113,10 +113,6 @@ public abstract class FileSystemWrapper extends FileSystem {
FileUtils.moveTo(unwrap(oldName), unwrap(newName));
}
public boolean tryDelete(String fileName) {
return FileUtils.tryDelete(unwrap(fileName));
}
protected boolean accepts(String fileName) {
return fileName.startsWith(getPrefix());
}
......
......@@ -203,10 +203,6 @@ public class FileSystemZip extends FileSystem {
throw DbException.getUnsupportedException("write");
}
public boolean tryDelete(String fileName) {
return false;
}
private static String translateFileName(String fileName) {
if (fileName.startsWith(PREFIX)) {
fileName = fileName.substring(PREFIX.length());
......
......@@ -44,13 +44,14 @@ public class FileUtils {
}
/**
* Delete a file.
* This method is similar to Java 7 <code>java.nio.file.Path.delete</code>.
* Delete a file or directory if it exists.
* Directories may only be deleted if they are empty.
* This method is similar to Java 7 <code>java.nio.file.Path.deleteIfExists</code>.
*
* @param fileName the file name
* @param path the file or directory name
*/
public static void delete(String fileName) {
getFileSystem(fileName).delete(fileName);
public static void delete(String path) {
getFileSystem(path).delete(path);
}
/**
......@@ -214,16 +215,6 @@ public class FileUtils {
return getFileSystem(fileName).isReadOnly(fileName);
}
/**
* Try to delete a file.
*
* @param fileName the file name
* @return true if it worked
*/
public static boolean tryDelete(String fileName) {
return getFileSystem(fileName).tryDelete(fileName);
}
/**
* Disable the ability to write.
*
......@@ -302,7 +293,7 @@ public class FileUtils {
*/
public static void createDirectories(String dir) {
String parent = FileUtils.getParent(dir);
if (!FileUtils.exists(parent)) {
if (parent != null && !FileUtils.exists(parent)) {
createDirectories(parent);
}
createDirectory(dir);
......@@ -324,4 +315,19 @@ public class FileUtils {
return FileSystem.getInstance(fileName);
}
/**
* Try to delete a file (ignore errors).
*
* @param fileName the file name
* @return true if it worked
*/
public static boolean tryDelete(String fileName) {
try {
getFileSystem(fileName).delete(fileName);
return true;
} catch (Exception e) {
return false;
}
}
}
......@@ -15,53 +15,48 @@ public interface Recorder {
* Copy a file. The file name contains the source and the target file
* separated with a colon.
*/
int COPY = 3;
int COPY = 0;
/**
* Create a directory.
*/
int CREATE_DIRECTORY = 4;
int CREATE_DIRECTORY = 1;
/**
* Create a new file.
*/
int CREATE_NEW_FILE = 5;
int CREATE_NEW_FILE = 2;
/**
* Create a temporary file.
*/
int CREATE_TEMP_FILE = 6;
int CREATE_TEMP_FILE = 3;
/**
* Delete a file.
*/
int DELETE = 7;
int DELETE = 4;
/**
* Open a file output stream.
*/
int OPEN_OUTPUT_STREAM = 8;
int OPEN_OUTPUT_STREAM = 5;
/**
* Rename a file. The file name contains the source and the target file
* separated with a colon.
*/
int RENAME = 9;
int RENAME = 6;
/**
* Set the length of the file.
* Truncate the file.
*/
int SET_LENGTH = 1;
/**
* Try to delete the file.
*/
int TRY_DELETE = 2;
int TRUNCATE = 7;
/**
* Write to the file.
*/
int WRITE = 0;
int WRITE = 8;
/**
* Record the method.
......
......@@ -17,39 +17,35 @@ public class RecordingFileObject implements FileObject {
private final FileObject file;
private final String name;
RecordingFileObject(RecordingFileSystem fs, FileObject file) {
RecordingFileObject(RecordingFileSystem fs, FileObject file, String fileName) {
this.fs = fs;
this.file = file;
this.name = file.getName();
this.name = fileName;
}
public void close() throws IOException {
file.close();
}
public long getFilePointer() throws IOException {
return file.getFilePointer();
public long position() throws IOException {
return file.position();
}
public String getName() {
return RecordingFileSystem.PREFIX + name;
}
public long length() throws IOException {
return file.length();
public long size() throws IOException {
return file.size();
}
public void readFully(byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
}
public void seek(long pos) throws IOException {
file.seek(pos);
public void position(long pos) throws IOException {
file.position(pos);
}
public void setFileLength(long newLength) throws IOException {
fs.log(Recorder.SET_LENGTH, name, null, newLength);
file.setFileLength(newLength);
public void truncate(long newLength) throws IOException {
fs.log(Recorder.TRUNCATE, name, null, newLength);
file.truncate(newLength);
}
public void sync() throws IOException {
......@@ -63,7 +59,7 @@ public class RecordingFileObject implements FileObject {
System.arraycopy(b, off, buff, 0, len);
}
file.write(b, off, len);
fs.log(Recorder.WRITE, name, buff, file.getFilePointer());
fs.log(Recorder.WRITE, name, buff, file.position());
}
public boolean tryLock() {
......
......@@ -63,7 +63,7 @@ public class RecordingFileSystem extends FileSystemWrapper {
}
public FileObject openFileObject(String fileName, String mode) throws IOException {
return new RecordingFileObject(this, super.openFileObject(fileName, mode));
return new RecordingFileObject(this, super.openFileObject(fileName, mode), fileName);
}
public OutputStream newOutputStream(String fileName, boolean append) {
......@@ -76,11 +76,6 @@ public class RecordingFileSystem extends FileSystemWrapper {
super.moveTo(oldName, newName);
}
public boolean tryDelete(String fileName) {
log(Recorder.TRY_DELETE, unwrap(fileName));
return super.tryDelete(fileName);
}
public String getPrefix() {
return PREFIX;
}
......
......@@ -123,9 +123,7 @@ public class Backup extends Tool {
FileLister.tryUnlockDatabase(list, "backup");
}
zipFileName = FileUtils.getCanonicalPath(zipFileName);
if (FileUtils.exists(zipFileName)) {
FileUtils.delete(zipFileName);
}
OutputStream fileOut = null;
try {
fileOut = FileUtils.newOutputStream(zipFileName, false);
......
......@@ -17,7 +17,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
......@@ -25,7 +24,6 @@ import java.io.Writer;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.store.fs.FileSystem;
/**
* This utility class contains input/output functions.
......@@ -477,36 +475,6 @@ public class IOUtils {
throw new IOException("Could not create directory: " + directory.getAbsolutePath());
}
/**
* Change the length of the file.
*
* @param file the random access file
* @param newLength the new length
*/
public static void setLength(RandomAccessFile file, long newLength) throws IOException {
try {
trace("setLength", null, file);
file.setLength(newLength);
} catch (IOException e) {
long length = file.length();
if (newLength < length) {
throw e;
}
long pos = file.getFilePointer();
file.seek(length);
long remaining = newLength - length;
int maxSize = 1024 * 1024;
int block = (int) Math.min(remaining, maxSize);
byte[] buffer = new byte[block];
while (remaining > 0) {
int write = (int) Math.min(remaining, maxSize);
file.write(buffer, 0, write);
remaining -= write;
}
file.seek(pos);
}
}
/**
* Trace input or output operations if enabled.
*
......
......@@ -68,7 +68,8 @@ public class TestOpenClose extends TestBase implements DatabaseEventListener {
conn.createStatement().execute("create table test(id int, name varchar) as select 1, space(1000000)");
conn.close();
FileObject f = FileUtils.openFileObject(getBaseDir() + "/openClose2.h2.db.1.part", "rw");
f.setFileLength(f.length() * 2);
f.position(f.size() * 2 - 1);
f.write(new byte[1], 0, 1);
f.close();
assertThrows(ErrorCode.IO_EXCEPTION_2, this).
getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2");
......
......@@ -19,8 +19,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.util.IOUtils;
/**
* This application tests the durability / non-durability of file systems and
* databases. Two computers with network connection are required to run this
......@@ -121,7 +119,8 @@ public class Test {
RandomAccessFile write = new RandomAccessFile(file, "rws");
// RandomAccessFile write = new RandomAccessFile(file, "rwd");
int fileSize = 10 * 1024 * 1024;
IOUtils.setLength(write, fileSize);
write.seek(fileSize - 1);
write.write(0);
write.seek(0);
int i = 0;
FileDescriptor fd = write.getFD();
......
......@@ -37,11 +37,11 @@ public class FileObjectDatabase implements FileObject {
sync();
}
public long getFilePointer() {
public long position() {
return pos;
}
public long length() {
public long size() {
return length;
}
......@@ -53,14 +53,17 @@ public class FileObjectDatabase implements FileObject {
pos += len;
}
public void seek(long newPos) {
public void position(long newPos) {
this.pos = (int) newPos;
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
if (readOnly) {
throw new IOException("read only");
}
if (newLength >= this.length) {
return;
}
this.length = (int) newLength;
if (length != data.length) {
byte[] n = Utils.newBytes(length);
......@@ -94,10 +97,6 @@ public class FileObjectDatabase implements FileObject {
changed = true;
}
public String getName() {
return fileName;
}
public void releaseLock() {
// ignore
}
......
......@@ -204,9 +204,9 @@ public class FileSystemDatabase extends FileSystem {
}
}
public synchronized void delete(String fileName) {
public synchronized void delete(String path) {
try {
long id = getId(fileName, false);
long id = getId(path, false);
PreparedStatement prep = prepare("DELETE FROM FILES WHERE ID=?");
prep.setLong(1, id);
prep.execute();
......@@ -370,11 +370,6 @@ public class FileSystemDatabase extends FileSystem {
}
}
public boolean tryDelete(String fileName) {
delete(fileName);
return true;
}
/**
* Update a file in the file system.
*
......
......@@ -49,6 +49,7 @@ public class TestFileSystem extends TestBase {
// DebugFileSystem.register().setTrace(true);
// testFileSystem("crypt:aes:x:" + getBaseDir() + "/fs");
testSimpleExpandTruncateSize();
testSplitDatabaseInZip();
testDatabaseInMemFileSys();
testDatabaseInJar();
......@@ -107,6 +108,18 @@ public class TestFileSystem extends TestBase {
in.close();
}
private void testSimpleExpandTruncateSize() throws Exception {
String f = "memFS:" + getBaseDir() + "/fs/test.data";
FileUtils.createDirectories("memFS:" + getBaseDir() + "/fs");
FileObject o = FileUtils.openFileObject(f, "rw");
o.position(4000);
o.write(new byte[1], 0, 1);
o.tryLock();
o.truncate(0);
o.releaseLock();
o.close();
}
private void testSplitDatabaseInZip() throws SQLException {
String dir = getBaseDir() + "/fs";
FileUtils.deleteRecursive(dir, false);
......@@ -206,7 +219,7 @@ public class TestFileSystem extends TestBase {
testRandomAccess(fsBase);
}
private void testSimple(String fsBase) throws Exception {
private void testSimple(final String fsBase) throws Exception {
long time = System.currentTimeMillis();
for (String s : FileUtils.listFiles(fsBase)) {
FileUtils.delete(s);
......@@ -221,16 +234,16 @@ public class TestFileSystem extends TestBase {
Random random = new Random(1);
random.nextBytes(buffer);
fo.write(buffer, 0, 10000);
assertEquals(10000, fo.length());
fo.seek(20000);
assertEquals(20000, fo.getFilePointer());
assertEquals(10000, fo.size());
fo.position(20000);
assertEquals(20000, fo.position());
assertThrows(EOFException.class, fo).readFully(buffer, 0, 1);
assertEquals(fsBase + "/test", fo.getName().replace('\\', '/'));
assertEquals("test", FileUtils.getName(fo.getName()));
assertEquals(fsBase, FileUtils.getParent(fo.getName()).replace('\\', '/'));
String path = fsBase + "/test";
assertEquals("test", FileUtils.getName(path));
assertEquals(fsBase, FileUtils.getParent(path).replace('\\', '/'));
fo.tryLock();
fo.releaseLock();
assertEquals(10000, fo.length());
assertEquals(10000, fo.size());
fo.close();
assertEquals(10000, FileUtils.size(fsBase + "/test"));
fo = FileUtils.openFileObject(fsBase + "/test", "r");
......@@ -238,7 +251,7 @@ public class TestFileSystem extends TestBase {
fo.readFully(test, 0, 10000);
assertEquals(buffer, test);
assertThrows(IOException.class, fo).write(test, 0, 10);
assertThrows(IOException.class, fo).setFileLength(10);
assertThrows(IOException.class, fo).truncate(10);
fo.close();
long lastMod = FileUtils.lastModified(fsBase + "/test");
if (lastMod < time - 1999) {
......@@ -306,7 +319,7 @@ public class TestFileSystem extends TestBase {
pos = (int) Math.min(pos, ra.length());
trace("seek " + pos);
buff.append("seek " + pos + "\n");
f.seek(pos);
f.position(pos);
ra.seek(pos);
break;
}
......@@ -320,14 +333,15 @@ public class TestFileSystem extends TestBase {
break;
}
case 2: {
trace("setLength " + pos);
f.setFileLength(pos);
trace("truncate " + pos);
f.truncate(pos);
if (pos < ra.getFilePointer()) {
// truncate is supposed to have no effect if the
// position is larger than the current position
ra.setLength(pos);
if (ra.getFilePointer() > pos) {
f.seek(0);
ra.seek(0);
}
buff.append("setLength " + pos + "\n");
assertEquals(ra.getFilePointer(), f.position());
buff.append("truncate " + pos + "\n");
break;
}
case 3: {
......@@ -345,13 +359,13 @@ public class TestFileSystem extends TestBase {
case 4: {
trace("getFilePointer");
buff.append("getFilePointer\n");
assertEquals(ra.getFilePointer(), f.getFilePointer());
assertEquals(ra.getFilePointer(), f.position());
break;
}
case 5: {
trace("length " + ra.length());
buff.append("length " + ra.length() + "\n");
assertEquals(ra.length(), f.length());
assertEquals(ra.length(), f.size());
break;
}
case 6: {
......@@ -361,7 +375,7 @@ public class TestFileSystem extends TestBase {
ra.close();
ra = new RandomAccessFile(file, "rw");
f = FileUtils.openFileObject(s, "rw");
assertEquals(ra.length(), f.length());
assertEquals(ra.length(), f.size());
break;
}
default:
......
......@@ -234,7 +234,7 @@ public class TestPageStoreCoverage extends TestBase {
conn = getConnection("pageStore");
conn.close();
f = FileUtils.openFileObject(fileName, "rw");
f.setFileLength(16);
f.truncate(16);
// create a new database
conn = getConnection("pageStore");
conn.close();
......
......@@ -136,11 +136,11 @@ public class TestRecovery extends TestBase {
conn.close();
FileObject f = FileUtils.openFileObject(getBaseDir() + "/recovery.h2.db", "rw");
byte[] buff = new byte[Constants.DEFAULT_PAGE_SIZE];
while (f.getFilePointer() < f.length()) {
while (f.position() < f.size()) {
f.readFully(buff, 0, buff.length);
if (new String(buff).indexOf("Hello World1") >= 0) {
buff[buff.length - 1]++;
f.seek(f.getFilePointer() - buff.length);
f.position(f.position() - buff.length);
f.write(buff, 0, buff.length);
}
}
......
......@@ -65,7 +65,7 @@ public class TestReopen extends TestBase implements Recorder {
}
public void log(int op, String fileName, byte[] data, long x) {
if (op != Recorder.WRITE && op != Recorder.SET_LENGTH) {
if (op != Recorder.WRITE && op != Recorder.TRUNCATE) {
return;
}
if (!fileName.endsWith(Constants.SUFFIX_PAGE_FILE)) {
......
......@@ -86,8 +86,7 @@ public abstract class AssertThrows {
if (errorCode != expectedErrorCode) {
AssertionError ae = new AssertionError(
"Expected an SQLException or DbException with error code " +
expectedErrorCode +
" for " + ProxyCodeGenerator.formatMethodCall(m, args));
expectedErrorCode);
ae.initCause(t);
throw ae;
}
......
......@@ -18,10 +18,10 @@ public class DebugFileObject implements FileObject {
private final FileObject file;
private final String name;
DebugFileObject(DebugFileSystem fs, FileObject file) {
DebugFileObject(DebugFileSystem fs, FileObject file, String name) {
this.fs = fs;
this.file = file;
this.name = fs.getPrefix() + file.getName();
this.name = fs.getPrefix() + name;
}
public void close() throws IOException {
......@@ -29,35 +29,30 @@ public class DebugFileObject implements FileObject {
file.close();
}
public long getFilePointer() throws IOException {
public long position() throws IOException {
debug("getFilePointer");
return file.getFilePointer();
return file.position();
}
public String getName() {
debug("getName");
return DebugFileSystem.PREFIX + file.getName();
}
public long length() throws IOException {
public long size() throws IOException {
debug("length");
return file.length();
return file.size();
}
public void readFully(byte[] b, int off, int len) throws IOException {
debug("readFully", file.getFilePointer(), off, len);
debug("readFully", file.position(), off, len);
file.readFully(b, off, len);
}
public void seek(long pos) throws IOException {
public void position(long pos) throws IOException {
debug("seek", pos);
file.seek(pos);
file.position(pos);
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
checkPowerOff();
debug("setFileLength", newLength);
file.setFileLength(newLength);
debug("truncate", newLength);
file.truncate(newLength);
}
public void sync() throws IOException {
......@@ -67,7 +62,7 @@ public class DebugFileObject implements FileObject {
public void write(byte[] b, int off, int len) throws IOException {
checkPowerOff();
debug("write", file.getFilePointer(), off, len);
debug("write", file.position(), off, len);
file.write(b, off, len);
}
......
......@@ -172,7 +172,7 @@ public class DebugFileSystem extends FileSystemWrapper {
public FileObject openFileObject(String fileName, String mode) throws IOException {
trace(fileName, "openFileObject", mode);
return new DebugFileObject(this, super.openFileObject(fileName, mode));
return new DebugFileObject(this, super.openFileObject(fileName, mode), fileName);
}
public OutputStream newOutputStream(String fileName, boolean append) {
......@@ -185,11 +185,6 @@ public class DebugFileSystem extends FileSystemWrapper {
super.moveTo(oldName, newName);
}
public boolean tryDelete(String fileName) {
trace(fileName, "tryDelete");
return super.tryDelete(fileName);
}
public String getPrefix() {
return PREFIX;
}
......
......@@ -41,7 +41,7 @@ public class FileObjectCrypt implements FileObject {
public FileObjectCrypt(String name, String algorithm, String password, FileObject file) throws IOException {
this.name = name;
this.file = file;
boolean newFile = file.length() < HEADER_LENGTH + BLOCK_SIZE;
boolean newFile = file.size() < HEADER_LENGTH + BLOCK_SIZE;
byte[] filePasswordHash;
if (algorithm.endsWith("-hash")) {
filePasswordHash = StringUtils.convertHexToBytes(password);
......@@ -56,11 +56,11 @@ public class FileObjectCrypt implements FileObject {
if (newFile) {
salt = MathUtils.secureRandomBytes(SALT_LENGTH);
file.write(HEADER, 0, HEADER.length);
file.seek(SALT_POS);
file.position(SALT_POS);
file.write(salt, 0, salt.length);
} else {
salt = new byte[SALT_LENGTH];
file.seek(SALT_POS);
file.position(SALT_POS);
file.readFully(salt, 0, SALT_LENGTH);
}
byte[] key = SHA256.getHashWithSalt(filePasswordHash, salt);
......@@ -69,27 +69,23 @@ public class FileObjectCrypt implements FileObject {
}
cipher.setKey(key);
bufferForInitVector = new byte[BLOCK_SIZE];
seek(0);
position(0);
}
public long getFilePointer() throws IOException {
return Math.max(0, file.getFilePointer() - HEADER_LENGTH);
public long position() throws IOException {
return Math.max(0, file.position() - HEADER_LENGTH);
}
public String getName() {
return name;
}
public long length() throws IOException {
return Math.max(0, file.length() - HEADER_LENGTH - BLOCK_SIZE);
public long size() throws IOException {
return Math.max(0, file.size() - HEADER_LENGTH - BLOCK_SIZE);
}
public void releaseLock() {
file.releaseLock();
}
public void seek(long pos) throws IOException {
file.seek(pos + HEADER_LENGTH);
public void position(long pos) throws IOException {
file.position(pos + HEADER_LENGTH);
}
public void sync() throws IOException {
......@@ -104,29 +100,30 @@ public class FileObjectCrypt implements FileObject {
file.close();
}
public void setFileLength(long newLength) throws IOException {
if (newLength < length()) {
public void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
int mod = (int) (newLength % BLOCK_SIZE);
if (mod == 0) {
file.setFileLength(HEADER_LENGTH + newLength);
file.truncate(HEADER_LENGTH + newLength);
} else {
file.setFileLength(HEADER_LENGTH + newLength + BLOCK_SIZE - mod);
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE - mod);
byte[] buff = new byte[BLOCK_SIZE - mod];
long pos = getFilePointer();
seek(newLength);
long pos = position();
position(newLength);
write(buff, 0, buff.length);
seek(pos);
position(pos);
}
}
file.setFileLength(HEADER_LENGTH + newLength + BLOCK_SIZE);
if (newLength < getFilePointer()) {
seek(newLength);
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE);
if (newLength < position()) {
position(newLength);
}
}
public void readFully(byte[] b, int off, int len) throws IOException {
long pos = getFilePointer();
long length = length();
long pos = position();
long length = size();
if (pos + len > length) {
throw new EOFException("pos: " + pos + " len: " + len + " length: " + length);
}
......@@ -140,19 +137,19 @@ public class FileObjectCrypt implements FileObject {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
seek(p);
position(p);
byte[] temp = new byte[l];
try {
readAligned(p, temp, 0, l);
System.arraycopy(temp, posMod, b, off, len);
} finally {
seek(pos + len);
position(pos + len);
}
}
}
public void write(byte[] b, int off, int len) throws IOException {
long pos = getFilePointer();
long pos = position();
int posMod = (int) (pos % BLOCK_SIZE);
if (posMod == 0 && len % BLOCK_SIZE == 0) {
byte[] temp = new byte[len];
......@@ -165,23 +162,27 @@ public class FileObjectCrypt implements FileObject {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
seek(p);
position(p);
byte[] temp = new byte[l];
if (file.length() < HEADER_LENGTH + p + l) {
file.setFileLength(HEADER_LENGTH + p + l);
if (file.size() < HEADER_LENGTH + p + l) {
file.position(HEADER_LENGTH + p + l - 1);
file.write(new byte[1], 0, 1);
position(p);
}
readAligned(p, temp, 0, l);
System.arraycopy(b, off, temp, posMod, len);
seek(p);
position(p);
try {
writeAligned(p, temp, 0, l);
} finally {
seek(pos + len);
position(pos + len);
}
}
pos = file.getFilePointer();
if (file.length() < pos + BLOCK_SIZE) {
file.setFileLength(pos + BLOCK_SIZE);
pos = file.position();
if (file.size() < pos + BLOCK_SIZE) {
file.position(pos + BLOCK_SIZE - 1);
file.write(new byte[1], 0, 1);
file.position(pos);
}
}
......@@ -237,4 +238,8 @@ public class FileObjectCrypt implements FileObject {
}
}
public String toString() {
return name;
}
}
......@@ -47,11 +47,11 @@ public class FileObjectZip2 implements FileObject {
}
}
public long getFilePointer() {
public long position() {
return pos;
}
public long length() {
public long size() {
return length;
}
......@@ -93,11 +93,11 @@ public class FileObjectZip2 implements FileObject {
inPos += len;
}
public void seek(long newPos) {
public void position(long newPos) {
this.pos = newPos;
}
public void setFileLength(long newLength) throws IOException {
public void truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
......@@ -109,10 +109,6 @@ public class FileObjectZip2 implements FileObject {
throw new IOException("File is read-only");
}
public String getName() {
return name;
}
public boolean tryLock() {
return false;
}
......@@ -121,4 +117,8 @@ public class FileObjectZip2 implements FileObject {
// ignore
}
public String toString() {
return name;
}
}
......@@ -314,7 +314,7 @@ public class FileShell extends Tool {
FileObject f = null;
try {
f = FileUtils.openFileObject(fileName, "rw");
f.setFileLength(length);
f.truncate(length);
} catch (IOException e) {
error(e);
} finally {
......@@ -334,9 +334,7 @@ public class FileShell extends Tool {
}
private void zip(String zipFileName, String base, ArrayList<String> source) {
if (FileUtils.exists(zipFileName)) {
FileUtils.delete(zipFileName);
}
OutputStream fileOut = null;
try {
fileOut = FileUtils.newOutputStream(zipFileName, false);
......
......@@ -272,10 +272,6 @@ public class FileSystemZip2 extends FileSystem {
throw DbException.getUnsupportedException("write");
}
public boolean tryDelete(String fileName) {
return false;
}
public String unwrap(String fileName) {
if (fileName.startsWith(PREFIX)) {
fileName = fileName.substring(PREFIX.length());
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论