package uc.files.transfer; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import uc.crypto.InterleaveHashes; import uc.files.downloadqueue.Block; import uc.files.downloadqueue.FileDQE; import uc.files.downloadqueue.FileListDQE; import uc.files.downloadqueue.TTHLDQE; /** * the abstract WriteableFileInterval * represents a starting point to make downloads independent * of what is downloaded .. TTHL files or FileList just * extend this abstract class to fit their needs. * * @author quicksilver * */ public abstract class AbstractWritableFileInterval extends AbstractFileInterval { public abstract WritableByteChannel getWriteChannel() throws IOException; public AbstractWritableFileInterval(long startpos, long length,long totalLength) { super(startpos, length, totalLength); } // @Override // public long getShownLength() { // return length(); // } // // @Override // public long getShownRelativePos() { // return currentpos-startpos; // } public static class FileListWriteInterval extends AbstractWritableFileInterval { private final FileListDQE fdqe; public FileListWriteInterval(FileListDQE fdqe, long length) { super(0L,length,length); this.fdqe = fdqe; } @Override public WritableByteChannel getWriteChannel() throws IOException { return new WritableByteChannel() { private final FileChannel fc = new FileOutputStream(fdqe.getTempPath()).getChannel(); public void close() throws IOException { boolean wasOpen = fc.isOpen(); fc.close(); if (currentpos == length && wasOpen) { fdqe.downloadedFilelist(); } } public boolean isOpen() { return fc.isOpen(); } public int write(ByteBuffer src) throws IOException { int written = fc.write(src); if (written >= 0) { currentpos += written; } return written; } }; } } public static class TTHLWriteInterval extends AbstractWritableFileInterval { private final TTHLDQE dqe; public TTHLWriteInterval(TTHLDQE dqe,long length) { super(0L,length,length); this.dqe = dqe; } public WritableByteChannel getWriteChannel() throws IOException { final ByteBuffer bbuf = ByteBuffer.allocate((int)length); bbuf.clear(); return new WritableByteChannel() { private boolean open = true; public int write(ByteBuffer src) throws IOException { int remaining = src.remaining(); if (remaining <= bbuf.remaining()) { bbuf.put(src); currentpos += remaining; return remaining; } else { return 0; } } /** * when the WritableByteChannel for interleaves is closed * the Interleave hashes are generated from the read bytes.. * afterwards the Interleave hashes are set to the DQE if they are valid */ public void close() throws IOException { if (open && !bbuf.hasRemaining()) { bbuf.flip(); InterleaveHashes ih = new InterleaveHashes(bbuf); if (ih.verify(dqe.getTTHRoot())) { dqe.onDownloadOfInterleaves(ih); } else { throw new IOException("Interleaves do not match root hash"); } } open = false; } public boolean isOpen() { return open; } }; } } public static class FileWriteInterval extends AbstractWritableFileInterval { private final FileDQE dqe; private final int startBlock; private volatile int currentBlock; //private volatile long bytesWrittenInCurrentBlock; public FileWriteInterval(FileDQE dqe, long startpos , long length) { super(startpos,length,dqe.getSize()); this.dqe = dqe; long blocksize = dqe.getBlock(0).getLength(); this.startBlock = blocksize == 0 ? 0: (int) (startpos / blocksize); if (blocksize != 0 && startpos % blocksize != 0 ) { throw new IllegalArgumentException("can't start download in the middle of a block sp:" +startpos+" blocksize: "+blocksize+" "+dqe.getBlock(startBlock).getState()); } this.currentBlock = startBlock; updateLength(); //this.currentpos = 0; //startpos; } public WritableByteChannel getWriteChannel() throws IOException { return new WritableByteChannel() { private Block current = dqe.getBlock(currentBlock); private WritableByteChannel currentBlockChannel = null; private int recursiveness = 0; public int write(ByteBuffer src) throws IOException { //set a working current Write channel if not already set if (currentBlockChannel == null) { if (current == null) { throw new IOException(); } synchronized(current) { if (current.isWritable()) { //current != null && removed null check... would be dereferenced before currentBlockChannel = current.getWriteChannel(); } else { return -1; } } } int written = currentBlockChannel.write(src); if (written == 0) { //no more bytes could be written.. //so advance by one block currentBlockChannel.close(); currentBlockChannel = null; currentBlock++; current = dqe.getBlock(currentBlock); updateLength(); if (++recursiveness > 20) { throw new IOException("write failed"); } return write(src); } else { recursiveness = 0; currentpos += written; // bytesWrittenInCurrentBlock += written; return written; } } public void close() throws IOException { //hook start.. this hook will jump in for 0 Byte files . Otherwise they can't be downloaded long blocksize = dqe.getBlock(0).getLength(); if (blocksize == 0) { dqe.getBlock(0).getWriteChannel().close(); } //hook end if (currentBlockChannel != null) { currentBlockChannel.close(); } } public boolean isOpen() { if (currentBlockChannel != null) { return currentBlockChannel.isOpen(); } else { return dqe.getBlock(currentBlock+1) != null; } } }; } /** * updates the length value.. * called only on each new Block.. * from the writing stream */ private void updateLength() { long total = 0; Block b = dqe.getBlock(startBlock); long blocksize = b.getLength(); total += blocksize * (currentBlock-startBlock); int current = currentBlock; while ((b = dqe.getBlock(current)) != null) { if (b.isWritable()) { total += b.getLength(); current++; } else { break; } } length = total; } } }