/** * 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.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import freenet.client.async.ClientContext; import freenet.crypt.MasterSecret; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.api.Bucket; import freenet.support.api.LockableRandomAccessBuffer; import freenet.support.api.RandomAccessBucket; public class DelayedFreeRandomAccessBucket implements Bucket, Serializable, RandomAccessBucket, DelayedFree { private static final long serialVersionUID = 1L; // Only set on construction and on onResume() on startup. So shouldn't need locking. private transient PersistentFileTracker factory; private final RandomAccessBucket bucket; private boolean freed; private transient long createdCommitID; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } @Override public boolean toFree() { return freed; } public DelayedFreeRandomAccessBucket(PersistentFileTracker factory, RandomAccessBucket bucket) { this.factory = factory; this.bucket = bucket; this.createdCommitID = factory.commitID(); if(bucket == null) throw new NullPointerException(); } @Override public OutputStream getOutputStream() throws IOException { synchronized(this) { if(freed) throw new IOException("Already freed"); } return bucket.getOutputStream(); } @Override public OutputStream getOutputStreamUnbuffered() throws IOException { synchronized(this) { if(freed) throw new IOException("Already freed"); } return bucket.getOutputStreamUnbuffered(); } @Override public InputStream getInputStream() throws IOException { synchronized(this) { if(freed) throw new IOException("Already freed"); } return bucket.getInputStream(); } @Override public InputStream getInputStreamUnbuffered() throws IOException { synchronized(this) { if(freed) throw new IOException("Already freed"); } return bucket.getInputStreamUnbuffered(); } @Override public String getName() { return bucket.getName(); } @Override public long size() { return bucket.size(); } @Override public boolean isReadOnly() { return bucket.isReadOnly(); } @Override public void setReadOnly() { bucket.setReadOnly(); } public synchronized Bucket getUnderlying() { if(freed) return null; return bucket; } @Override public void free() { synchronized(this) { if(freed) return; freed = true; } if(logMINOR) Logger.minor(this, "Freeing "+this+" underlying="+bucket, new Exception("debug")); this.factory.delayedFree(this, createdCommitID); } @Override public String toString() { return super.toString()+":"+bucket; } @Override public RandomAccessBucket createShadow() { return bucket.createShadow(); } @Override public void realFree() { bucket.free(); } @Override public void onResume(ClientContext context) throws ResumeFailedException { this.factory = context.persistentBucketFactory; bucket.onResume(context); } static final int MAGIC = 0xa28f2a2d; static final int VERSION = 1; @Override public void storeTo(DataOutputStream dos) throws IOException { dos.writeInt(MAGIC); dos.writeInt(VERSION); bucket.storeTo(dos); } protected DelayedFreeRandomAccessBucket(DataInputStream dis, FilenameGenerator fg, PersistentFileTracker persistentFileTracker, MasterSecret masterKey) throws StorageFormatException, IOException, ResumeFailedException { int version = dis.readInt(); if(version != VERSION) throw new StorageFormatException("Bad version"); bucket = (RandomAccessBucket) BucketTools.restoreFrom(dis, fg, persistentFileTracker, masterKey); } @Override public LockableRandomAccessBuffer toRandomAccessBuffer() throws IOException { synchronized(this) { if(freed) throw new IOException("Already freed"); } setReadOnly(); return new DelayedFreeRandomAccessBuffer(bucket.toRandomAccessBuffer(), factory); } }