Java tutorial
/* * PHEX - The pure-java Gnutella-servent. * Copyright (C) 2001 - 2011 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 4542 2011-10-24 09:51:11Z gregork $ */ package phex.download.swarming; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.download.DownloadScope; import phex.download.DownloadScopeList; import phex.download.RemoteFile; import phex.http.HTTPHeader; import phex.http.HTTPRangeSet; import phex.http.Range; import phex.http.XQueueParameters; import phex.msg.GUID; import phex.net.repres.PresentationManager; import phex.DownloadPrefs; import phex.query.QueryHitHost; import phex.util.URLCodecUtils; import phex.util.URLUtil; import phex.xml.XMLUtils; import phex.xml.sax.downloads.DDownloadCandidate; import java.util.*; /** * 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 { private static final Logger logger = LoggerFactory.getLogger(SWDownloadCandidate.class); /** * 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 event service to inform of status changes. */ /** * 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 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 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; /** * Indicates if it assumed that the available scope list is complete. */ private boolean isAvailableScopeComplete; /** * 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; /** * A list containing the received content urn headers of a candidate. * The list is lazy initialized on first access. */ private List<HTTPHeader> contentURNHeaders; /** * 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) { logger.warn("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) { logger.warn("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.. logger.warn(e.toString(), 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; } /** * 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); } public String getStatusReason() { return statusReason; } /** * 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; } /** * 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; }*/ /** * 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; } public List<HTTPHeader> getContentURNHeaders() { if (contentURNHeaders == null) { contentURNHeaders = new ArrayList<HTTPHeader>(); } return contentURNHeaders; } /** * Sets the available range set. * * @param newRangeSet the available range set, must not be null. */ public void setAvailableRangeSet(HTTPRangeSet newRangeSet) { if (newRangeSet == null) { // not only clear existing scope list but set it to null to give // free memory availableScopeList = null; return; } if (!processAvailableRangeSet(newRangeSet)) { return; } if (newRangeSet.size() == 0) { // not only clear existing scope list but set it to null to give // free memory 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) { DownloadScopeList newScopeList = new DownloadScopeList(); addHttpRangeSet(newScopeList, newRangeSet); availableScopeList = newScopeList; availableRangeSetTime = System.currentTimeMillis(); isAvailableScopeComplete = true; } logger.debug("Added new rangeset for {}: ", downloadFile.getFileName(), newRangeSet); } /** * Sets the available range set. * * @param rangeSetToAdd the available range set, must not be null */ public void addToAvailableRangeSet(HTTPRangeSet rangeSetToAdd, long availableSize) { if (rangeSetToAdd == null) { throw new NullPointerException(); } if (!processAvailableRangeSet(rangeSetToAdd)) { return; } if (rangeSetToAdd.size() == 0) { // nothing to add... 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) { DownloadScopeList newScopeList = new DownloadScopeList(); // add all old scopes... if (availableScopeList != null) { newScopeList.addAll(availableScopeList); } addHttpRangeSet(newScopeList, rangeSetToAdd); availableScopeList = newScopeList; availableRangeSetTime = System.currentTimeMillis(); isAvailableScopeComplete = availableScopeList.getAggregatedLength() >= availableSize; } logger.debug("Added additional rangeset for {}: {}", downloadFile.getFileName(), rangeSetToAdd); } /** * Used to check if the range set should be processed further or * should be ignored. * * @param rangeSet * @return true to process, false to ignore. */ private boolean processAvailableRangeSet(HTTPRangeSet rangeSet) { // don't do anything if we have no range set and the scope list is // uninitialized if (rangeSet.size() == 0 && availableScopeList == null) { return false; } long fileSize = downloadFile.getTotalDataSize(); return fileSize != SWDownloadConstants.UNKNOWN_FILE_SIZE; } /** * Helper method to add a {@link HTTPRangeSet} to a {@link DownloadScopeList}. * * @param scopeList the scope list to add the ranges to. * @param httpRangeSet the range set to add to the scope list. */ private void addHttpRangeSet(DownloadScopeList scopeList, HTTPRangeSet httpRangeSet) { long fileSize = downloadFile.getTotalDataSize(); Iterator<Range> iterator = httpRangeSet.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 logger.warn("Invalid range: {} - {} - {} - {} - {}", range.buildHTTPRangeString(), start, end, fileSize, vendor); continue; } DownloadScope scope = new DownloadScope(start, end); scopeList.add(scope); } } /** * 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 && isAvailableScopeComplete) { setAvailableRangeSet(null); } // We lock the access to prevent giving out a currently written to // availableScopeList synchronized (this) { return availableScopeList; } } public boolean isAvailableScopeComplete() { synchronized (this) { return isAvailableScopeComplete; } } /** * @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_* * @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 (logger.isWarnEnabled() && System.currentTimeMillis() < statusTimeout) { logger.warn("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; } 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; } else { downloadFile.markCandidateMedium(this); newStatusTimeout += calculateConnectionFailedTimeout(); } break; case ALLOCATING_SEGMENT: 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; logger.debug("Setting status from {} to {} and raise timeout from {} to {}({}) Reason:{}.", oldStatus, newStatus, statusTimeout, newStatusTimeout, newStatusTimeout - statusTimeout, 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: // Java 6 - Math.scalb() might perform faster. 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. // Java 6 - Math.scalb() might perform faster. return RANGE_UNAVAILABLE_STEP_TIME * (long) Math.pow(2, errorStatusRepetition); case REMOTLY_QUEUED: if (xQueueParameters == null) { return 0; } else { return xQueueParameters.getRequestSleepTime(); } default: logger.warn("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"); logger.debug("Refusing candidate allocation as last transfer rate was only {} bps.", lastTransferRateBPS); 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) { logger.warn("Preferred size looks strange. bps={} and stt={} res {} res1 {} res2 {}", lastTransferRateBPS, DownloadPrefs.SegmentTransferTargetTime.get().intValue(), result, lastTransferRateBPS * DownloadPrefs.SegmentTransferTargetTime.get().intValue(), (-result) % DownloadPrefs.SegmentMultiple.get().intValue()); result = DownloadPrefs.SegmentInitialSize.get().intValue(); } logger.debug("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); } } } private void fireCandidateStatusChange(CandidateStatus oldStatus, CandidateStatus newStatus) { } /** * 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 looking for a segment. ALLOCATING_SEGMENT, // Indicating the candidate is remotely queued. REMOTLY_QUEUED, // Indicating the candidate is requesting a segment REQUESTING, // Indicating the candidate is downloading DOWNLOADING } ///////////////////// START event handling methods //////////////////////// 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 } ///////////////////// END event handling methods //////////////////////// }