/* 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.BufferedInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.Serializable; import freenet.client.async.ClientContext; import freenet.support.api.Bucket; /** * FIXME: implement a hash verifying version of this. */ public class ReadOnlyFileSliceBucket implements Bucket, Serializable { private static final long serialVersionUID = 1L; private final File file; private final long startAt; private final long length; /** * zero arg c'tor for db4o on jamvm / serialization */ @SuppressWarnings("unused") private ReadOnlyFileSliceBucket() { startAt = 0; length = 0; file = null; } public ReadOnlyFileSliceBucket(File f, long startAt, long length) { this.file = new File(f.getPath()); // copy so we can delete it this.startAt = startAt; this.length = length; } @Override public OutputStream getOutputStream() throws IOException { throw new IOException("Bucket is read-only"); } @Override public OutputStream getOutputStreamUnbuffered() throws IOException { throw new IOException("Bucket is read-only"); } @Override public InputStream getInputStream() throws IOException { return new BufferedInputStream(getInputStreamUnbuffered()); } @Override public InputStream getInputStreamUnbuffered() throws IOException { return new MyInputStream(); } @Override public String getName() { return "ROFS:" + file.getAbsolutePath() + ':' + startAt + ':' + length; } @Override public long size() { return length; } @Override public boolean isReadOnly() { return true; } @Override public void setReadOnly() { // Do nothing } private class MyInputStream extends InputStream { private RandomAccessFile f; private long ptr; // relative to startAt MyInputStream() throws IOException { try { this.f = new RandomAccessFile(file, "r"); f.seek(startAt); if(f.length() < (startAt + length)) throw new ReadOnlyFileSliceBucketException("File truncated? Length " + f.length() + " but start at " + startAt + " for " + length + " bytes"); ptr = 0; } catch(FileNotFoundException e) { throw new ReadOnlyFileSliceBucketException(e); } } @Override public int read() throws IOException { if(ptr >= length) return -1; int x = f.read(); if(x != -1) ptr++; return x; } @Override public int read(byte[] buf, int offset, int len) throws IOException { if(ptr >= length) return -1; len = (int) Math.min(len, length - ptr); int x = f.read(buf, offset, len); ptr += x; return x; } @Override public int read(byte[] buf) throws IOException { return read(buf, 0, buf.length); } @Override public void close() throws IOException { f.close(); } } public static class ReadOnlyFileSliceBucketException extends IOException { private static final long serialVersionUID = -1; public ReadOnlyFileSliceBucketException(FileNotFoundException e) { super("File not found: " + e.getMessage()); initCause(e); } public ReadOnlyFileSliceBucketException(String string) { super(string); } } @Override public void free() { } @Override public Bucket createShadow() { String fnam = file.getPath(); File newFile = new File(fnam); return new ReadOnlyFileSliceBucket(newFile, startAt, length); } @Override public void onResume(ClientContext context) { // Do nothing. } static final int MAGIC = 0x99e54c4; static final int VERSION = 1; @Override public void storeTo(DataOutputStream dos) throws IOException { dos.writeInt(MAGIC); dos.writeInt(VERSION); dos.writeUTF(file.toString()); dos.writeLong(startAt); dos.writeLong(length); } protected ReadOnlyFileSliceBucket(DataInputStream dis) throws StorageFormatException, IOException { int version = dis.readInt(); if(version != VERSION) throw new StorageFormatException("Bad version"); file = new File(dis.readUTF()); startAt = dis.readLong(); if(startAt < 0) throw new StorageFormatException("Bad start at"); length = dis.readLong(); if(length < 0) throw new StorageFormatException("Bad length"); if(!file.exists()) throw new StorageFormatException("File does not exist any more"); if(file.length() < startAt+length) throw new StorageFormatException("Slice does not fit in file"); } }