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.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.api.Bucket; import freenet.support.api.RandomAccessBucket; /* * This code is part of FProxy, an HTTP proxy server for Freenet. * It is distributed under the GNU Public Licence (GPL) version 2. See * http://www.gnu.org/ for further details of the GPL. */ /** * Temporary file handling. TempFileBuckets start empty. * * @author giannij */ public class TempFileBucket extends BaseFileBucket implements Bucket, Serializable { // Should not be serialized but we need Serializable to save the parent state for PersistentTempFileBucket. private static final long serialVersionUID = 1L; long filenameID; protected transient FilenameGenerator generator; private boolean readOnly; private final boolean deleteOnFree; private File file; private transient boolean resumed; private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } public TempFileBucket(long id, FilenameGenerator generator) { // deleteOnExit -> files get stuck in a big HashSet, whether or not // they are deleted. This grows without bound, it's a major memory // leak. this(id, generator, true); this.file = generator.getFilename(id); } /** * Constructor for the TempFileBucket object * Subclasses can call this constructor. * @param deleteOnExit Set if you want the bucket deleted on shutdown. Passed to * the parent BaseFileBucket. You must also override deleteOnExit() and * implement your own createShadow()! * @param deleteOnFree True for a normal temp bucket, false for a shadow. */ protected TempFileBucket( long id, FilenameGenerator generator, boolean deleteOnFree) { super(generator.getFilename(id), false); this.filenameID = id; this.generator = generator; this.deleteOnFree = deleteOnFree; this.file = generator.getFilename(id); if (logDEBUG) { Logger.debug(this,"Initializing TempFileBucket(" + getFile()); } } protected TempFileBucket() { // For serialization. deleteOnFree = false; } @Override protected boolean createFileOnly() { return false; } @Override protected boolean deleteOnFree() { return deleteOnFree; } @Override public File getFile() { if(file != null) return file; return generator.getFilename(filenameID); } @Override public boolean isReadOnly() { return readOnly; } @Override public void setReadOnly() { readOnly = true; } @Override protected boolean deleteOnExit() { // Temp files will be cleaned up on next restart. // File.deleteOnExit() is a hideous memory leak. // It should NOT be used for temp files. return false; } @Override public RandomAccessBucket createShadow() { TempFileBucket ret = new TempFileBucket(filenameID, generator, false); ret.setReadOnly(); if(!getFile().exists()) Logger.error(this, "File does not exist when creating shadow: "+getFile()); return ret; } protected void innerResume(ClientContext context) throws ResumeFailedException { generator = context.persistentFG; if(file == null) { // Migrating from old tempfile, possibly db4o era. file = generator.getFilename(filenameID); checkExists(file); } else { // File must exist! if(!file.exists()) { // Maybe moved after the last checkpoint? File f = generator.getFilename(filenameID); if(f.exists()) { file = f; } } checkExists(file); file = generator.maybeMove(file, filenameID); } } @Override public final void onResume(ClientContext context) throws ResumeFailedException { if(!persistent()) throw new UnsupportedOperationException(); synchronized(this) { if(resumed) return; resumed = true; } super.onResume(context); innerResume(context); } private void checkExists(File file) throws ResumeFailedException { // File must exist! try { if(!(file.createNewFile() || file.exists())) throw new ResumeFailedException("Tempfile "+file+" does not exist and cannot be created"); } catch (IOException e) { throw new ResumeFailedException("Tempfile cannot be created"); } } protected boolean persistent() { return false; } @Override protected boolean tempFileAlreadyExists() { return true; } static final int VERSION = 1; @Override public void storeTo(DataOutputStream dos) throws IOException { dos.writeInt(magic()); super.storeTo(dos); dos.writeInt(VERSION); dos.writeLong(filenameID); dos.writeBoolean(readOnly); dos.writeBoolean(deleteOnFree); dos.writeUTF(file.toString()); } protected int magic() { throw new UnsupportedOperationException(); } protected TempFileBucket(DataInputStream dis) throws IOException, StorageFormatException { super(dis); int version = dis.readInt(); if(version != VERSION) throw new StorageFormatException("Bad version"); filenameID = dis.readLong(); if(filenameID == -1) throw new StorageFormatException("Bad filename ID"); readOnly = dis.readBoolean(); deleteOnFree = dis.readBoolean(); file = new File(dis.readUTF()); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (deleteOnFree ? 1231 : 1237); result = prime * result + (int) (filenameID ^ (filenameID >>> 32)); 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; } TempFileBucket other = (TempFileBucket) obj; if (deleteOnFree != other.deleteOnFree) { return false; } if (filenameID != other.filenameID) { return false; } if (readOnly != other.readOnly) { return false; } return true; } }