package edu.washington.cs.oneswarm.f2f.multisource; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; 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.peer.impl.PEPeerControl; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentException; import org.gudy.azureus2.core3.torrent.TOTorrentFile; import org.gudy.azureus2.core3.util.DirectByteBuffer; import org.gudy.azureus2.core3.util.DirectByteBufferPool; import org.gudy.azureus2.core3.util.HashWrapper; import com.aelitis.azureus.core.peermanager.piecepicker.util.BitFlags; import edu.washington.cs.oneswarm.f2f.FileListFile; public class Sha1PieceRequestTranslator { private final static Logger logger = Logger.getLogger(Sha1PieceRequestTranslator.class.getName()); private final DownloadManager dstDownloadManager; private final PEPeerControl sourceManager; private final DownloadManager srcDownloadManager; private final Sha1Peer virtualPeer; private final VirtualPiece[] virtualPieces; public Sha1PieceRequestTranslator(Sha1Peer peer, DownloadManager sourceDownloadManager, DownloadManager destinationDownloadManager) throws TOTorrentException, PieceTranslationExcetion { logger.fine("created sha1piecetranslator: " + sourceDownloadManager.getDisplayName() + "->" + destinationDownloadManager.getDisplayName()); this.virtualPeer = peer; this.srcDownloadManager = sourceDownloadManager; this.sourceManager = (PEPeerControl) sourceDownloadManager.getPeerManager(); this.dstDownloadManager = destinationDownloadManager; if (logger.isLoggable(Level.FINER)) { printTorrentInfo(); } this.virtualPieces = createVirtualPieces(); logger.finer("created virtual pieces, " + getAvailable().nbSet + "/" + virtualPieces.length + " pieces available"); } private VirtualPiece[] createVirtualPieces() throws TOTorrentException, PieceTranslationExcetion { VirtualPiece[] pieces = new VirtualPiece[dstDownloadManager.getNbPieces()]; // for each file in the destination torrent, try to locate it in the // source torrent TOTorrent destTorrent = dstDownloadManager.getTorrent(); TOTorrent sourceTorrent = srcDownloadManager.getTorrent(); TOTorrentFile[] destFiles = destTorrent.getFiles(); HashWrapper[] destFileSha1s = Sha1DownloadManager.getHashesFromDownload( dstDownloadManager, FileListFile.KEY_SHA1_HASH, false); HashWrapper[] srcFileSha1s = Sha1DownloadManager.getHashesFromDownload( srcDownloadManager, FileListFile.KEY_SHA1_HASH, false); TOTorrentFile[] sourceFiles = sourceTorrent.getFiles(); for (int i = 0; i < destFiles.length; i++) { TOTorrentFile destFile = destFiles[i]; HashWrapper destFileSha1 = destFileSha1s[i]; for (int j = 0; j < sourceFiles.length; j++) { TOTorrentFile sourceFile = sourceFiles[j]; HashWrapper sourceFileSha1 = srcFileSha1s[j]; if (destFileSha1.equals(sourceFileSha1)) { logger.fine("found sha1 match: " + destFile.getRelativePath() + "==" + sourceFile.getRelativePath()); DiskManagerPiece[] dstPieces = dstDownloadManager.getDiskManager().getPieces(); DiskManagerPiece[] srcPieces = srcDownloadManager.getDiskManager().getPieces(); long dstFileStartPosInTorrent = getFileStartPosInTorrent(destFiles, i); long srcFileStartPosInTorrent = getFileStartPosInTorrent(sourceFiles, j); logger.finer("src file starts at: " + srcFileStartPosInTorrent); logger.finer("dst file starts at: " + dstFileStartPosInTorrent); /* * ok, time for the tricky part * * for each piece in the destination file we need to create * a virtual piece and put in in the pieces array */ file_loop: for (int pieceNum = destFile.getFirstPieceNumber(); pieceNum <= destFile.getLastPieceNumber(); pieceNum++) { // for each virtual piece we need: // the start offset, that is the position in the source // piece where the destination piece starts // + the pieces in the source that corresponds to the // current piece in the destination // get the position in the destination file for the // current piece logger.finest("creating virtual piece: " + pieceNum); long dstPiecePosInTorrent = 0; for (int k = 0; k < pieceNum; k++) { dstPiecePosInTorrent += dstPieces[k].getLength(); } logger.finest("dst piece starts at pos " + dstPiecePosInTorrent + " in torrent"); // this piece is the first piece in the file, the // beginning of // it will contain parts of other files... nothing we // can do, lets try the next one if (dstFileStartPosInTorrent > dstPiecePosInTorrent) { logger.finest("dst piece starts before file, skipping"); continue file_loop; } // ok, we found the position in the file were we are, // next, find what piece+offset that corresponds to in // the source file long dstPiecePosInFile = dstPiecePosInTorrent - dstFileStartPosInTorrent; logger.finest("dst piece starts " + dstPiecePosInFile + " bytes into the file"); /* * next, we need to find the pieces that contains the * range * [dstPiecePosInFile,dstPiecePosInFile+currentDstPieceSize * ] */ // start by finding the first piece and the piece offset long dstPieceStartPosInSrcTorrent = srcFileStartPosInTorrent + dstPiecePosInFile; logger.finest("destination piece starts at " + dstPieceStartPosInSrcTorrent + " in src torrent"); int firstSrcPieceNum = 0; long currentPieceStartPos = 0; int firstSrcPieceOffset = 0; for (int k = 0; k < srcPieces.length; k++) { long nextPos = currentPieceStartPos + srcPieces[k].getLength(); if (nextPos > dstPieceStartPosInSrcTorrent) { // ok, the current piece contains the byte // get the offset (the number of bytes from the // piece start pos in the firstSrcPieceOffset = (int) (dstPieceStartPosInSrcTorrent - currentPieceStartPos); break; } firstSrcPieceNum++; currentPieceStartPos = nextPos; } logger.finest("first src piece include dst piece: " + firstSrcPieceNum); logger.finest("first src piece offset: " + firstSrcPieceOffset); // great, next step, figure out if we need to add more // pieces int dstPieceLength = dstPieces[pieceNum].getLength(); ArrayList<Integer> piecesForVirtualPiece = new ArrayList<Integer>(); piecesForVirtualPiece.add(firstSrcPieceNum); long bytesCovered = srcPieces[firstSrcPieceNum].getLength() - firstSrcPieceOffset; int nextPieceNum = firstSrcPieceNum + 1; while (bytesCovered < dstPieceLength) { // deal with last piece issues if (nextPieceNum >= srcPieces.length) { logger.finest("reached last piece, covered=" + bytesCovered + " needed=" + dstPieceLength + ", skipping"); continue file_loop; } piecesForVirtualPiece.add(new Integer(nextPieceNum)); bytesCovered += srcPieces[nextPieceNum].getLength(); nextPieceNum++; } // excellent! create the piece int[] pieceMapping = new int[piecesForVirtualPiece.size()]; for (int k = 0; k < pieceMapping.length; k++) { pieceMapping[k] = piecesForVirtualPiece.get(k).intValue(); } pieces[pieceNum] = new VirtualPiece(pieceNum, pieceMapping, firstSrcPieceOffset); logger.finest("created virtual piece: " + pieces[pieceNum].toString()); } } } } return pieces; } public BitFlags getAvailable() throws PieceTranslationExcetion { boolean[] available = new boolean[virtualPieces.length]; for (int i = 0; i < virtualPieces.length; i++) { available[i] = isPieceCompleted(i); } BitFlags a = new BitFlags(available); return a; } private boolean isPieceCompleted(int pieceNumber) throws PieceTranslationExcetion { if (virtualPieces[pieceNumber] != null) { return virtualPieces[pieceNumber].isDone(); } return false; } private void printTorrentInfo() { logger.finer("source torrent:"); printTorrentInfo(srcDownloadManager.getTorrent()); logger.finer("destination torrent:"); printTorrentInfo(dstDownloadManager.getTorrent()); } public void request(DiskManagerReadRequest request, DiskManagerReadRequestListener listener) { try { logger.finest("got read request: piece=" + request.getPieceNumber() + " offset=" + request.getOffset() + " length=" + request.getLength()); int pieceNumber = request.getPieceNumber(); if (pieceNumber < 0 || pieceNumber >= virtualPieces.length) { listener.readFailed(request, new Exception("invalid piece number")); } VirtualPiece piece = virtualPieces[pieceNumber]; if (!piece.isDone()) { listener.readFailed(request, new Exception( "requested virtual piece not completed")); } piece.enqueueReadRequest(request, listener); } catch (Throwable t) { listener.readFailed(request, t); t.printStackTrace(); } } private static long getFileStartPosInTorrent(TOTorrentFile[] files, int index) { long fileStartPosInTorrent = 0; for (int i = 0; i < index; i++) { fileStartPosInTorrent += files[i].getLength(); } return fileStartPosInTorrent; } // @SuppressWarnings("unchecked") // private static HashWrapper getSha1FromTorrent(TOTorrent t, Map torrentMap, int fileIndex) throws TOTorrentException { // // if (t.isSimpleTorrent()) { // return getSha1FromTorrentMap(torrentMap); // } else { // Object filePropertiesObj = torrentMap.get("files"); // if (filePropertiesObj == null || !(filePropertiesObj instanceof List)) { // throw new TOTorrentException("no files map in non-simple torrent!", TOTorrentException.RT_UNSUPPORTED_ENCODING); // } // // List fileProperties = (List) filePropertiesObj; // if (fileProperties.size() <= fileIndex) { // throw new TOTorrentException("invalid index!", TOTorrentException.RT_UNSUPPORTED_ENCODING); // } // Object pObj = fileProperties.get(fileIndex); // if (!(pObj instanceof Map)) { // throw new TOTorrentException("invalid file properties map", TOTorrentException.RT_UNSUPPORTED_ENCODING); // } // Map pMap = (Map) pObj; // return getSha1FromTorrentMap(pMap); // } // } // // @SuppressWarnings("unchecked") // private static HashWrapper getSha1FromTorrentMap(Map torrentMap) throws TOTorrentException { // if (torrentMap.containsKey(FileListFile.KEY_SHA1_HASH)) { // byte[] hash = (byte[]) torrentMap.get(FileListFile.KEY_SHA1_HASH); // return new HashWrapper(hash); // } // throw new TOTorrentException("no sha1 in map", TOTorrentException.RT_UNSUPPORTED_ENCODING); // } private static void printTorrentInfo(TOTorrent torrent) { logger.finer(new String(torrent.getName())); logger.finer("length: " + torrent.getSize()); logger.finer("pieces: " + torrent.getNumberOfPieces()); logger.finer("piece length: " + torrent.getPieceLength()); logger.finer("files: " + torrent.getFiles().length); TOTorrentFile[] files = torrent.getFiles(); for (int i = 0; i < files.length; i++) { logger.finer(getFileStartPosInTorrent(files, i) + ": " + files[i].getRelativePath()); } } class PieceTranslationExcetion extends Exception { private static final long serialVersionUID = 1L; public PieceTranslationExcetion(String message) { super(message); } public PieceTranslationExcetion(String message, Throwable cause) { super(message, cause); } } private class VirtualPiece { private final int firstPieceOffset; private final int[] pieceMapping; private final int pieceNumber; public VirtualPiece(int pieceNumber, int[] pieceMapping, int firstPieceOffset) throws PieceTranslationExcetion { this.pieceMapping = pieceMapping; this.firstPieceOffset = firstPieceOffset; this.pieceNumber = pieceNumber; } public void enqueueReadRequest( final DiskManagerReadRequest originalRequest, final DiskManagerReadRequestListener originalListener) throws PieceTranslationExcetion { int requestOffset = originalRequest.getOffset(); // find the piece and position in which this request starts in the // source torrent int requestStartPiece = 0; int numBytesIntoDstPieceThatSrcPieceEnds = getPiece(0).getLength() - firstPieceOffset; while (numBytesIntoDstPieceThatSrcPieceEnds < requestOffset) { requestStartPiece++; numBytesIntoDstPieceThatSrcPieceEnds += getPiece(requestStartPiece).getLength(); } int overshoot = numBytesIntoDstPieceThatSrcPieceEnds - requestOffset; int requestStartPos = getPiece(requestStartPiece).getLength() - overshoot; int firstPiece = getPiece(requestStartPiece).getPieceNumber(); logger.finest("converted to: piece=" + firstPiece + " offset=" + requestStartPos); // we can only read up to the piece boundary which means that we // might have to queue up some requests ArrayList<DiskManagerReadRequest> requests = new ArrayList<DiskManagerReadRequest>(); int requestedSoFar = 0; int currentPiece = firstPiece; int pieceOffSet = requestStartPos; DiskManagerPiece[] srcPieces = srcDownloadManager.getDiskManager().getPieces(); while (requestedSoFar < originalRequest.getLength()) { int remaining = originalRequest.getLength() - requestedSoFar; int bytesLeftInPiece = srcPieces[currentPiece].getLength() - pieceOffSet; int requestedBytes = Math.min(remaining, bytesLeftInPiece); if (requestedBytes > 0) { logger.finest("requesting partial: piece=" + currentPiece + " offset=" + pieceOffSet + " length=" + requestedBytes); DiskManagerReadRequest request = sourceManager.createDiskManagerRequest( currentPiece, pieceOffSet, requestedBytes); requests.add(request); } else { // no bytes left in piece, jump to the next one } // prepare for the next iteration requestedSoFar += requestedBytes; currentPiece++; pieceOffSet = 0; } // ok, now wait for the requests to return final DirectByteBuffer[] responses = new DirectByteBuffer[requests.size()]; for (int i = 0; i < requests.size(); i++) { final int index = i; DiskManagerReadRequest request = requests.get(index); sourceManager.getAdapter().enqueueReadRequest(virtualPeer, request, new DiskManagerReadRequestListener() { public int getPriority() { return originalListener.getPriority(); } private boolean isCompleted() { for (DirectByteBuffer d : responses) { if (d == null) { return false; } } return true; } public void readCompleted(DiskManagerReadRequest _request, DirectByteBuffer data) { logger.finest("partial request completed: piece=" + _request.getPieceNumber() + " offset=" + _request.getOffset() + " length=" + data.remaining(DirectByteBuffer.SS_FILE)); responses[index] = data; if (isCompleted()) { DirectByteBuffer target = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_FILE, originalRequest.getLength()); putInBuffer(responses, 0, responses.length, target, true); target.flip(DirectByteBuffer.SS_FILE); logger.finest("original request completed: piece=" + originalRequest.getPieceNumber() + " offset=" + originalRequest.getOffset() + " length=" + target.remaining(DirectByteBuffer.SS_FILE)); originalListener.readCompleted(originalRequest, target); } } public void readFailed(DiskManagerReadRequest request, Throwable cause) { logger.finest("request failed: piece=" + request.getPieceNumber() + " offset=" + request.getOffset() + " length=" + request.getLength() + ": " + cause.getMessage()); originalListener.readFailed(originalRequest, cause); } public void requestExecuted(long bytes) { originalListener.requestExecuted(bytes); } }); } } private int getFirstPieceNum() throws PieceTranslationExcetion { return getPiece(0).getPieceNumber(); } private int getLastPieceNum() throws PieceTranslationExcetion { return getPiece(pieceMapping.length - 1).getPieceNumber(); } private DiskManagerPiece getPiece(int virtualIndex) throws PieceTranslationExcetion { if (virtualIndex > pieceMapping.length || virtualIndex < 0) { throw new PieceTranslationExcetion("invalid virtual index: " + virtualIndex + " len=" + pieceMapping.length); } DiskManager diskMan = srcDownloadManager.getDiskManager(); if (diskMan == null) { throw new PieceTranslationExcetion("disk manager is null!"); } DiskManagerPiece[] dmPieces = diskMan.getPieces(); if (dmPieces == null) { throw new PieceTranslationExcetion("pieces[] is null"); } int realIndex = pieceMapping[virtualIndex]; if (realIndex >= dmPieces.length || realIndex < 0) { throw new PieceTranslationExcetion("invalid piece index: " + realIndex + " len=" + dmPieces.length); } return dmPieces[realIndex]; } public boolean isDone() throws PieceTranslationExcetion { for (int i = 0; i < pieceMapping.length; i++) { if (!getPiece(i).isDone()) { return false; } } return true; } private int putInBuffer(DirectByteBuffer sources[], int array_offset, int length, DirectByteBuffer target, boolean returnSourceToPool) { int copied = 0; ByteBuffer t = target.getBuffer(DirectByteBuffer.SS_FILE); for (int i = array_offset; i < array_offset + length; i++) { ByteBuffer source = sources[i].getBuffer(DirectByteBuffer.SS_FILE); if (t.remaining() == 0) { break; } if (source.remaining() == 0) { continue; } int numBytesToCopy = Math.min(t.remaining(), source.remaining()); if (t.remaining() < source.remaining()) { // we need to set the limit to avoid buffer overflow int oldLimit = source.limit(); source.limit(source.position() + t.remaining()); t.put(source); source.limit(oldLimit); } else { t.put(source); } copied += numBytesToCopy; sources[i].returnToPool(); } return copied; } public String toString() { try { return "VirtualPiece " + pieceNumber + ": src[" + getFirstPieceNum() + "," + getLastPieceNum() + "] offset=" + firstPieceOffset + " done=" + isDone(); } catch (PieceTranslationExcetion e) { return "VirtualPiece " + pieceNumber; } } } }