SWDownloadCandidate.java
| Index Score | ||
|---|---|---|
![]() |
![]() |
phex.download.swarming |
![]() |
![]() |
Phex |
View: Reasons, Metrics, Source Code
These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.
/*
* PHEX - The pure-java Gnutella-servent.
* Copyright (C) 2001 - 2008 Phex Development Group
*
* 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, or
* (at your option) any later version.
*
* 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.
*
* 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
*
* --- SVN Information ---
* $Id: SWDownloadCandidate.java 4231 2008-07-15 16:01:10Z gregork $
*/
package phex.download.swarming;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import phex.common.AlternateLocation;
import phex.common.URN;
import phex.common.address.DefaultDestAddress;
import phex.common.address.DestAddress;
import phex.common.address.MalformedDestAddressException;
import phex.common.log.LogBuffer;
import phex.common.log.LogRecord;
import phex.common.log.NLogger;
import phex.download.DownloadScope;
import phex.download.DownloadScopeList;
import phex.download.RemoteFile;
import phex.event.ChangeEvent;
import phex.event.PhexEventTopics;
import phex.http.HTTPRangeSet;
import phex.http.Range;
import phex.http.XQueueParameters;
import phex.msg.GUID;
import phex.net.repres.PresentationManager;
import phex.prefs.core.DownloadPrefs;
import phex.query.QueryHitHost;
import phex.servent.Servent;
import phex.utils.URLCodecUtils;
import phex.utils.URLUtil;
import phex.xml.XMLUtils;
import phex.xml.sax.downloads.DDownloadCandidate;
/**
* A representation of a download candidate. A download candidate contains all
* information required for a given endpoint that can offer us data for download.
* <p>
*
*/
public class SWDownloadCandidate implements SWDownloadConstants
{
/**
* The value of the candidate status constants are used for sorting
* therefore status values should be kept in a reasonable order.
*/
public enum CandidateStatus
{
// Indicating the candidate is ignored from further processing
IGNORED,
// Indicating the candidate is ignored from further processing
BAD,
// Indicating the candidate is waiting to be processed.
WAITING,
// Indicating the last connecting try to the candidate failed.
CONNECTION_FAILED,
// Indicating the candidate is busy.
BUSY,
// Indicating the last requested range of the candidate was unavailable.
RANGE_UNAVAILABLE,
// Indicating the candidate is connecting
CONNECTING,
// Indicating the candidate is connecting with a push request.
PUSH_REQUEST,
// Indicating the candidate is remotely queued.
REMOTLY_QUEUED,
// Indicating the candidate is requesting a segment
REQUESTING,
// Indicating the candidate is downloading
DOWNLOADING
}
public enum ThexStatus
{
// Indicating the candidate has not yet succeeded or failed to perform
// a thex request.
OPEN,
// Indicating the candidate has succeeded to perform a thex request.
SUCCEDED,
// Indicating the candidate failed to perform a thex request.
FAILED
}
/**
* The download file that this candidate belongs to.
*/
private final SWDownloadFile downloadFile;
/**
* A {@link LogBuffer} to add candidate log messages.
* This can be null and also a shared instance across multiple
* candidate.
*/
private final LogBuffer candidateLogBuffer;
/**
* The time this download candidate was first added to the list of download
* candidates of the corresponding download file. This is the first creation
* time of the SWDownloadCandidate object.
*/
//private Date firstSeenDate;
/**
* The time a connection to this download candidate could be established
* successful.
*/
private long lastConnectionTime;
/**
* The number of failed connection tries to this download candidate, since
* the last successful connection.
*/
private int failedConnectionTries;
/**
* The GUID of the client used for push requests.
*/
private GUID guid;
/**
* The file index of the file at the download candidate.
* This is the old style identifier for candidates without
* urn.
*/
private long fileIndex;
/**
* The resource URN of the file. If known this will be used
* for download requests.
*/
private URN resourceURN;
/**
* A complete download URI to use. This is necessary for standard
* uri downloads like in http urls.
*/
private URI downloadURI;
/**
* The thex request status for this candidate.
*/
private ThexStatus thexStatus;
/**
* The thex uri string, expected without host (it's supposed to be intended
* for this candidate). The value is initialized during the download
* handshake.
*/
private String thexUri;
/**
* The thex root string. Initialized during the download handshake.
*/
private String thexRoot;
/**
* The name of the file at the download candidate.
*/
private String fileName;
/**
* Rate at which the last segment was transferred
*/
private int lastTransferRateBPS;
/**
* The host address of the download candidate.
*/
private DestAddress hostAddress;
/**
* The status of the candidate.
*/
private CandidateStatus status;
/**
* A possible status reason.
*/
private String statusReason;
/**
* The last error status of the download to track status changes.
*/
private CandidateStatus errorStatus;
/**
* The time after which the current status times out and things continue.
*/
private long statusTimeout;
/**
* Counts the number of times the status keeps repeating.
*/
private int errorStatusRepetition;
/**
* The vendor of the client running at the candidate.
*/
private String vendor;
private boolean isG2FeatureAdded;
/**
* Defines if a push is needed for this candidate.
*/
private boolean isPushNeeded;
/**
* The addresses of push proxies of this host or null
* if not available.
*/
private DestAddress[] pushProxyAddresses;
/**
* Defines if the candidate supports chat connections.
*/
private boolean isChatSupported;
/**
* The available range set of the candidate file.
*/
private DownloadScopeList availableScopeList;
/**
* The time the available range was last updated.
*/
private long availableRangeSetTime = 0;
/**
* The download segment that is currently associated by this download
* candidate or null if no association exists.
*/
private SWDownloadSegment downloadSegment;
/**
* The parameters that are available in case the candidate is remotely queuing
* our download request. This is referenced for information purpose only.
*/
private XQueueParameters xQueueParameters;
/**
* This is a Map holding all AltLocs already send to this candidate during
* this session. It is used to make sure the same AltLocs are not send twice
* to the same candidate. The list is lazy initialized on first access.
*/
private Set<AlternateLocation> sendAltLocSet;
/**
* The total amount of data downloaded from this candidate.
*/
private long totalDownloadSize;
private SWDownloadCandidate( SWDownloadFile downloadFile,
LogBuffer candidateLogBuffer )
{
if ( downloadFile == null )
{
throw new NullPointerException( "downloadFile is null." );
}
this.downloadFile = downloadFile;
// can be null
this.candidateLogBuffer = candidateLogBuffer;
availableScopeList = null;
lastTransferRateBPS = 0;
totalDownloadSize = 0;
lastConnectionTime = 0;
}
public SWDownloadCandidate( RemoteFile remoteFile,
SWDownloadFile downloadFile, LogBuffer candidateLogBuffer )
{
this( downloadFile, candidateLogBuffer );
fileIndex = remoteFile.getFileIndex();
fileName = remoteFile.getFilename();
resourceURN = remoteFile.getURN();
guid = remoteFile.getRemoteClientID();
QueryHitHost qhHost = remoteFile.getQueryHitHost();
vendor = qhHost.getVendor();
isPushNeeded = qhHost.isPushNeeded();
hostAddress = remoteFile.getHostAddress();
isChatSupported = qhHost.isChatSupported();
pushProxyAddresses = qhHost.getPushProxyAddresses();
status = CandidateStatus.WAITING;
thexStatus = ThexStatus.OPEN;
}
public SWDownloadCandidate( DestAddress aHostAddress, long aFileIndex,
String aFileName, URN aResourceURN, SWDownloadFile downloadFile,
LogBuffer candidateLogBuffer )
{
this( downloadFile, candidateLogBuffer );
fileIndex = aFileIndex;
fileName = aFileName;
resourceURN = aResourceURN;
guid = null;
vendor = null;
isPushNeeded = false;
// assume chat is supported but we dont know...
isChatSupported = true;
hostAddress = aHostAddress;
status = CandidateStatus.WAITING;
thexStatus = ThexStatus.OPEN;
/*setAvailableRangeSet(new HTTPRangeSet(0, downloadFile.getTotalDataSize())); */
}
public SWDownloadCandidate(DestAddress address, URI downloadUri,
SWDownloadFile downloadFile, LogBuffer candidateLogBuffer )
throws URIException
{
this( downloadFile, candidateLogBuffer );
fileName = URLUtil.getPathQueryFromUri( downloadUri );
this.downloadURI = downloadUri;
resourceURN = URLUtil.getQueryURN( downloadUri );
guid = null;
vendor = null;
isPushNeeded = false;
// assume chat is supported but we dont know...
isChatSupported = true;
hostAddress = address;
status = CandidateStatus.WAITING;
thexStatus = ThexStatus.OPEN;
}
public SWDownloadCandidate( DDownloadCandidate dCandidate,
SWDownloadFile downloadFile, LogBuffer candidateLogBuffer )
throws MalformedDestAddressException
{
this( downloadFile, candidateLogBuffer );
fileIndex = dCandidate.getFileIndex();
fileName = dCandidate.getFileName();
/* setAvailableRangeSet(new HTTPRangeSet(0, downloadFile.getTotalDataSize())); */
String guidHexStr = dCandidate.getGuid();
if ( guidHexStr != null )
{
guid = new GUID( guidHexStr );
}
String downloadUriStr = dCandidate.getDownloadUri();
if ( downloadUriStr != null )
{
try
{
downloadURI = new URI( downloadUriStr, true );
}
catch ( URIException exp )
{
NLogger.warn( SWDownloadCandidate.class,
"Malformed URI in: " + downloadFile.toString() +
" - " + downloadUriStr + " - " + this.toString(),
exp );
// continue anyway.. download candidate might still be useful
}
}
String resourceUrnStr = dCandidate.getResourceUrn();
if ( resourceUrnStr != null )
{
resourceURN = new URN( resourceUrnStr );
}
else
{
// No resource urn was found there could be two reasons, the
// candidate uses a download uri or the download file is from
// release 3.0.2 or earlier. If no download uri is available
// we use the resource urn of the download file to work around
// this shortcoming and support pre 3.0.2 download lists as
// good as possible. Basically this means we don't support
// file index downloads anymore.. but do we really need them
// nowadays..
if ( downloadURI == null )
{
resourceURN = downloadFile.getFileURN();
}
}
vendor = dCandidate.getVendor();
isPushNeeded = dCandidate.isPushNeeded();
isChatSupported = dCandidate.isChatSupported();
if ( dCandidate.isSetLastConnectionTime() )
{
lastConnectionTime = dCandidate.getLastConnectionTime();
}
else
{
lastConnectionTime = 0;
}
try
{
hostAddress = PresentationManager.getInstance().createHostAddress(
dCandidate.getRemoteHost(), DefaultDestAddress.DEFAULT_PORT );
}
catch ( MalformedDestAddressException exp )
{
NLogger.warn( SWDownloadCandidate.class,
"Malformed host address in: " + downloadFile.toString() +
" - " + dCandidate.getRemoteHost() + " - " + this.toString(), exp );
throw exp;
}
if ( dCandidate.getConnectionFailedRepetition() > 0 )
{
errorStatus = CandidateStatus.CONNECTION_FAILED;
status = CandidateStatus.CONNECTION_FAILED;
errorStatusRepetition = dCandidate.getConnectionFailedRepetition();
failedConnectionTries = errorStatusRepetition;
}
else
{
status = CandidateStatus.WAITING;
}
// thex status is reset on startup.
thexStatus = ThexStatus.OPEN;
}
/**
* Returns the url necessary for the download request.
* @return the download url.
*/
public String getDownloadRequestUrl()
{
String requestUrl;
if ( downloadURI != null )
{
try
{
// Don't use whole uri.. only file and query part..!?
requestUrl = URLUtil.getPathQueryFromUri( downloadURI );
return requestUrl;
}
catch (URIException e)
{// failed to use uri.. try other request urls..
NLogger.warn( SWDownloadCandidate.class, e, e );
}
}
if ( resourceURN != null )
{
requestUrl = URLUtil.buildName2ResourceURL( resourceURN );
}
else
{
// build standard old style gnutella request.
String fileIndexStr = String.valueOf( fileIndex );
StringBuffer urlBuffer = new StringBuffer( 6 + fileIndexStr.length()
+ fileName.length() );
urlBuffer.append( "/get/" );
urlBuffer.append( fileIndexStr );
urlBuffer.append( '/' );
urlBuffer.append( URLCodecUtils.encodeURL( fileName ) );
requestUrl = urlBuffer.toString();
}
return requestUrl;
}
public SWDownloadFile getDownloadFile()
{
return downloadFile;
}
/**
* Returns the average transfer speed of the last transfer,
* or 0 if no transfer has finished yet.
* @return the average transfer speed of the last transfer,
* or 0 if no transfer has finished yet
*/
public long getSpeed()
{
return lastTransferRateBPS;
}
/**
* Returns the HostAddress of the download candidate
*/
public DestAddress getHostAddress()
{
return hostAddress;
}
/**
* Returns the name of the file at the download candidate.
*/
public String getFileName()
{
return fileName;
}
/**
* Returns the resource URN of the file at the download candidate.
*/
public URN getResourceURN()
{
return resourceURN;
}
/**
* Returns the GUID of the candidate for push requests.
*/
public GUID getGUID()
{
return guid;
}
/**
* Returns the file index of the file at the download candidate.
*/
public long getFileIndex()
{
return fileIndex;
}
public long getTotalDownloadSize()
{
return totalDownloadSize;
}
public void incTotalDownloadSize( int val )
{
totalDownloadSize += val;
}
/**
* Returns the time in millis until the the status timesout.
*/
public long getStatusTimeLeft()
{
long timeLeft = statusTimeout - System.currentTimeMillis();
if ( timeLeft < 0 )
{
timeLeft = 0;
}
return timeLeft;
}
/**
* Returns the current status of the candidate.
*/
public CandidateStatus getStatus()
{
return status;
}
public String getStatusReason()
{
return statusReason;
}
/**
* Indicates how often the last error status repeated. The last error status
* must must not be the current status, but is the current status in case the
* current status is a error status.
* @return how often the last error status repeated
*/
/*public int getErrorStatusRepetition()
{
return errorStatusRepetition;
}*/
/**
* Returns the number of failed connection tries since the last successful
* connection.
* @return the number of failed connection tries since the last successful
* connection.
*/
public int getFailedConnectionTries()
{
return failedConnectionTries;
}
/**
* The download candidate vendor.
* @return the vendor name
*/
public String getVendor()
{
return vendor;
}
public void setVendor( String aVendor )
{
if ( vendor == null || !vendor.equals( aVendor ) )
{
// verify characters - this is used to remove invalid xml characters
for ( int i = 0; i < aVendor.length(); i++ )
{
if ( !XMLUtils.isXmlChar( aVendor.charAt( i ) ) )
{
return;
}
}
vendor = aVendor;
addToCandidateLog( "Set vendor to: " + vendor );
}
}
public boolean isG2FeatureAdded()
{
return isG2FeatureAdded;
}
public void setG2FeatureAdded( boolean state )
{
isG2FeatureAdded = state;
}
public void updateXQueueParameters( XQueueParameters newXQueueParameters )
{
if ( xQueueParameters == null )
{
xQueueParameters = newXQueueParameters;
}
else
{
xQueueParameters.update( newXQueueParameters );
}
}
public XQueueParameters getXQueueParameters()
{
return xQueueParameters;
}
public boolean isPushNeeded()
{
return isPushNeeded;
}
/**
* Returns the array of push proxies of this connection
* or null if there are no available.
* @return an array of push proxies or null
*/
public DestAddress[] getPushProxyAddresses()
{
return pushProxyAddresses;
}
public void setPushProxyAddresses( DestAddress[] addresses )
{
pushProxyAddresses = addresses;
}
public long getLastConnectionTime()
{
return lastConnectionTime;
}
public void setLastConnectionTime( long lastConnectionTime )
{
this.lastConnectionTime = lastConnectionTime;
}
public boolean isChatSupported()
{
return isChatSupported;
}
public void setChatSupported( boolean state )
{
isChatSupported = state;
}
/**
* Returns true if the candidate is remotly queued, false otherwise.
* @return true is the candidate is remotly queued, false otherwise.
*/
public boolean isRemotlyQueued()
{
return status == CandidateStatus.REMOTLY_QUEUED;
}
/**
* Returns true if the candidate is busy or remotly queued, false otherwise.
* @return true is the candidate is busy or remotly queued, false otherwise.
*/
public boolean isBusyOrQueued()
{
return status == CandidateStatus.BUSY || status == CandidateStatus.REMOTLY_QUEUED;
}
/**
* Returns true if the candidate range is unavailable, false otherwise.
* @return true is the candidate range is unavailable, false otherwise.
*/
public boolean isRangeUnavailable()
{
return status == CandidateStatus.RANGE_UNAVAILABLE;
}
/**
* Returns true if the candidate is downloading, false otherwise.
* @return true is the candidate is downloading, false otherwise.
*/
public boolean isDownloading()
{
return status == CandidateStatus.DOWNLOADING;
}
public boolean isThexSupported()
{
return thexUri != null && thexRoot != null && thexStatus == ThexStatus.OPEN;
}
public String getThexUri()
{
return thexUri;
}
public String getThexRoot()
{
return thexRoot;
}
public void setThexUriRoot( String thexUri, String thexRoot )
{
this.thexUri = thexUri;
this.thexRoot = thexRoot;
}
public void setThexStatus( ThexStatus thexStatus )
{
this.thexStatus = thexStatus;
}
/**
* Returns the list of alt locs already send to this connection.
* @return the list of alt locs already send to this connection.
*/
public Set<AlternateLocation> getSendAltLocsSet()
{
if ( sendAltLocSet == null )
{// TODO2 use something like a LRUMap. But current LRUMap uses maxSize
// as initial hash size. This would be much to big in most cases!
// Currently this HashSet has no size boundry. We would need our own
// LRUMap implementation with a low initial size and a different max size.
sendAltLocSet = new HashSet<AlternateLocation>();
}
return sendAltLocSet;
}
/**
* Sets the available range set.
* @param newRangeSet the available range set.
*/
public void setAvailableRangeSet(HTTPRangeSet newRangeSet)
{
// dont do anything if we have no range set and the scope list is
// uninitialized
if ( (newRangeSet == null || newRangeSet.size() == 0)
&& availableScopeList == null )
{
return;
}
long fileSize = downloadFile.getTotalDataSize();
if ( fileSize == SWDownloadConstants.UNKNOWN_FILE_SIZE )
{// we cant handle available range set without knowing
// file size...
return;
}
if ( newRangeSet == null )
{
// not only clear existing scope list but set it to NULL
availableScopeList = null;
return;
}
// we always initialize a new available scope list here instead of
// clearing and reusing the existing scope list to prevent concurrent
// modification exceptions during the many access operations to the list
// after calls to getAvailableScopeList()
// Also we lock the write operation to prevent giving out a incomplete
// availableScopeList
synchronized( this )
{
availableScopeList = new DownloadScopeList();
// add..
Iterator<Range> iterator = newRangeSet.getIterator();
while ( iterator.hasNext() )
{
Range range = iterator.next();
long start = range.getStartOffset( fileSize );
long end = range.getEndOffset( fileSize );
if ( end < start )
{// this is an invalid range... skip it
NLogger.warn( SWDownloadCandidate.class,
"Invalid range: " +range.buildHTTPRangeString() + " - "
+ start + " - " + end + " - " + fileSize + " - " + vendor );
continue;
}
DownloadScope scope = new DownloadScope(
start, end );
availableScopeList.add( scope );
}
availableRangeSetTime = System.currentTimeMillis();
}
NLogger.debug( SWDownloadCandidate.class,
"Added new rangeset for " + downloadFile.getFileName()
+ ": " + newRangeSet);
}
/**
* Returns the available range set or null if not set.
* @return the available range set or null if not set.
*/
public DownloadScopeList getAvailableScopeList( )
{
if ( System.currentTimeMillis() >
availableRangeSetTime + AVAILABLE_RANGE_SET_TIMEOUT )
{
setAvailableRangeSet( null );
}
// We lock the access to prevent giving out a currently written to
// availableScopeList
synchronized( this )
{
return availableScopeList;
}
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int PRIME = 31;
int result = 17;
result = PRIME * result + ((hostAddress == null) ? 0 : hostAddress.hashCode());
return result;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj )
{
if ( this == obj ) return true;
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
final SWDownloadCandidate other = (SWDownloadCandidate) obj;
if ( hostAddress == null )
{
if ( other.hostAddress != null ) return false;
}
else if ( !hostAddress.equals( other.hostAddress ) ) return false;
return true;
}
/**
* Sets the status of the candidate and fulfills the required actions
* necessary for that status. E.g. setting the statusTime.
* @param newStatus the status to set from
* SWDownloadConstants.STATUS_CANDIDATE_*
*/
public void setStatus( CandidateStatus newStatus )
{
setStatus( newStatus, -1, null );
}
/**
* Sets the status of the candidate and fulfills the required actions
* necessary for that status. E.g. setting the statusTime.
* @param newStatus the status to set from
* SWDownloadConstants.STATUS_CANDIDATE_*
* @param statusSeconds the time in seconds the status should last.
*/
public void setStatus( CandidateStatus newStatus, int statusSeconds )
{
setStatus( newStatus, statusSeconds, null );
}
/**
* Sets the status of the candidate and fulfills the required actions
* necessary for that status. E.g. setting the statusTime.
* @param newStatus the status to set from
* SWDownloadConstants.STATUS_CANDIDATE_*
* @param statusSeconds the time in seconds the status should last only
* used for REMOTLY_QUEUED.
* @param aStatusReason a status reason to be displayed to the user.
*/
public void setStatus( CandidateStatus newStatus, int statusSeconds, String aStatusReason )
{
// Don't care for same status
if ( status == newStatus )
{
return;
}
CandidateStatus oldStatus = status;
status = newStatus;
if ( System.currentTimeMillis() < statusTimeout
&& NLogger.isWarnEnabled( SWDownloadCandidate.class ) )
{
NLogger.warn( SWDownloadCandidate.class,
"Status timeout has not passed yet." );
}
long newStatusTimeout;
statusTimeout = newStatusTimeout = System.currentTimeMillis();
switch( status )
{
case BAD:
newStatusTimeout += BAD_CANDIDATE_STATUS_TIMEOUT;
break;
case IGNORED:
newStatusTimeout = Long.MAX_VALUE;
break;
case CONNECTING:
//connectionTries ++;
break;
case CONNECTION_FAILED:
failedConnectionTries ++;
if ( failedConnectionTries >= IGNORE_CANDIDATE_CONNECTION_TRIES )
{// we have tried long enough to connect to this candidate, ignore
// it for the future (causes delete after session).
downloadFile.markCandidateIgnored( this,
"CandidateStatusReason_ConnectionFailed" );
// markCandidateIgnored updates the statusTimeout
// and statusReason, this value is reset here to not
// overwrite it...
newStatusTimeout = statusTimeout;
aStatusReason = statusReason;
break;
}
else if ( failedConnectionTries >= BAD_CANDIDATE_CONNECTION_TRIES )
{
// we dont remove candidates but put them into a bad list.
// once we see a new X-Alt the candidate is valid again.
// candidates might go into the bad list quickly.
// when no "good" candidates are avaiable we might also try additional
// bad list connects, every 3 hours or so...
downloadFile.markCandidateBad( this );
// markCandidateBad updates the statusTimeout, this value is
// reset here to not overwrite it...
newStatusTimeout = statusTimeout;
break;
}
else
{
downloadFile.markCandidateMedium( this );
newStatusTimeout += calculateConnectionFailedTimeout();
break;
}
// watch... no break here...
case REQUESTING:
failedConnectionTries = 0;
break;
case BUSY:
case RANGE_UNAVAILABLE:
case REMOTLY_QUEUED:
failedConnectionTries = 0;
if ( statusSeconds > 0 )
{
newStatusTimeout += statusSeconds * 1000L;
}
else
{
newStatusTimeout += determineErrorStatusTimeout( status );
}
break;
case PUSH_REQUEST:
newStatusTimeout += DownloadPrefs.PushRequestTimeout.get().intValue();
break;
case DOWNLOADING:
// clear the current error status... use the waiting status for this
errorStatus = CandidateStatus.WAITING;
failedConnectionTries = 0;
break;
}
this.statusReason = aStatusReason;
NLogger.debug( SWDownloadCandidate.class,
"Setting status from " + oldStatus + " to " + newStatus
+ " and raise timeout from " + statusTimeout + " to " + newStatusTimeout + "(" +
(newStatusTimeout-statusTimeout) + ") Reason:" + aStatusReason + ".");
addToCandidateLog( "Setting status to "
+ SWDownloadInfo.getDownloadCandidateStatusString(this)
+ " and raise timeout from " + statusTimeout + " to "
+ newStatusTimeout + "(" + (newStatusTimeout-statusTimeout)
+ ") Reason:" + aStatusReason + ". OldStatus: " + oldStatus);
statusTimeout = newStatusTimeout;
fireCandidateStatusChange( oldStatus, newStatus );
}
/**
* Calculates the timeout between the last failed connection and the next
* connection try.
* @return the number of millies to wait till the status expires.
*/
private long calculateConnectionFailedTimeout()
{
// Once we are over BAD_CANDIDATE_CONNECTION_TRIES we dont call this
// method anymore and use BAD_CANDIDATE_STATUS_TIMEOUT instead.
// When CONNECTION_FAILED_STEP_TIME time is 2 it gives a sequence of
// tries: 1, 2, 3,| 4, 5, 6, 7, 8, 9, 10
// to: 2, 4, 8,| 16, 32, 64, 128, 128, 128, 128
// to2: 2, 6, 12,| 22, 40, 74, 140, 142, 144, 146
//
// to1 - would cause the values to double on each repetition.
// CONNECTION_FAILED_STEP_TIME * (long)Math.pow( 2,
// Math.min( failedConnectionTries - 1, 7 ) );
// to2 - would add a raising penalty to to1 on each repetition
// CONNECTION_FAILED_STEP_TIME * (long)Math.pow( 2,
// Math.min( failedConnectionTries - 1, 7 ) )
// + (failedConnectionTries - 1) * 2;
//
// We use to2 currently:
return CONNECTION_FAILED_STEP_TIME * (long)Math.pow( 2,
Math.min( failedConnectionTries - 1, 7 ) )
+ (failedConnectionTries - 1) * 2;
}
/**
* Maintains error status tracking. This is needed to track repeting errors
* that will be handled by flexible status timeouts.
* @returns the livetime of the status.
*/
private long determineErrorStatusTimeout( CandidateStatus aErrorStatus )
{
if ( errorStatus == aErrorStatus )
{
errorStatusRepetition ++;
}
else
{
errorStatus = aErrorStatus;
errorStatusRepetition = 0;
}
switch( errorStatus )
{
case BUSY:
// we can add here a step thing for each retry with a top limit
// and a shorter start sleep time but currently we keep it like this.
return HOST_BUSY_SLEEP_TIME;
/*( statusRepetition + 1 ) * */
case RANGE_UNAVAILABLE:
// when step time is 1 it gives a sequence of
// 1, 2, 4, 8, 16, 32...
// this would cause the values to double on each repetition.
return RANGE_UNAVAILABLE_STEP_TIME * (long)Math.pow( 2, errorStatusRepetition );
case REMOTLY_QUEUED:
if ( xQueueParameters == null )
{
return 0;
}
else
{
return xQueueParameters.getRequestSleepTime();
}
default:
NLogger.warn( SWDownloadCandidate.class, "Unknown error status: " + errorStatus );
return 0;
}
}
/**
* Manually forces a connection retry, when the candidate is in a
* valid state.
* The method should only be called from user triggered GUI action.
*/
public void manualConnectionRetry()
{
if ( status != CandidateStatus.BUSY &&
status != CandidateStatus.CONNECTION_FAILED &&
status != CandidateStatus.RANGE_UNAVAILABLE &&
status != CandidateStatus.BAD &&
status != CandidateStatus.IGNORED )
{
return;
}
setStatus( CandidateStatus.WAITING );
}
/**
* Returns if the candidate is able to be allocated. To be allocated a
* candidate must not have a worker assigned and the nextRetryTime must be
* passed.
*/
public boolean isAbleToBeAllocated( )
{
// Do not allow allocation if it's too slow!
if (lastTransferRateBPS < DownloadPrefs.CandidateMinAllowedTransferRate.get().intValue()
&& lastTransferRateBPS > 0)
{
addToCandidateLog( "Refusing candidate allocation as last transfer rate was only "
+ lastTransferRateBPS + " bps");
NLogger.debug( SWDownloadCandidate.class,
"Refusing candidate allocation as last transfer rate was only "
+ lastTransferRateBPS + " bps");
return false;
}
long currentTime = System.currentTimeMillis();
return statusTimeout <= currentTime;
}
public void associateDownloadSegment( SWDownloadSegment aSegment )
{
downloadSegment = aSegment;
}
/**
* Returns the preferred (ie: largest) segment size to use for this candidate.
* This will be DEFAULT_SEGMENT_SIZE initially, but will then be calculated so that if
* the transfer rate for the previous segment were maintained, the next segment
* would take DEFAULT_SEGMENT_TIME seconds.
* The value MAXIMUM_ALLOWED_SEGMENT_SIZE is respected.
*/
public long getPreferredSegmentSize()
{
long result;
// No previous segment has been transferred
if (lastTransferRateBPS == 0)
{
result = DownloadPrefs.SegmentInitialSize.get().intValue();
}
else
{
// default is rate * seconds, but not lower then a segmentMultiple.
result = Math.max( lastTransferRateBPS *
DownloadPrefs.SegmentTransferTargetTime.get().intValue(),
DownloadPrefs.SegmentMultiple.get().intValue() );
// round the result up to the next multiple of segmentMultiple
long remainder = (-result) % DownloadPrefs.SegmentMultiple.get().intValue();
result += remainder;
// Too fast (ie: segment would be bigger than allowed
if (result > DownloadPrefs.SegmentMaximumSize.get().intValue() )
{
result = DownloadPrefs.SegmentMaximumSize.get().intValue();
}
}
if ( result < 1 )
{
NLogger.warn( SWDownloadCandidate.class,
"Preferred size looks strange. bps=" + lastTransferRateBPS
+ " and stt=" + DownloadPrefs.SegmentTransferTargetTime.get().intValue()
+ " res " + result
+ " res1 " + (lastTransferRateBPS * DownloadPrefs.SegmentTransferTargetTime.get().intValue())
+ " res2 " + ((-result) % DownloadPrefs.SegmentMultiple.get().intValue() ) );
result = DownloadPrefs.SegmentInitialSize.get().intValue();
}
NLogger.debug( SWDownloadCandidate.class,
"Preferred segment size is " + result);
return result;
}
public void releaseDownloadSegment()
{
if ( downloadSegment != null )
{
lastTransferRateBPS = downloadSegment.getLongTermTransferRate();
downloadSegment = null;
}
}
/**
* Provides the caller with the currently associated segment or null if no
* association is available.
* @return the associated segment or null.
*/
public SWDownloadSegment getDownloadSegment()
{
return downloadSegment;
}
/**
* Creates the DElement representation of this object to serialize it
* into XML.
* @return the DElement representation of this object
*/
public DDownloadCandidate createDDownloadCandidate()
{
DDownloadCandidate dCandidate = new DDownloadCandidate();
dCandidate.setFileIndex( fileIndex );
dCandidate.setFileName( fileName );
if ( guid != null )
{
dCandidate.setGuid( guid.toHexString() );
}
if ( downloadURI != null )
{
dCandidate.setDownloadUri( downloadURI.getEscapedURI() );
}
// we need to store the resource urn since not all candidates
// have one, and we need to identify them.
if ( resourceURN != null )
{
dCandidate.setResourceUrn( resourceURN.getAsString() );
}
dCandidate.setPushNeeded( isPushNeeded );
dCandidate.setChatSupported( isChatSupported );
dCandidate.setRemoteHost( hostAddress.getFullHostName() );
dCandidate.setVendor( vendor );
if ( lastConnectionTime > 0 )
{
dCandidate.setLastConnectionTime( lastConnectionTime );
}
// also maintain count how often a connection was failed in a row...
//if ( failedConnectionTries >= BAD_CANDIDATE_CONNECTION_TRIES )
if ( failedConnectionTries > 0 )
{
dCandidate.setConnectionFailedRepetition( failedConnectionTries );
}
return dCandidate;
}
@Override
public String toString()
{
StringBuffer buffer = new StringBuffer( "[Candidate: ");
if ( vendor != null )
{
buffer.append( vendor );
buffer.append( ',' );
}
buffer.append( "Adr:" );
buffer.append( hostAddress );
buffer.append( " ->" );
buffer.append( super.toString() );
buffer.append( "]" );
return buffer.toString();
}
public void addToCandidateLog( String message )
{
if ( candidateLogBuffer != null )
{
LogRecord record = new LogRecord( this, message );
candidateLogBuffer.addLogRecord( record );
}
}
public void addToCandidateLog( Throwable th )
{
if ( candidateLogBuffer != null )
{
StackTraceElement[] stackTrace = th.getStackTrace();
if ( stackTrace == null )
{
return;
}
for ( int i = 0; i < 2 && i < stackTrace.length; i++ )
{
LogRecord record = new LogRecord( this, stackTrace[i].toString() );
candidateLogBuffer.addLogRecord( record );
}
}
}
///////////////////// START event handling methods ////////////////////////
private void fireCandidateStatusChange( CandidateStatus oldStatus, CandidateStatus newStatus )
{
Servent.getInstance().getEventService().publish( PhexEventTopics.Download_Candidate_Status,
new ChangeEvent( this, oldStatus, newStatus ) );
}
///////////////////// END event handling methods ////////////////////////
}
The table below shows all metrics for SWDownloadCandidate.java.




