package org.gudy.azureus2.core3.disk.impl; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.Queue; import org.gudy.azureus2.core3.disk.DiskManager; import org.gudy.azureus2.core3.disk.DiskManagerPiece; import org.gudy.azureus2.core3.disk.DiskManagerReadRequest; import org.gudy.azureus2.core3.disk.DiskManagerReadRequestListener; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.util.DirectByteBuffer; import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo; import org.gudy.azureus2.plugins.download.Download; import org.gudy.azureus2.plugins.download.DownloadException; import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker; /** * This class allows plugins to read files sequentially, but still allows them * to use the DiskManager's Disk Cache * * @author isdal * */ public class SequentialDiskReaderImpl extends InputStream { // specify the number of seconds of video that has to be done after the // current piece to kick in the emergency piece getter // private final int MIN_REAL_TIME_LIMIT = 1; // specify the min percentage of completed data in the next 10 sec to make a // piece an emergency piece private static final double MIN_EMERGENCY_PERCENTAGE = 0.9; private final long numPiecesFor10sData; private EmergencyPieceProvider emergencyProvider; private final DiskManagerPiece[] pieces; private final DiskManager diskManager; private final DownloadManager dm; private volatile boolean quit = false; private Object syncObject = new Object(); private int currentPiece = 0; private int currentByteInPiece = 0; private final int firstPieceInFile; private final int lastPieceInFile; private final DiskManagerFileInfo fileInfo; private long totalWritten = 0; public SequentialDiskReaderImpl(DownloadManager dm, DiskManagerFileInfo fileInfo, long streamByteRate) { System.out.println("Creating Sequential Disk reader"); this.fileInfo = fileInfo; this.dm = dm; this.diskManager = dm.getDiskManager(); this.numPiecesFor10sData = Math.round(streamByteRate * 10.0 / diskManager.getPieceLength()); this.pieces = diskManager.getPieces(); // this.file = file; this.firstPieceInFile = fileInfo.getFirstPieceNumber(); this.lastPieceInFile = firstPieceInFile + fileInfo.getNumPieces(); this.currentPiece = firstPieceInFile; System.out.println("Sequential Disk reader started"); // files might start a couple bytes into a piece, try to figure out where it starts int fileIndex = fileInfo.getIndex(); long sumFileLen = 0; for (int i = 0; i < fileIndex; i++) { sumFileLen += dm.getTorrent().getFiles()[i].getLength(); } int posInPiece = 0; if (sumFileLen > 0) { posInPiece = (int) (sumFileLen % dm.getTorrent().getPieceLength()); } System.out.println("file starts at byte: " + sumFileLen + " in the torrent, and byte " + posInPiece + " in the piece"); currentByteInPiece = posInPiece; // check if the download is complete // boolean bComplete = fileInfo.getLength() == fileInfo.getDownloaded(); // if (!bComplete) { // DownloadManagerEnhancer dmEnhancer = DownloadManagerEnhancer // .getSingleton(); // if (dmEnhancer != null) { // edm = dmEnhancer.getEnhancedDownload(dm); // edm.setContentBps(streamBitRate); // System.out.println("edm progressive: " // + edm.getProgressiveMode()); // if (edm != null // && (!edm.supportsProgressiveMode() || edm // .getProgressivePlayETA() > 0)) { // return; // } // } // } } public void asyncReadCompleted() { synchronized (syncObject) { syncObject.notify(); } } /** * reads at most destination.length bytes from the file, waiting if * necessary for data to become available * * @param destination * the destination * @return the number of bytes read * @throws IOException */ @Override public int read(byte[] destination, int offset, int bytesToRead) throws IOException { if (destination == null) { throw new NullPointerException(); } if (offset < 0 || bytesToRead < 0 || offset + bytesToRead > destination.length) { throw new ArrayIndexOutOfBoundsException(); } // int bytesLeftToRead = length; int bytesRead = 0; // System.out.println("Got request for " + bytesToRead + " bytes"); Queue<ReadRequestListener> outstandingRequests = new LinkedList<ReadRequestListener>(); // first, create and submit all requests while (bytesToRead > bytesRead && !quit) { System.out.println("got request for " + bytesToRead + " currentPiece=" + currentPiece + " current byte=" + currentByteInPiece); // check if we are outside the file if (currentPiece > lastPieceInFile) { // check if we actually read anything if (bytesRead == 0) { // if not, return end of file reached return -1; } break; } try { boolean pieceBoosted = false; while (!pieces[currentPiece].isDone() && !quit) { // oups, we reached a piece that is not yet downloaded // if we already read something, return it if (!pieceBoosted) { System.out.println("Reached piece that is still downloading " + currentPiece); double percentageDone = percentageDoneOfNext10s(pieces, currentPiece); if (percentageDone > MIN_EMERGENCY_PERCENTAGE) { System.out.println("boosting piece"); if (emergencyProvider == null) { PiecePicker picker = dm.getPeerManager().getPiecePicker(); emergencyProvider = new EmergencyPieceProvider(); emergencyProvider.activate(picker); } emergencyProvider.boostPiece(currentPiece); pieceBoosted = true; } else { System.out.println("not boosting it yet, percent done=" + percentageDone + " limit=" + MIN_EMERGENCY_PERCENTAGE); } // // long numDoneBytesAfterPiece = // numDoneBytesAfterPiece( // pieces, currentPiece); // if (numDoneBytesAfterPiece > realTimeLimit) { // System.out.println("boosting piece"); // // if (emergencyProvider == null) { // PiecePicker picker = dm.getPeerManager() // .getPiecePicker(); // emergencyProvider = new EmergencyPieceProvider(); // emergencyProvider.activate(picker); // } // emergencyProvider.boostPiece(currentPiece); // pieceBoosted = true; // } else { // System.out.println("not boosting it yet, numDome=" // + numDoneBytesAfterPiece + " limit=" // + realTimeLimit); // // } } if (bytesRead > 0) { break; } else { // ok, lets just wait a while and try again // TODO I'm sure there is a listener I can attach in // some // way to get notified about this event, might want to // figure that out later Thread.sleep(500); System.out.println("Waiting for piece " + currentPiece + " to download"); System.out.println("status of next piece: " + pieces[currentPiece + 1].isDone()); if (fileInfo.getDownload().getState() == Download.ST_STOPPED) { System.out.println("download stopped"); System.err.println("timed out waiting for download to complete"); System.err.println("total written from stream: " + totalWritten); System.err.println("download manager state: " + dm.getState()); throw new IOException("read position=" + totalWritten); } } } } catch (InterruptedException e) { } catch (DownloadException e) { e.printStackTrace(); return -1; } int bytesLeftInPiece = pieces[currentPiece].getLength() - currentByteInPiece; int bytesLeftToRead = bytesToRead - bytesRead; // check if this read can be handled by this piece boolean partialRead = bytesLeftInPiece > bytesLeftToRead; DiskManagerReadRequest readRequest; if (partialRead) { readRequest = diskManager.createReadRequest(currentPiece, currentByteInPiece, bytesLeftToRead); // move the pointer of where we are in the piece currentByteInPiece += bytesLeftToRead; bytesRead += bytesLeftToRead; } else { readRequest = diskManager.createReadRequest(currentPiece, currentByteInPiece, bytesLeftInPiece); // ok, so after this we need to move on to the next piece currentPiece++; currentByteInPiece = 0; // and update the left to read counter bytesRead += bytesLeftInPiece; } System.out.println("enqueueing read request: " + readRequest.getPieceNumber() + " " + readRequest.getOffset()); ReadRequestListener listener = new ReadRequestListener(this); outstandingRequests.add(listener); diskManager.enqueueReadRequest(readRequest, listener); } ReadRequestListener readRequest = outstandingRequests.poll(); int writePosition = 0; while (readRequest != null) { byte[] data = readRequest.getData(); // wait for the data to become available synchronized (syncObject) { while (data == null) { try { System.out.println("Waiting to asynchrounus read to complete " + writePosition + "/" + bytesRead); syncObject.wait(2000); data = readRequest.getData(); if (data != null) { System.out.println("asynchronus read completed"); } else { System.err.println("timed out waiting for read to complete"); System.err.println("total written from stream: " + totalWritten); System.err.println("download manager state: " + dm.getState()); if (fileInfo.getDownloaded() == fileInfo.getLength()) { // we are done anyway... let the file reader take care of this throw new IOException("read position=" + totalWritten); } else if (dm.getState() == DownloadManager.STATE_DOWNLOADING) { // ok, maybe the read was slow, try again syncObject.wait(5000); data = readRequest.getData(); if (data != null) { System.out.println("asynchronus read completed"); } else { // ok, this must be an error return -1; } } } } catch (InterruptedException e) { System.out.println("SequentialDiskReader interupted"); return -1; } if (readRequest.readFailed()) { System.out.println("Read failed"); return -1; } } // great! we got the data, copy and update writePosition System.arraycopy(data, 0, destination, writePosition, data.length); writePosition += data.length; totalWritten += data.length; } readRequest = outstandingRequests.poll(); } if (writePosition != bytesRead) { System.err.println("SequentialDiskReader: Strange, requested " + bytesRead + ", got " + writePosition); } return bytesRead; } private double percentageDoneOfNext10s(DiskManagerPiece[] pieces, int currentPiece) { double total = 0; double completed = 0; for (int i = currentPiece; i < currentPiece + numPiecesFor10sData && i < pieces.length; i++) { DiskManagerPiece piece = pieces[i]; boolean[] written = piece.getWritten(); total += piece.getLength(); if (written == null) { completed += piece.getLength(); } else { for (int j = 0; j < written.length; j++) { if (written[j]) { completed += piece.getBlockSize(j); } } } } return completed / total; } @Override public int read() throws IOException { byte[] b = new byte[1]; this.read(b); return b[0]; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public void close() { quit = true; if (emergencyProvider != null) { emergencyProvider.deactivate(dm.getPeerManager().getPiecePicker()); } } @Override /** * calculates the number of byte */ public int available() { return 0; } @Override public void mark(int readLimit) { } @Override public boolean markSupported() { return false; } @Override public long skip(long n) { return 0; } public void reset() { currentByteInPiece = 0; currentPiece = firstPieceInFile; } private class ReadRequestListener implements DiskManagerReadRequestListener { private final SequentialDiskReaderImpl parent; private volatile byte[] data = null; private volatile boolean error = false; public ReadRequestListener(SequentialDiskReaderImpl parent) { this.parent = parent; } public int getPriority() { // TODO Auto-generated method stub return 0; } public boolean readFailed() { return error; } public void readCompleted(DiskManagerReadRequest request, DirectByteBuffer dataBuffer) { ByteBuffer buffer = dataBuffer.getBuffer(DirectByteBuffer.SS_CACHE); buffer.position(0); int len = buffer.limit(); this.data = new byte[len]; buffer.get(data); buffer.position(0); parent.asyncReadCompleted(); } public void readFailed(DiskManagerReadRequest request, Throwable cause) { cause.printStackTrace(); error = true; parent.asyncReadCompleted(); } public byte[] getData() { return data; } public void requestExecuted(long bytes) { // TODO Auto-generated method stub } } }