/*
* 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 (time_last_download >0 &&now >=time_last_download)
return now -time_last_download;
if (creationTime >0 &&now >=creationTime)
return now -creationTime;
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, boolean enable_request_hints )
{
final String ip =peer.getIp();
final boolean[] written =dmPiece.getWritten();
int blocksFound =0;
if ( enable_request_hints ){
int[] request_hint = peer.getRequestHint();
if ( request_hint != null && request_hint[0] == dmPiece.getPieceNumber()){
// try to honour the hint first
int hint_block_start = request_hint[1] / DiskManager.BLOCK_SIZE;
int hint_block_count = ( request_hint[2] + DiskManager.BLOCK_SIZE-1 ) / DiskManager.BLOCK_SIZE;
for (int i =hint_block_start; i < nbBlocks && i <hint_block_start + hint_block_count; i++)
{
while (blocksFound <nbWanted &&(i +blocksFound) <nbBlocks &&!downloaded[i +blocksFound]
&&requested[i +blocksFound] ==null &&(written ==null ||!written[i]))
{
requested[i +blocksFound] =ip;
blocksFound++;
}
if (blocksFound >0){
return new int[] {i, blocksFound};
}
}
}
}
// scan piece to find first free block
for (int i =0; i <nbBlocks; i++)
{
while (blocksFound <nbWanted &&(i +blocksFound) <nbBlocks &&!downloaded[i +blocksFound]
&&requested[i +blocksFound] ==null &&(written ==null ||!written[i]))
{
requested[i +blocksFound] =ip;
blocksFound++;
}
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 );
}
} |