package freenet.support.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;
import freenet.client.async.ClientContext;
import freenet.support.Logger;
import freenet.support.api.LockableRandomAccessBuffer;
public class FileRandomAccessBuffer implements LockableRandomAccessBuffer, Serializable {
private static final long serialVersionUID = 1L;
transient RandomAccessFile raf;
final File file;
private boolean closed = false;
private final long length;
private final boolean readOnly;
private boolean secureDelete;
public FileRandomAccessBuffer(RandomAccessFile raf, File filename, boolean readOnly) throws IOException {
this.raf = raf;
this.file = filename;
length = raf.length();
this.readOnly = readOnly;
}
public FileRandomAccessBuffer(File filename, long length, boolean readOnly) throws IOException {
raf = new RandomAccessFile(filename, readOnly ? "r" : "rw");
raf.setLength(length);
this.length = length;
this.file = filename;
this.readOnly = readOnly;
}
public FileRandomAccessBuffer(File filename, boolean readOnly) throws IOException {
raf = new RandomAccessFile(filename, readOnly ? "r" : "rw");
this.length = raf.length();
this.file = filename;
this.readOnly = readOnly;
}
@Override
public void pread(long fileOffset, byte[] buf, int bufOffset, int length)
throws IOException {
if(fileOffset < 0) throw new IllegalArgumentException();
if(fileOffset + length > this.length)
throw new IOException("Length limit exceeded reading "+length+" bytes from "+fileOffset+" of "+this.length);
// FIXME Use NIO (which has proper pread, with concurrency)! This is absurd!
synchronized(this) {
raf.seek(fileOffset);
raf.readFully(buf, bufOffset, length);
}
}
@Override
public void pwrite(long fileOffset, byte[] buf, int bufOffset, int length)
throws IOException {
if(fileOffset < 0) throw new IllegalArgumentException();
if(fileOffset + length > this.length)
throw new IOException("Length limit exceeded writing "+length+" bytes from "+fileOffset+" of "+this.length);
if(readOnly) throw new IOException("Read only");
// FIXME Use NIO (which has proper pwrite, with concurrency)! This is absurd!
synchronized(this) {
raf.seek(fileOffset);
raf.write(buf, bufOffset, length);
}
}
@Override
public long size() {
return length;
}
@Override
public void close() {
synchronized(this) {
if(closed) return;
closed = true;
}
try {
raf.close();
} catch (IOException e) {
Logger.error(this, "Could not close "+raf+" : "+e+" for "+this, e);
}
}
@Override
public RAFLock lockOpen() {
return new RAFLock() {
@Override
protected void innerUnlock() {
// Do nothing. RAFW is always open.
}
};
}
@Override
public void free() {
close();
if(secureDelete) {
try {
FileUtil.secureDelete(file);
} catch (IOException e) {
Logger.error(this, "Unable to delete "+file+" : "+e, e);
System.err.println("Unable to delete temporary file "+file);
}
} else {
file.delete();
}
}
public void setSecureDelete(boolean secureDelete) {
this.secureDelete = secureDelete;
}
@Override
public void onResume(ClientContext context) throws ResumeFailedException {
if(!file.exists()) throw new ResumeFailedException("File does not exist any more");
if(file.length() != length) throw new ResumeFailedException("File is wrong length");
try {
raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
} catch (FileNotFoundException e) {
throw new ResumeFailedException("File does not exist any more");
}
}
static final int MAGIC = 0xdd0f4ab2;
static final int VERSION = 1;
@Override
public void storeTo(DataOutputStream dos) throws IOException {
dos.writeInt(MAGIC);
dos.writeInt(VERSION);
dos.writeUTF(file.toString());
dos.writeBoolean(readOnly);
dos.writeLong(length);
dos.writeBoolean(secureDelete);
}
public FileRandomAccessBuffer(DataInputStream dis) throws IOException, StorageFormatException, ResumeFailedException {
int version = dis.readInt();
if(version != VERSION) throw new StorageFormatException("Bad version");
file = new File(dis.readUTF());
readOnly = dis.readBoolean();
length = dis.readLong();
secureDelete = dis.readBoolean();
if(length < 0) throw new StorageFormatException("Bad length");
// Have to check here because we need the RAF immediately.
if(!file.exists()) throw new ResumeFailedException("File does not exist");
if(length > file.length()) throw new ResumeFailedException("Bad length");
this.raf = new RandomAccessFile(file, readOnly ? "r" : "rw");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((file == null) ? 0 : file.hashCode());
result = prime * result + (int) (length ^ (length >>> 32));
result = prime * result + (readOnly ? 1231 : 1237);
result = prime * result + (secureDelete ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FileRandomAccessBuffer other = (FileRandomAccessBuffer) obj;
if (!file.equals(other.file)) {
return false;
}
if (length != other.length) {
return false;
}
if (readOnly != other.readOnly) {
return false;
}
if (secureDelete != other.secureDelete) {
return false;
}
return true;
}
}