/* * bitlet - Simple bittorrent library * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.bitlet.wetorrent.disk; import java.io.EOFException; import java.io.IOException; import java.io.RandomAccessFile; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.bitlet.wetorrent.util.Utils; public class Piece { private byte[] sha1; private int length; private List<FilePieceMapper> files = new ArrayList<FilePieceMapper>(); private class PieceBlock { public PieceBlock(Integer begin, Integer lenght) { this.begin = begin; this.length = lenght; } public Integer begin; public Integer length; public String toString() { return "b:" + begin + " l:" + length; } } private Set<PieceBlock> blocks = new TreeSet<PieceBlock>(new Comparator<PieceBlock>() { public int compare(Piece.PieceBlock o1, Piece.PieceBlock o2) { int beginDiff = o1.begin - o2.begin; if (beginDiff != 0) { return beginDiff; } return o1.length - o2.length; } }); /** * Creates a new instance of Piece */ public Piece(byte[] sha1) { this.sha1 = sha1; } public void addFilePointer(FilePieceMapper filePointer) { files.add(filePointer); } public int getCompleted() { Integer completed = 0; for (PieceBlock block : blocks) { completed += block.length; } return completed; } public void setLength(int length) { this.length = length; } public int getLength() { return length; } public boolean isCompleted() { return getCompleted() == length; } public void write(int begin, byte[] block) throws IOException { int filePieceIndex = findFilePieceIndex(begin); FilePieceMapper filePiece = files.get(filePieceIndex); int writtenBytes = 0; while (writtenBytes < block.length) { RandomAccessFile raf = filePiece.getFile(); Long seek = filePiece.getFileOffset() + ((begin + writtenBytes) - filePiece.getPieceOffset()); raf.seek(seek); int byteToWrite = block.length - writtenBytes; Long byteAvaiableInThisFile = raf.length() - seek; Long byteAvaiableToWrite = byteToWrite < byteAvaiableInThisFile ? byteToWrite : byteAvaiableInThisFile; raf.write(block, writtenBytes, byteAvaiableToWrite.intValue()); writtenBytes += byteAvaiableToWrite.intValue(); if (byteAvaiableToWrite.equals(byteAvaiableInThisFile) && writtenBytes < block.length) { filePiece = files.get(++filePieceIndex); } } addPieceBlock(begin, block.length); if (isCompleted()) { if (!checkSha1()) { blocks.clear(); throw new IOException("sha check failed"); } } } public byte[] read(int begin, int length) throws IOException { if (!isAvaiable(begin, length)) { throw new EOFException("Data not available " + "begin: " + begin + " length: " + length); } int filePieceIndex = findFilePieceIndex(begin); FilePieceMapper filePiece = files.get(filePieceIndex); byte[] block = new byte[length]; int readBytes = 0; while (readBytes < length) { RandomAccessFile raf = filePiece.getFile(); Long seek = filePiece.getFileOffset() + ((begin + readBytes) - filePiece.getPieceOffset()); raf.seek(seek); int byteToRead = length - readBytes; Long byteAvaiableInThisFile = raf.length() - seek; Long byteAvaiableToRead = byteToRead < byteAvaiableInThisFile ? byteToRead : byteAvaiableInThisFile; raf.readFully(block, readBytes, byteAvaiableToRead.intValue()); readBytes += byteAvaiableToRead.intValue(); if (byteAvaiableToRead.equals(byteAvaiableInThisFile) && readBytes < length) { filePiece = files.get(++filePieceIndex); } } return block; } /* TODO: Optimize with bisection */ private int findFilePieceIndex(int begin) { int i = 0; for (i = 0; i < files.size() - 1; i++) { if (files.get(i).getPieceOffset() <= begin && files.get(i + 1).getPieceOffset() > begin) { return i; } } return i; } public void addPieceBlock(int begin, int length) { PieceBlock newPieceBlock = new PieceBlock(begin, length); /*The pieceBlocks are sorted using begin as key*/ blocks.add(newPieceBlock); Iterator<PieceBlock> iterator = blocks.iterator(); PieceBlock prev = iterator.next(); Collection<PieceBlock> blocksToBeRemoved = new LinkedList<PieceBlock>(); while (iterator.hasNext()) { PieceBlock p = iterator.next(); if (prev.begin + prev.length >= p.begin) { p.length = Math.max(p.length + (p.begin - prev.begin), prev.length); p.begin = prev.begin; blocksToBeRemoved.add(prev); } prev = p; } for (PieceBlock pb : blocksToBeRemoved) { blocks.remove(pb); } } public boolean isAvaiable(int begin, int length) { for (PieceBlock block : blocks) { if (begin >= block.begin && length <= block.length) { return true; } else if (begin + length < block.begin) { return false; } } return false; } public int getFirstMissingByte() { if (blocks.size() > 0) { PieceBlock firstBlock = blocks.iterator().next(); if (firstBlock.begin == 0) { return firstBlock.length; } else { return 0; } } else { return 0; } } public boolean checkSha1() throws IOException { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA1"); } catch (NoSuchAlgorithmException ex) { ex.printStackTrace(); } byte[] pieceBuffer = read(0, length); byte[] sha1Digest = md.digest(pieceBuffer); return Utils.bytesCompare(sha1, sha1Digest); } public void clear() { blocks.clear(); } public int available(int begin) { for (PieceBlock pb : blocks) { if (pb.begin <= begin && (pb.begin + pb.length) > begin) { return (pb.begin + pb.length) - begin; } } return 0; } }