package freenet.support.io; import java.io.File; import java.io.IOException; import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import freenet.support.Logger; import freenet.support.api.LockableRandomAccessBuffer; import freenet.support.api.LockableRandomAccessBufferFactory; public class DiskSpaceCheckingRandomAccessBufferFactory implements LockableRandomAccessBufferFactory, DiskSpaceChecker, FileRandomAccessBufferFactory { private final LockableRandomAccessBufferFactory underlying; private final File dir; private volatile long minDiskSpace; /** LOCKING: We synchronize throughout the whole operation to prevent fragmentation and to have * an accurate free space estimate. FIXME ideally this would be per-filesystem. It might be * possible to get that information from Java (1.7) via java.nio.file. */ private static final Lock lock = new ReentrantLock(true); public DiskSpaceCheckingRandomAccessBufferFactory(LockableRandomAccessBufferFactory underlying, File dir, long minDiskSpace) { this.underlying = underlying; this.dir = dir; this.minDiskSpace = minDiskSpace; } public void setMinDiskSpace(long min) { if(min < 0) throw new IllegalArgumentException(); this.minDiskSpace = min; } @Override public LockableRandomAccessBuffer makeRAF(long size) throws IOException { lock.lock(); try { if(dir.getUsableSpace() > size + minDiskSpace) return underlying.makeRAF(size); else throw new InsufficientDiskSpaceException(); } finally { lock.unlock(); } } @Override public synchronized LockableRandomAccessBuffer makeRAF(byte[] initialContents, int offset, int size, boolean readOnly) throws IOException { lock.lock(); try { if(dir.getUsableSpace() > size + minDiskSpace) return underlying.makeRAF(initialContents, offset, size, readOnly); else throw new InsufficientDiskSpaceException(); } finally { lock.unlock(); } } public String toString() { return super.toString()+":"+underlying.toString(); } /** Create a new RAF for a specified file, which must exist but be 0 bytes long. Will delete * the file if an RAF cannot be created. * @throws InsufficientDiskSpaceException If there is not enough disk space. * @throws IOException If some other disk I/O error occurs. */ public PooledFileRandomAccessBuffer createNewRAF(File file, long size, Random random) throws IOException { lock.lock(); PooledFileRandomAccessBuffer ret = null; try { if(!file.exists()) throw new IOException("File does not exist"); if(file.length() != 0) throw new IOException("File is wrong length"); // FIXME ideally we would have separate locks for each filesystem ... if(dir.getUsableSpace() > size + minDiskSpace) { ret = new PooledFileRandomAccessBuffer(file, false, size, random, -1, true); return ret; } else { throw new InsufficientDiskSpaceException(); } } finally { if(ret == null) file.delete(); lock.unlock(); } } @Override public boolean checkDiskSpace(File file, int toWrite, int bufferSize) { if(!FileUtil.isParent(dir, file)) { Logger.error(this, "Not checking disk space because "+file+" is not child of "+dir); return true; } lock.lock(); try { if(dir.getUsableSpace() - (toWrite + bufferSize) < minDiskSpace) return false; else return true; } finally { lock.unlock(); } } }