/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.support.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import freenet.client.async.ClientContext;
import freenet.support.Logger;
import freenet.support.api.Bucket;
import freenet.support.api.RandomAccessBucket;
/**
* A file Bucket is an implementation of Bucket that writes to a file.
*
* @author oskar
*/
public class FileBucket extends BaseFileBucket implements Bucket, Serializable {
private static final long serialVersionUID = 1L;
protected final File file;
protected boolean readOnly;
protected boolean deleteOnFree;
protected final boolean deleteOnExit;
protected final boolean createFileOnly;
// JVM caches File.size() and there is no way to flush the cache, so we
// need to track it ourselves
private static volatile boolean logMINOR;
static {
Logger.registerClass(FileBucket.class);
}
/**
* Creates a new FileBucket.
*
* @param file The File to read and write to.
* @param readOnly If true, any attempt to write to the bucket will result in an IOException.
* Can be set later. Irreversible. @see isReadOnly(), setReadOnly()
* @param createFileOnly If true, create the file if it doesn't exist, but if it does exist,
* throw a FileExistsException on any write operation. This is safe against symlink attacks
* because we write to a temp file and then rename. It is technically possible that the rename
* will clobber an existing file if there is a race condition, but since it will not write over
* a symlink this is probably not dangerous. User-supplied filenames should in any case be
* restricted by higher levels.
* @param deleteOnExit If true, delete the file on a clean exit of the JVM. Irreversible - use with care!
* @param deleteOnFree If true, delete the file on finalization. Reversible.
*/
public FileBucket(File file, boolean readOnly, boolean createFileOnly, boolean deleteOnExit, boolean deleteOnFree) {
super(file, deleteOnExit);
if(file == null) throw new NullPointerException();
File origFile = file;
file = file.getAbsoluteFile();
// Copy it so we can safely delete it.
if(origFile == file)
file = new File(file.getPath());
this.readOnly = readOnly;
this.createFileOnly = createFileOnly;
this.file = file;
this.deleteOnFree = deleteOnFree;
this.deleteOnExit = deleteOnExit;
// Useful for finding temp file leaks.
// System.err.println("-- FileBucket.ctr(0) -- " +
// file.getAbsolutePath());
// (new Exception("get stack")).printStackTrace();
fileRestartCounter = 0;
}
protected FileBucket() {
// For serialization.
super();
file = null;
deleteOnExit = false;
createFileOnly = false;
}
/**
* Returns the file object this buckets data is kept in.
*/
@Override
public synchronized File getFile() {
return file;
}
@Override
public synchronized boolean isReadOnly() {
return readOnly;
}
@Override
public synchronized void setReadOnly() {
readOnly = true;
}
@Override
protected boolean createFileOnly() {
return createFileOnly;
}
@Override
protected boolean deleteOnExit() {
return deleteOnExit;
}
@Override
protected boolean deleteOnFree() {
return deleteOnFree;
}
@Override
public RandomAccessBucket createShadow() {
String fnam = file.getPath();
File newFile = new File(fnam);
return new FileBucket(newFile, true, false, false, false);
}
@Override
public void onResume(ClientContext context) throws ResumeFailedException {
super.onResume(context);
}
@Override
protected boolean tempFileAlreadyExists() {
return false;
}
public static final int MAGIC = 0x8fe6e41b;
static final int VERSION = 1;
@Override
public void storeTo(DataOutputStream dos) throws IOException {
dos.writeInt(MAGIC);
super.storeTo(dos);
dos.writeInt(VERSION);
dos.writeUTF(file.toString());
dos.writeBoolean(readOnly);
dos.writeBoolean(deleteOnFree);
if(deleteOnExit) throw new IllegalStateException("Must not free on exit if persistent");
dos.writeBoolean(createFileOnly);
}
protected FileBucket(DataInputStream dis) throws IOException, StorageFormatException {
super(dis);
int version = dis.readInt();
if(version != VERSION) throw new StorageFormatException("Bad version");
file = new File(dis.readUTF());
readOnly = dis.readBoolean();
deleteOnFree = dis.readBoolean();
deleteOnExit = false;
createFileOnly = dis.readBoolean();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (createFileOnly ? 1231 : 1237);
result = prime * result + (deleteOnExit ? 1231 : 1237);
result = prime * result + (deleteOnFree ? 1231 : 1237);
result = prime * result + ((file == null) ? 0 : file.hashCode());
result = prime * result + (readOnly ? 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;
}
FileBucket other = (FileBucket) obj;
if (createFileOnly != other.createFileOnly) {
return false;
}
if (deleteOnExit != other.deleteOnExit) {
return false;
}
if (deleteOnFree != other.deleteOnFree) {
return false;
}
if (file == null) {
if (other.file != null) {
return false;
}
} else if (!file.equals(other.file)) {
return false;
}
if (readOnly != other.readOnly) {
return false;
}
return true;
}
}