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