/* * File : PEPieceImpl.java * Created : 15-Oct-2003 * By : Olivier * * Azureus - a Java Bittorrent client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.core3.peer.impl; /** * @author parg * @author MjrTom * 2005/Oct/08: numerous changes for new piece-picking * 2006/Jan/02: refactoring piece picking to elsewhere, and consolidations */ import java.util.*; import org.gudy.azureus2.core3.disk.*; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.peer.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.peermanager.piecepicker.PiecePicker; public class PEPieceImpl implements PEPiece { private static final LogIDs LOGID = LogIDs.PIECES; private final DiskManagerPiece dmPiece; private final PEPeerManager manager; private final int nbBlocks; // number of blocks in this piece private long creationTime; private final String[] requested; private boolean fully_requested; private final boolean[] downloaded; private boolean fully_downloaded; private long time_last_download; private final String[] writers; private List writes; private String reservedBy; // using address for when they send bad/disconnect/reconnect //In end game mode, this limitation isn't used private int speed; //slower peers dont slow down fast pieces too much private int resumePriority; private Object real_time_data; // experimental class level lock protected static final AEMonitor class_mon = new AEMonitor( "PEPiece:class"); /** piece for tracking partially downloaded pieces * @param _manager the PEPeerManager * @param _dm_piece the backing dmPiece * @param _pieceSpeed the speed threshold for potential new requesters */ public PEPieceImpl( PEPeerManager _manager, DiskManagerPiece _dm_piece, int _pieceSpeed) { creationTime =SystemTime.getCurrentTime(); manager =_manager; dmPiece =_dm_piece; speed =_pieceSpeed; nbBlocks =dmPiece.getNbBlocks(); requested =new String[nbBlocks]; final boolean[] written =dmPiece.getWritten(); if (written ==null) downloaded =new boolean[nbBlocks]; else downloaded =(boolean[])written.clone(); writers =new String[nbBlocks]; writes =new ArrayList(0); } public DiskManagerPiece getDMPiece() { return dmPiece; } public long getCreationTime() { final long now =SystemTime.getCurrentTime(); if (now >=creationTime &&creationTime >0){ return creationTime; } creationTime =now; return now; } public long getTimeSinceLastActivity() { final long now =SystemTime.getCurrentTime(); final long lastWriteTime =getLastDownloadTime(now); if (lastWriteTime >0){ return now -lastWriteTime; } final long lastCreateTime = creationTime; if (lastCreateTime > 0 && now >=lastCreateTime ){ return now -lastCreateTime; } creationTime = now; return 0; } public long getLastDownloadTime(final long now) { if (time_last_download <=now) return time_last_download; return time_last_download =now; } /** Tells if a block has been requested * @param blockNumber the block in question * @return true if the block is Requested already */ public boolean isRequested(int blockNumber) { return requested[blockNumber] !=null; } /** Tells if a block has been downloaded * @param blockNumber the block in question * @return true if the block is downloaded already */ public boolean isDownloaded(int blockNumber) { return downloaded[blockNumber]; } /** This flags the block at the given offset as having been downloaded * If all blocks are now downloaed, sets the dmPiece as downloaded * @param blockNumber */ public void setDownloaded(int offset) { time_last_download =SystemTime.getCurrentTime(); downloaded[offset /DiskManager.BLOCK_SIZE] =true; for (int i =0; i <nbBlocks; i++) { if (!downloaded[i]) return; } fully_downloaded = true; fully_requested = false; } /** This flags the block at the given offset as NOT having been downloaded * and the whole piece as not having been fully downloaded * @param blockNumber */ public void clearDownloaded(int offset) { downloaded[offset /DiskManager.BLOCK_SIZE] =false; fully_downloaded = false; } public boolean isDownloaded() { return( fully_downloaded ); } public boolean[] getDownloaded() { return( downloaded ); } public boolean hasUndownloadedBlock() { for (int i =0; i <nbBlocks; i++ ){ if (!downloaded[i]){ return( true ); } } return( false ); } /** This marks a given block as having been written by the given peer * @param peer the PEPeer that sent the data * @param blockNumber the block we're operating on */ public void setWritten(PEPeer peer, int blockNumber) { writers[blockNumber] =peer.getIp(); dmPiece.setWritten(blockNumber); } /** This method clears the requested information for the given block * unless the block has already been downloaded, in which case the writer's * IP is recorded as a request for the block. */ public void clearRequested(int blockNumber) { requested[blockNumber] =downloaded[blockNumber] ?writers[blockNumber] :null; fully_requested = false; } public boolean isRequested() { return( fully_requested ); } public void setRequested() { fully_requested = true; } /** This will scan each block looking for requested blocks. For each one, it'll verify * if the PEPeer for it still exists and is still willing and able to upload data. * If not, it'll unmark the block as requested. * @return int of how many were cleared (0 to nbBlocks) */ /* public int checkRequests() { if (getTimeSinceLastActivity() <30 *1000) return 0; int cleared =0; boolean nullPeer =false; for (int i =0; i <nbBlocks; i++) { if (!downloaded[i] &&!dmPiece.isWritten(i)) { final String requester =requested[i]; final PEPeerTransport pt; if (requester !=null) { pt =manager.getTransportFromAddress(requester); if (pt !=null) { pt.setSnubbed(true); if (!pt.isDownloadPossible()) { clearRequested(i); cleared++; } } else { nullPeer =true; clearRequested(i); cleared++; } } } } if (cleared >0) { dmPiece.clearRequested(); if (Logger.isEnabled()) Logger.log(new LogEvent(dmPiece.getManager().getTorrent(), LOGID, LogEvent.LT_WARNING, "checkRequests(): piece #" +getPieceNumber()+" cleared " +cleared +" requests." + (nullPeer ?" Null peer was detected." :""))); } return cleared; } */ /* * Parg: replaced above commented out checking with one that verifies that the * requests still exist. As piece-picker activity and peer disconnect logic is multi-threaded * and full of holes, this is a stop-gap measure to prevent a piece from being left with * requests that no longer exist */ public void checkRequests() { if ( getTimeSinceLastActivity() < 30*1000 ){ return; } int cleared = 0; for (int i=0; i<nbBlocks; i++){ if (!downloaded[i] &&!dmPiece.isWritten(i)){ final String requester = requested[i]; if ( requester != null ){ if ( !manager.requestExists( requester, getPieceNumber(), i *DiskManager.BLOCK_SIZE, getBlockSize( i ))){ clearRequested(i); cleared++; } } } } if ( cleared > 0 ){ if (Logger.isEnabled()) Logger.log(new LogEvent(dmPiece.getManager().getTorrent(), LOGID, LogEvent.LT_WARNING, "checkRequests(): piece #" +getPieceNumber()+" cleared " +cleared +" requests" )); }else{ if ( fully_requested && getNbUnrequested() > 0 ){ if (Logger.isEnabled()) Logger.log(new LogEvent(dmPiece.getManager().getTorrent(), LOGID, LogEvent.LT_WARNING, "checkRequests(): piece #" +getPieceNumber()+" reset fully requested" )); fully_requested = false; } } } /** @return true if the piece has any blocks that are not; * Downloaded, Requested, or Written */ public boolean hasUnrequestedBlock() { final boolean[] written =dmPiece.getWritten(); for (int i =0; i <nbBlocks; i++ ) { if (!downloaded[i] &&requested[i] ==null &&(written ==null ||!written[i])) return true; } return false; } /** * This method scans a piece for the first unrequested block. Upon finding it, * it counts how many are unrequested up to nbWanted. * The blocks are marked as requested by the PEPeer * Assumption - single threaded access to this * TODO: this should return the largest span equal or smaller than nbWanted * OR, probably a different method should do that, so this one can support 'more sequential' picking */ public int[] getAndMarkBlocks( PEPeer peer, int nbWanted, int[] request_hint, boolean reverse_order ) { final String ip = peer.getIp(); final boolean[] written = dmPiece.getWritten(); if ( request_hint != null ){ // try to honour the hint first int hint_block_start = request_hint[1] / DiskManager.BLOCK_SIZE; int hint_block_end = ( request_hint[1] + request_hint[2] -1 )/ DiskManager.BLOCK_SIZE; if ( reverse_order ){ for ( int i = Math.min( nbBlocks-1, hint_block_end ); i >= hint_block_start; i--){ int blocksFound = 0; int block_index = i; while ( blocksFound < nbWanted && block_index < nbBlocks && !downloaded[ block_index ] && requested[block_index] == null && ( written == null || !written[block_index] )){ requested[ block_index ] = ip; blocksFound++; block_index--; } if ( blocksFound > 0 ){ return new int[] {block_index+1, blocksFound}; } } }else{ for (int i = hint_block_start; i < nbBlocks && i <= hint_block_end; i++){ int blocksFound = 0; int block_index = i; while ( blocksFound < nbWanted && block_index < nbBlocks && !downloaded[ block_index ] && requested[block_index] == null && ( written == null || !written[block_index] )){ requested[ block_index ] = ip; blocksFound++; block_index++; } if ( blocksFound > 0 ){ return new int[] {i, blocksFound}; } } } } // scan piece to find first free block if ( reverse_order ){ for (int i=nbBlocks-1; i >= 0; i-- ){ int blocksFound = 0; int block_index = i; while ( blocksFound < nbWanted && block_index >= 0 && !downloaded[block_index] && requested[block_index] == null && ( written == null || !written[block_index] )){ requested[block_index] = ip; blocksFound++; block_index--; } if ( blocksFound > 0 ){ return new int[] {block_index+1, blocksFound}; } } }else{ for (int i =0; i <nbBlocks; i++){ int blocksFound = 0; int block_index = i; while ( blocksFound < nbWanted && block_index < nbBlocks && !downloaded[ block_index ] && requested[ block_index ] == null && ( written == null || !written[block_index] )){ requested[block_index] = ip; blocksFound++; block_index++; } if ( blocksFound > 0 ){ return new int[] {i, blocksFound}; } } } return new int[] {-1, 0}; } public void getAndMarkBlock(PEPeer peer, int index ) { requested[index] = peer.getIp(); if ( getNbUnrequested() <= 0 ){ setRequested(); } } public int getNbRequests() { int result =0; for (int i =0; i <nbBlocks; i++) { if (!downloaded[i] &&requested[i] !=null) result++; } return result; } public int getNbUnrequested() { int result =0; final boolean[] written =dmPiece.getWritten(); for (int i =0; i <nbBlocks; i++ ) { if (!downloaded[i] &&requested[i] ==null &&(written ==null ||!written[i])) result++; } return result; } /** * Assumption - single threaded with getAndMarkBlock */ public boolean setRequested(PEPeer peer, int blockNumber) { if (!downloaded[blockNumber]) { requested[blockNumber] =peer.getIp(); return true; } return false; } public boolean isRequestable() { return( dmPiece.isDownloadable() && !( fully_downloaded || fully_requested )); } public int getBlockSize( int blockNumber) { if ( blockNumber == (nbBlocks - 1)){ int length = dmPiece.getLength(); if ((length % DiskManager.BLOCK_SIZE) != 0){ return( length % DiskManager.BLOCK_SIZE ); } } return DiskManager.BLOCK_SIZE; } public int getBlockNumber(int offset) { return offset /DiskManager.BLOCK_SIZE; } public int getNbBlocks() { return nbBlocks; } public List getPieceWrites() { List result; try{ class_mon.enter(); result = new ArrayList(writes); }finally{ class_mon.exit(); } return result; } public List getPieceWrites(int blockNumber) { final List result; try{ class_mon.enter(); result = new ArrayList(writes); }finally{ class_mon.exit(); } final Iterator iter = result.iterator(); while(iter.hasNext()) { final PEPieceWriteImpl write = (PEPieceWriteImpl) iter.next(); if(write.getBlockNumber() != blockNumber) iter.remove(); } return result; } public List getPieceWrites(PEPeer peer) { final List result; try{ class_mon.enter(); result = new ArrayList(writes); }finally{ class_mon.exit(); } final Iterator iter = result.iterator(); while(iter.hasNext()) { PEPieceWriteImpl write = (PEPieceWriteImpl) iter.next(); if(peer == null || ! peer.getIp().equals(write.getSender())) iter.remove(); } return result; } public List getPieceWrites( String ip ) { final List result; try{ class_mon.enter(); result = new ArrayList(writes); }finally{ class_mon.exit(); } final Iterator iter = result.iterator(); while(iter.hasNext()) { final PEPieceWriteImpl write = (PEPieceWriteImpl) iter.next(); if ( !write.getSender().equals( ip )){ iter.remove(); } } return result; } public void reset() { dmPiece.reset(); for (int i =0; i <nbBlocks; i++) { requested[i] =null; downloaded[i] =false; writers[i] =null; } fully_downloaded = false; time_last_download = 0; reservedBy =null; real_time_data=null; } public Object getRealTimeData() { return( real_time_data ); } public void setRealTimeData( Object o ) { real_time_data = o; } protected void addWrite(PEPieceWriteImpl write) { try{ class_mon.enter(); writes.add(write); }finally{ class_mon.exit(); } } public void addWrite( int blockNumber, String sender, byte[] hash, boolean correct ) { addWrite( new PEPieceWriteImpl( blockNumber, sender, hash, correct )); } public String[] getWriters() { return writers; } public int getSpeed() { return speed; } public void setSpeed(int newSpeed) { speed =newSpeed; } public void setLastRequestedPeerSpeed( int peerSpeed ) { // Up the speed on this piece? if (peerSpeed > speed ){ speed++; } } /** * @return Returns the manager. */ public PEPeerManager getManager() { return manager; } public void setReservedBy(String peer) { reservedBy =peer; } public String getReservedBy() { return reservedBy; } /** for a block that's already downloadedt, mark up the piece * so that the block will get downloaded again. This is used * when the piece fails hash-checking. */ public void reDownloadBlock(int blockNumber) { downloaded[blockNumber] =false; requested[blockNumber] =null; fully_downloaded = false; writers[blockNumber] = null; dmPiece.reDownloadBlock(blockNumber); } /** finds all blocks downloaded by the given address * and marks them up for re-downloading * @param address String */ public void reDownloadBlocks(String address) { for (int i =0; i <writers.length; i++ ) { final String writer =writers[i]; if (writer !=null &&writer.equals(address)) reDownloadBlock(i); } } public void setResumePriority(int p) { resumePriority =p; } public int getResumePriority() { return resumePriority; } /** * @return int of availability in the swarm for this piece * @see org.gudy.azureus2.core3.peer.PEPeerManager.getAvailability(int pieceNumber) */ public int getAvailability() { return manager.getAvailability(dmPiece.getPieceNumber()); } /** This support method returns how many blocks have already been * written from the dmPiece * @return int from dmPiece.getNbWritten() * @see org.gudy.azureus2.core3.disk.DiskManagerPiece.getNbWritten() */ public int getNbWritten() { return dmPiece.getNbWritten(); } /** This support method returns the dmPiece's written array * @return boolean[] from the dmPiece * @see org.gudy.azureus2.core3.disk.DiskManagerPiece.getWritten() */ public boolean[] getWritten() { return dmPiece.getWritten(); } public boolean isWritten() { return dmPiece.isWritten(); } public boolean isWritten( int block) { return dmPiece.isWritten( block ); } public int getPieceNumber() { return dmPiece.getPieceNumber(); } public int getLength() { return dmPiece.getLength(); } public void setRequestable() { fully_downloaded = false; fully_requested = false; dmPiece.setDownloadable(); } public String getString() { String text = ""; PiecePicker pp = manager.getPiecePicker(); text += ( isRequestable()?"reqable,":"" ); text += "req=" + getNbRequests() + ","; text += ( isRequested()?"reqstd,":"" ); text += ( isDownloaded()?"downed,":"" ); text += ( getReservedBy()!=null?"resrv,":"" ); text += "speed=" + getSpeed() + ","; text += ( pp==null?("pri=" + getResumePriority()):pp.getPieceString(dmPiece.getPieceNumber())); if ( text.endsWith(",")){ text = text.substring(0,text.length()-1); } return( text ); } }