/* * Copyright (C) 2005, 2006 Aelitis, All Rights Reserved. * * 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. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.plugins.startstoprules.defaultplugin; import java.util.Iterator; import java.util.List; import org.gudy.azureus2.core3.config.COConfigurationListener; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerState; import org.gudy.azureus2.core3.download.DownloadManagerStateAttributeListener; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.DisplayFormatters; import org.gudy.azureus2.core3.util.SystemTime; import org.gudy.azureus2.core3.util.TimeFormatter; import org.gudy.azureus2.plugins.PluginConfig; import org.gudy.azureus2.plugins.download.Download; import org.gudy.azureus2.plugins.download.DownloadScrapeResult; import org.gudy.azureus2.plugins.download.DownloadStats; import org.gudy.azureus2.plugins.logging.LoggerChannel; import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils; /** * @author TuxPaper * @created Dec 13, 2005 * */ public class DefaultRankCalculator implements DownloadManagerStateAttributeListener, Comparable { /** All of the First Priority rules must match */ public static final int FIRSTPRIORITY_ALL = 0; /** Any of the First Priority rules must match */ public static final int FIRSTPRIORITY_ANY = 1; /** * Force torrent to be "Actively Seeding/Downloading" for this many ms upon * start of torrent. */ private static final int FORCE_ACTIVE_FOR = 30000; /** * Wait XX ms before really changing activity (DL or CDing) state when * state changes via speed change */ private static final int ACTIVE_CHANGE_WAIT = 10000; /** Maximium ranking that a torrent can get using the SPRATIO ranking type */ private static int SPRATIO_BASE_LIMIT = 99999; /** * Amount to shift over the rank of the SEEDONLY ranking type, to make room * in case the user has fallback to SPRATIO set. */ private static int SEEDONLY_SHIFT = SPRATIO_BASE_LIMIT + 1; /** * For loading config settings */ private static COConfigurationListener configListener = null; // // Seeding Rank (SR) Limits and Values /** Rank that complete starts at (and incomplete ends at + 1) */ public static final int SR_COMPLETE_STARTS_AT = 1000000000; // billion /** Maximimum ranking for time queue mode. 1 unit is a second */ public static final int SR_TIMED_QUEUED_ENDS_AT = 999999; // 11.57 days /** Ranks below this value are for torrents to be ignored (moved to bottom & queued) */ public static final int SR_IGNORED_LESS_THAN = -1; /** Seeding Rank value when download is marked as not queued */ public static final int SR_NOTQUEUED = -2; /** Seeding Rank value when download is marked as S:P Ratio Met for FP */ public static final int SR_FP_SPRATIOMET = -3; /** Seeding Rank value when download is marked as P:1S Ratio Met */ public static final int SR_RATIOMET = -4; /** Seeding Rank value when download is marked as # Seeds Met */ public static final int SR_NUMSEEDSMET = -5; /** Seeding Rank value when download is marked as 0 Peers and FP */ public static final int SR_FP0PEERS = -6; /** Seeding Rank value when download is marked as 0 Peers */ public static final int SR_0PEERS = -7; /** Seeding Rank value when download is marked as Share Ratio Met */ public static final int SR_SHARERATIOMET = -8; public static final String[] SR_NEGATIVE_DEBUG = { "?", "Not Qd", "FP SPRatioMet", "Ratio Met", "# CDs Met", "FP 0 Peers", "0 Peers", "Share Ratio Met" }; private static final long STALE_REFRESH_INTERVAL = 1000 * 60; // // Static config values /** Ranking System to use */ protected static int iRankType = -1; /** Min # of Peers needed before boosting the rank of downloads with no seeds */ private static int minPeersToBoostNoSeeds; /** Min Speed needed to count a incomplete download as being actively downloading */ private static int minSpeedForActiveDL; /** Min speed needed to count a complete download as being actively seeding */ private static int minSpeedForActiveSeeding; // Ignore torrent if seed count is at least.. private static int iIgnoreSeedCount; // Ignore even when First Priority private static boolean bIgnore0Peers; private static int iIgnoreShareRatio; private static int iIgnoreShareRatio_SeedStart; private static int iIgnoreRatioPeers; private static int iIgnoreRatioPeers_SeedStart; private static int iRankTypeSeedFallback; private static boolean bPreferLargerSwarms; private static int minQueueingShareRatio; // Ignore First Priority private static int iFirstPriorityIgnoreSPRatio; private static boolean bFirstPriorityIgnore0Peer; private static int iFirstPriorityType; private static int iFirstPrioritySeedingMinutes; private static int iFirstPriorityActiveMinutes; private static int iFirstPriorityIgnoreIdleHours; private static long minTimeAlive; private static boolean bAutoStart0Peers; // // Class variables protected final Download dl; private boolean bActivelyDownloading; private long lDLActivelyChangedOn; private boolean bActivelySeeding; private long lCDActivelyChangedOn; private long staleCDSince; private long staleCDOffset; private long lastStaleCDRefresh; private boolean bIsFirstPriority; private int dlSpecificMinShareRatio; private int dlSpecificMaxShareRatio; /** Public for tooltip to access it */ public String sExplainFP = ""; /** Public for tooltip to access it */ public String sExplainSR = ""; /** Public for tooltip to access it */ public String sTrace = ""; private AEMonitor downloadData_this_mon = new AEMonitor( "StartStopRules:downloadData"); private final StartStopRulesDefaultPlugin rules; // state-caches for sorting int lastModifiedScrapeResultPeers = 0; int lastModifiedScrapeResultSeeds = 0; int lastModifiedShareRatio = 0; // modified by a listener in StartStopRulesDefaultPlugin boolean lastScrapeResultOk = false; /** * Default Initializer * * @param _rules * @param _dl */ public DefaultRankCalculator(StartStopRulesDefaultPlugin _rules, Download _dl) { rules = _rules; dl = _dl; DownloadManager core_dm = PluginCoreUtils.unwrap( dl ); DownloadManagerState dm_state = core_dm.getDownloadState(); dlSpecificMinShareRatio = dm_state.getIntParameter( DownloadManagerState.PARAM_MIN_SHARE_RATIO ); dlSpecificMaxShareRatio = dm_state.getIntParameter( DownloadManagerState.PARAM_MAX_SHARE_RATIO ); dm_state.addListener( this, DownloadManagerState.AT_PARAMETERS, DownloadManagerStateAttributeListener.WRITTEN ); try { downloadData_this_mon.enter(); if (configListener == null) { configListener = new COConfigurationListener() { public void configurationSaved() { reloadConfigParams(rules.plugin_config); } }; COConfigurationManager.addListener(configListener); configListener.configurationSaved(); } } finally { downloadData_this_mon.exit(); } } public void attributeEventOccurred( DownloadManager download, String attribute, int event_type) { DownloadManager core_dm = PluginCoreUtils.unwrap( dl ); DownloadManagerState dm_state = core_dm.getDownloadState(); dlSpecificMinShareRatio = dm_state.getIntParameter( DownloadManagerState.PARAM_MIN_SHARE_RATIO ); dlSpecificMaxShareRatio = dm_state.getIntParameter( DownloadManagerState.PARAM_MAX_SHARE_RATIO ); } protected void destroy() { DownloadManager core_dm = PluginCoreUtils.unwrap( dl ); DownloadManagerState dm_state = core_dm.getDownloadState(); dm_state.removeListener( this, DownloadManagerState.AT_PARAMETERS, DownloadManagerStateAttributeListener.WRITTEN ); } /** * Load config values into the static variables * * @param cfg */ public static void reloadConfigParams(PluginConfig cfg) { final String PREFIX = "StartStopManager_"; iRankType = cfg.getUnsafeIntParameter(PREFIX + "iRankType"); minPeersToBoostNoSeeds = cfg.getUnsafeIntParameter(PREFIX + "iMinPeersToBoostNoSeeds"); minSpeedForActiveDL = cfg.getUnsafeIntParameter(PREFIX + "iMinSpeedForActiveDL"); minSpeedForActiveSeeding = cfg.getUnsafeIntParameter(PREFIX + "iMinSpeedForActiveSeeding"); iRankTypeSeedFallback = cfg.getUnsafeIntParameter(PREFIX + "iRankTypeSeedFallback"); bPreferLargerSwarms = cfg.getUnsafeBooleanParameter(PREFIX + "bPreferLargerSwarms"); minTimeAlive = cfg.getUnsafeIntParameter(PREFIX + "iMinSeedingTime") * 1000; bAutoStart0Peers = cfg.getUnsafeBooleanParameter(PREFIX + "bAutoStart0Peers"); // Ignore torrent if seed count is at least.. iIgnoreSeedCount = cfg.getUnsafeIntParameter(PREFIX + "iIgnoreSeedCount"); bIgnore0Peers = cfg.getUnsafeBooleanParameter(PREFIX + "bIgnore0Peers"); iIgnoreShareRatio = (int) (1000 * cfg.getFloatParameter("Stop Ratio")); iIgnoreShareRatio_SeedStart = cfg.getUnsafeIntParameter(PREFIX + "iIgnoreShareRatioSeedStart"); iIgnoreRatioPeers = cfg.getIntParameter("Stop Peers Ratio", 0); iIgnoreRatioPeers_SeedStart = cfg.getUnsafeIntParameter(PREFIX + "iIgnoreRatioPeersSeedStart", 0); minQueueingShareRatio = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_ShareRatio"); iFirstPriorityType = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_Type"); iFirstPrioritySeedingMinutes = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_SeedingMinutes"); iFirstPriorityActiveMinutes = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_DLMinutes"); // Ignore FP iFirstPriorityIgnoreSPRatio = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_ignoreSPRatio"); bFirstPriorityIgnore0Peer = cfg.getUnsafeBooleanParameter(PREFIX + "bFirstPriority_ignore0Peer"); iFirstPriorityIgnoreIdleHours = cfg.getUnsafeIntParameter(PREFIX + "iFirstPriority_ignoreIdleHours"); } /** Sort first by SeedingRank Descending, then by Position Ascending. */ public int compareTo(Object obj) { if (!(obj instanceof DefaultRankCalculator)) { return -1; } DefaultRankCalculator dlData = (DefaultRankCalculator) obj; // Test FP. FP goes to top if (dlData.bIsFirstPriority && !bIsFirstPriority) return 1; if (!dlData.bIsFirstPriority && bIsFirstPriority) return -1; // Test Completeness. Complete go to bottom boolean aIsComplete = dlData.dl.isComplete(); boolean bIsComplete = dl.isComplete(); if (aIsComplete && !bIsComplete) return -1; if (!aIsComplete && bIsComplete) return 1; if (iRankType == StartStopRulesDefaultPlugin.RANK_NONE) { return dl.getPosition() - dlData.dl.getPosition(); } // Check Rank. Large to top int value = dlData.dl.getSeedingRank() - dl.getSeedingRank(); if (value != 0) return value; if (iRankType != StartStopRulesDefaultPlugin.RANK_TIMED) { // Test Large/Small Swarm pref int numPeersThem = dlData.lastModifiedScrapeResultPeers; int numPeersUs = lastModifiedScrapeResultPeers; if (bPreferLargerSwarms) value = numPeersThem - numPeersUs; else value = numPeersUs - numPeersThem; if (value != 0) return value; // Test Share Ratio value = lastModifiedShareRatio - dlData.lastModifiedShareRatio; if (value != 0) return value; } // Test Position return dl.getPosition() - dlData.dl.getPosition(); } public Download getDownloadObject() { return dl; } public boolean isForceActive() { DownloadStats stats = dl.getStats(); return SystemTime.getCurrentTime() - stats.getTimeStarted() <= FORCE_ACTIVE_FOR; } public boolean isQueued() { return( dl.getState() == Download.ST_QUEUED ); } /** * Retrieves whether the torrent is "actively" downloading * * @return true: actively downloading */ public boolean getActivelyDownloading() { boolean bIsActive = false; DownloadStats stats = dl.getStats(); int state = dl.getState(); // In order to be active, // - Must be downloading (and thus incomplete) // - Must be above speed threshold, or started less than 30s ago if (state != Download.ST_DOWNLOADING) { bIsActive = false; } else if (SystemTime.getCurrentTime() - stats.getTimeStarted() <= FORCE_ACTIVE_FOR) { bIsActive = true; } else { // activity based on DL Average bIsActive = (stats.getDownloadAverage() >= minSpeedForActiveDL); if (bActivelyDownloading != bIsActive) { long now = SystemTime.getCurrentTime(); // Change if (lDLActivelyChangedOn == -1) { // Start Timer lDLActivelyChangedOn = now; bIsActive = !bIsActive; } else if (now - lDLActivelyChangedOn < ACTIVE_CHANGE_WAIT) { // Continue as old state until timer finishes bIsActive = !bIsActive; } } else { // no change, reset timer lDLActivelyChangedOn = -1; } } if (bActivelyDownloading != bIsActive) { bActivelyDownloading = bIsActive; if (rules != null) { rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: ActivelyDownloading changed"); } } return bActivelyDownloading; } /** * Retrieves whether the torrent is "actively" seeding * * @return true: actively seeding */ public boolean getActivelySeeding() { boolean bIsActive = false; DownloadStats stats = dl.getStats(); int state = dl.getState(); // Timed torrents don't use a speed threshold, since they are based on time! // However, First Priorities need to be checked for activity so that // timed ones can start when FPs are below threshold. Ditto for 0 Peers // when bAutoStart0Peers if (iRankType == StartStopRulesDefaultPlugin.RANK_TIMED && !isFirstPriority() && !(bAutoStart0Peers && rules.calcPeersNoUs(dl,dl.getAggregatedScrapeResult()) == 0 && lastScrapeResultOk)) { bIsActive = (state == Download.ST_SEEDING); } else if (state != Download.ST_SEEDING || (bAutoStart0Peers && rules.calcPeersNoUs(dl,dl.getAggregatedScrapeResult()) == 0)) { // Not active if we aren't seeding // Not active if we are AutoStarting 0 Peers, and peer count == 0 bIsActive = false; staleCDSince = -1; } else if (SystemTime.getCurrentTime() - stats.getTimeStarted() <= FORCE_ACTIVE_FOR) { bIsActive = true; staleCDSince = -1; } else { bIsActive = (stats.getUploadAverage() >= minSpeedForActiveSeeding); if (bActivelySeeding != bIsActive) { long now = SystemTime.getCurrentTime(); // Change if (lCDActivelyChangedOn < 0) { // Start Timer lCDActivelyChangedOn = now; bIsActive = !bIsActive; } else if (now - lCDActivelyChangedOn < ACTIVE_CHANGE_WAIT) { // Continue as old state until timer finishes bIsActive = !bIsActive; } if (bActivelySeeding != bIsActive) { if (bIsActive) { staleCDSince = -1; staleCDOffset = 0; } else { staleCDSince = System.currentTimeMillis(); } } } else { // no change, reset timer lCDActivelyChangedOn = -1; } } if (bActivelySeeding != bIsActive) { bActivelySeeding = bIsActive; if (rules != null) { rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: ActivelySeeding changed"); } } return bActivelySeeding; } /** Assign Seeding Rank based on RankType * @return New Seeding Rank Value */ public int recalcSeedingRank() { try { downloadData_this_mon.enter(); int oldSR = dl.getSeedingRank(); int newSR = _recalcSeedingRankSupport( oldSR ); if ( newSR != oldSR ){ dl.setSeedingRank( newSR ); } return( newSR ); } finally { downloadData_this_mon.exit(); } } private int _recalcSeedingRankSupport( int oldSR ) { sExplainSR = ""; DownloadStats stats = dl.getStats(); int newSR = 0; // make undownloaded sort to top so they can start first. if (!dl.isComplete()) { newSR = SR_COMPLETE_STARTS_AT + (10000 - dl.getPosition()); // make sure we capture FP being turned off when torrent does from // complete to incomplete isFirstPriority(); if ( rules.bDebugLog ){ sExplainSR += " not complete. SetSR " + newSR + "\n"; } return newSR; } // here we are seeding lastModifiedShareRatio = stats.getShareRatio(); DownloadScrapeResult sr = dl.getAggregatedScrapeResult(); lastModifiedScrapeResultPeers = rules.calcPeersNoUs(dl,sr); lastModifiedScrapeResultSeeds = rules.calcSeedsNoUs(dl,sr); boolean bScrapeResultsOk = (lastModifiedScrapeResultPeers > 0 || lastModifiedScrapeResultSeeds > 0 || lastScrapeResultOk) && (lastModifiedScrapeResultPeers >= 0 && lastModifiedScrapeResultSeeds >= 0); if (!isFirstPriority()) { // Check Ignore Rules // never apply ignore rules to First Priority Matches // (we don't want leechers circumventing the 0.5 rule) //0 means unlimited int activeMaxSR = dlSpecificMaxShareRatio; if ( activeMaxSR <= 0 ){ activeMaxSR = iIgnoreShareRatio; } if (activeMaxSR != 0 && lastModifiedShareRatio >= activeMaxSR && (lastModifiedScrapeResultSeeds >= iIgnoreShareRatio_SeedStart || !bScrapeResultsOk) && lastModifiedShareRatio != -1) { if (rules.bDebugLog) sExplainSR += " shareratio met: shareRatio(" + lastModifiedShareRatio + ") >= " + activeMaxSR + "\n"; return SR_SHARERATIOMET; } else if (rules.bDebugLog && activeMaxSR != 0 && lastModifiedShareRatio >= activeMaxSR) { sExplainSR += " shareratio NOT met: "; if (lastModifiedScrapeResultSeeds >= iIgnoreShareRatio_SeedStart) sExplainSR += lastModifiedScrapeResultSeeds + " below seed threshold of " + iIgnoreShareRatio_SeedStart; sExplainSR += "\n"; } if (lastModifiedScrapeResultPeers == 0 && bScrapeResultsOk) { // If both bIgnore0Peers and bFirstPriorityIgnore0Peer are on, // we won't know which one it is at this point. // We have to use the normal SR_0PEERS in case it isn't FP if (bIgnore0Peers) { if (rules.bDebugLog) sExplainSR += " Ignore 0 Peers criteria met\n"; return SR_0PEERS; } // if (bFirstPriorityIgnore0Peer) { // if (rules.bDebugLog) // sExplainSR += " Ignore 0 Peers criteria for FP met\n"; // // return SR_FP0PEERS; // } } else if (rules.bDebugLog && lastModifiedScrapeResultPeers == 0) { sExplainSR += " 0 Peer Ignore rule NOT applied: Scrape invalid\n"; } // if (numPeers != 0 && iFirstPriorityIgnoreSPRatio != 0 // && numSeeds / numPeers >= iFirstPriorityIgnoreSPRatio) { // if (rules.bDebugLog) // sExplainSR += " Ignore rule for S:P Ratio for FP met. Current: (" // + (numSeeds / numPeers) // + ") >= Threshold(" // + iFirstPriorityIgnoreSPRatio + ")\n"; // // return SR_FP_SPRATIOMET; // } //0 means disabled if ((iIgnoreSeedCount != 0) && (lastModifiedScrapeResultSeeds >= iIgnoreSeedCount)) { if (rules.bDebugLog) sExplainSR += " SeedCount Ignore rule met. numSeeds(" + lastModifiedScrapeResultSeeds + " >= iIgnoreSeedCount(" + iIgnoreSeedCount + ")\n"; return SR_NUMSEEDSMET; } // Ignore when P:S ratio met // (More Peers for each Seed than specified in Config) //0 means never stop if (iIgnoreRatioPeers != 0 && lastModifiedScrapeResultSeeds != 0) { float ratio = (float) lastModifiedScrapeResultPeers / lastModifiedScrapeResultSeeds; if (ratio <= iIgnoreRatioPeers && lastModifiedScrapeResultSeeds >= iIgnoreRatioPeers_SeedStart) { if (rules.bDebugLog) sExplainSR += " P:S Ignore rule met. ratio(" + ratio + " <= threshold(" + iIgnoreRatioPeers_SeedStart + ")\n"; return SR_RATIOMET; } } } // Never do anything with rank type of none if (iRankType == StartStopRulesDefaultPlugin.RANK_NONE) { if (rules.bDebugLog) sExplainSR += " Ranking Type set to none.. blanking seeding rank\n"; // everythink ok! return newSR; } if (iRankType == StartStopRulesDefaultPlugin.RANK_TIMED) { if (bIsFirstPriority) { newSR += SR_TIMED_QUEUED_ENDS_AT + 1; return newSR; } int state = dl.getState(); if (state == Download.ST_STOPPING || state == Download.ST_STOPPED || state == Download.ST_ERROR) { if (rules.bDebugLog) sExplainSR += " Download stopping, stopped or in error\n"; return SR_NOTQUEUED; } else if (state == Download.ST_SEEDING || state == Download.ST_READY || state == Download.ST_WAITING || state == Download.ST_PREPARING) { // force sort to top long lMsElapsed = 0; if (state == Download.ST_SEEDING && !dl.isForceStart()) lMsElapsed = (SystemTime.getCurrentTime() - stats .getTimeStartedSeeding()); if (lMsElapsed >= minTimeAlive) { newSR = 1; if (oldSR > SR_TIMED_QUEUED_ENDS_AT) { rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: TimeUp"); } } else { newSR = SR_TIMED_QUEUED_ENDS_AT + 1 + (int) (lMsElapsed / 1000); if (oldSR <= SR_TIMED_QUEUED_ENDS_AT) { rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: strange timer change"); } } return newSR; } else { if (oldSR <= 0) { newSR = SR_TIMED_QUEUED_ENDS_AT - dl.getPosition(); rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: NotIgnored"); } return newSR; } } /** * Add to SeedingRank based on Rank Type */ // SeedCount and SPRatio require Scrape Results.. if (bScrapeResultsOk) { if ( iRankType == StartStopRulesDefaultPlugin.RANK_PEERCOUNT ) { if(lastModifiedScrapeResultPeers > lastModifiedScrapeResultSeeds * 10) newSR = 100 * lastModifiedScrapeResultPeers * 10; else newSR = (int)((long)100 * lastModifiedScrapeResultPeers * lastModifiedScrapeResultPeers/(lastModifiedScrapeResultSeeds+1)); } else if ((iRankType == StartStopRulesDefaultPlugin.RANK_SEEDCOUNT) && (iRankTypeSeedFallback == 0 || iRankTypeSeedFallback > lastModifiedScrapeResultSeeds)) { if (lastModifiedScrapeResultSeeds < 10000) newSR = 10000 - lastModifiedScrapeResultSeeds; else newSR = 1; // shift over to make way for fallback newSR *= SEEDONLY_SHIFT; } else { // iRankType == RANK_SPRATIO or we are falling back if (lastModifiedScrapeResultPeers != 0) { if (lastModifiedScrapeResultSeeds == 0) { if (lastModifiedScrapeResultPeers >= minPeersToBoostNoSeeds) newSR += SPRATIO_BASE_LIMIT; } else { // numSeeds != 0 && numPeers != 0 float x = (float) lastModifiedScrapeResultSeeds / lastModifiedScrapeResultPeers; newSR += SPRATIO_BASE_LIMIT / ((x + 1) * (x + 1)); } } } } else { if (rules.bDebugLog) sExplainSR += " Can't calculate SR, no scrape results\n"; } if (staleCDOffset > 0) { // every 10 minutes of not being active, subtract one SR if (newSR > staleCDOffset) { newSR -= staleCDOffset; sExplainSR += " subtracted " + staleCDOffset + " due to non-activeness\n"; } else { staleCDOffset = 0; } } if (newSR < 0) newSR = 1; return newSR; } /** Does the torrent match First Priority criteria? * @return FP State */ public boolean isFirstPriority() { boolean bFP = pisFirstPriority(); if (bIsFirstPriority != bFP) { bIsFirstPriority = bFP; rules.requestProcessCycle(null); if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: FP changed"); } return bIsFirstPriority; } private boolean pisFirstPriority() { if (rules.bDebugLog) sExplainFP = "FP if " + (iFirstPriorityType == FIRSTPRIORITY_ALL ? "all" : "any") + " criteria match:\n"; if (!dl.isPersistent()) { if (rules.bDebugLog) sExplainFP += "Not FP: Download not persistent\n"; return false; } int state = dl.getState(); if (state == Download.ST_ERROR || state == Download.ST_STOPPED) { if (rules.bDebugLog) sExplainFP += "Not FP: Download is ERROR or STOPPED\n"; return false; } // FP only applies to completed if (!dl.isComplete()) { if (rules.bDebugLog) sExplainFP += "Not FP: Download not complete\n"; return false; } List listeners = rules.getFPListeners(); StringBuffer fp_listener_debug = null; if (!listeners.isEmpty()) { if (rules.bDebugLog) fp_listener_debug = new StringBuffer(); for (Iterator iter = listeners.iterator(); iter.hasNext();) { StartStopRulesFPListener l = (StartStopRulesFPListener) iter.next(); boolean result = l.isFirstPriority(dl, lastModifiedScrapeResultSeeds, lastModifiedScrapeResultPeers, fp_listener_debug); if (fp_listener_debug != null && fp_listener_debug.length() > 0) { char last_ch = fp_listener_debug.charAt(fp_listener_debug.length() - 1); if (last_ch != '\n') fp_listener_debug.append('\n'); sExplainFP += fp_listener_debug; fp_listener_debug.setLength(0); } if (result) { return true; } } } // FP doesn't apply when S:P >= set SPratio (SPratio = 0 means ignore) if (lastModifiedScrapeResultPeers > 0 && lastModifiedScrapeResultSeeds > 0 && (lastModifiedScrapeResultSeeds / lastModifiedScrapeResultPeers) >= iFirstPriorityIgnoreSPRatio && iFirstPriorityIgnoreSPRatio != 0) { if (rules.bDebugLog) sExplainFP += "Not FP: S:P >= " + iFirstPriorityIgnoreSPRatio + ":1\n"; return false; } //not FP if no peers //Nolar, 2105 - Gouss, 2203 if (lastModifiedScrapeResultPeers == 0 && lastScrapeResultOk && bFirstPriorityIgnore0Peer) { if (rules.bDebugLog) sExplainFP += "Not FP: 0 peers\n"; return false; } if (iFirstPriorityIgnoreIdleHours > 0) { long lastUploadSecs = dl.getStats().getSecondsSinceLastUpload(); if (lastUploadSecs < 0) { lastUploadSecs = dl.getStats().getSecondsOnlySeeding(); } if (lastUploadSecs > 60 * 60 * (long)iFirstPriorityIgnoreIdleHours) { if (rules.bDebugLog) sExplainFP += "Not FP: " + lastUploadSecs + "s > " + iFirstPriorityIgnoreIdleHours + "h of no upload\n"; return false; } } int shareRatio = dl.getStats().getShareRatio(); int activeMinSR = dlSpecificMinShareRatio; if ( activeMinSR <= 0 ){ activeMinSR = minQueueingShareRatio; } boolean bLastMatched = (shareRatio != -1) && (shareRatio < activeMinSR); if (rules.bDebugLog) sExplainFP += " shareRatio(" + shareRatio + ") < " + activeMinSR + "=" + bLastMatched + "\n"; if (!bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ALL) { if (rules.bDebugLog) sExplainFP += "..Not FP. Exit Early\n"; return false; } if (bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ANY) { if (rules.bDebugLog) sExplainFP += "..Is FP. Exit Early\n"; return true; } bLastMatched = (iFirstPrioritySeedingMinutes == 0); if (!bLastMatched) { long timeSeeding = dl.getStats().getSecondsOnlySeeding(); if (timeSeeding >= 0) { bLastMatched = (timeSeeding < (iFirstPrioritySeedingMinutes * 60)); if (rules.bDebugLog) sExplainFP += " SeedingTime(" + timeSeeding + ") < " + (iFirstPrioritySeedingMinutes * 60) + "=" + bLastMatched + "\n"; if (!bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ALL) { if (rules.bDebugLog) sExplainFP += "..Not FP. Exit Early\n"; return false; } if (bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ANY) { if (rules.bDebugLog) sExplainFP += "..Is FP. Exit Early\n"; return true; } } } else if (rules.bDebugLog) { sExplainFP += " Skipping Seeding Time check (user disabled)\n"; } bLastMatched = (iFirstPriorityActiveMinutes == 0); if (!bLastMatched) { long timeActive = dl.getStats().getSecondsDownloading() + dl.getStats().getSecondsOnlySeeding(); if (timeActive >= 0) { bLastMatched = (timeActive < (iFirstPriorityActiveMinutes * 60)); if (rules.bDebugLog) sExplainFP += " ActiveTime(" + timeActive + ") < " + (iFirstPriorityActiveMinutes * 60) + "=" + bLastMatched + "\n"; if (!bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ALL) { if (rules.bDebugLog) sExplainFP += "..Not FP. Exit Early\n"; return false; } if (bLastMatched && iFirstPriorityType == FIRSTPRIORITY_ANY) { if (rules.bDebugLog) sExplainFP += "..Is FP. Exit Early\n"; return true; } } } else if (rules.bDebugLog) { sExplainFP += " Skipping DL Time check (user disabled)\n"; } if (iFirstPriorityType == FIRSTPRIORITY_ALL) { if (rules.bDebugLog) sExplainFP += "..Is FP\n"; return true; } if (rules.bDebugLog) sExplainFP += "..Not FP\n"; return false; } /** * * @return last calculated FP state */ public boolean getCachedIsFP() { return bIsFirstPriority; } private boolean dlr_test_active; private long dlr_test_start_time; private long dlr_test_bytes_start; private int dlr_test_average_bytes_per_sec = -1; public void setDLRInactive() { dlr_test_active = false; } public void setDLRActive( long time ) { if (rules.bDebugLog) { rules.log.log( dl.getTorrent(), LoggerChannel.LT_INFORMATION, "download speed test starts"); } dlr_test_active = true; dlr_test_start_time = time; dl.moveTo( 1 ); dlr_test_bytes_start = dl.getStats().getDownloaded( true ); } public void setDLRComplete( long time ) { long dlr_test_bytes_end = dl.getStats().getDownloaded( true ); long elapsed = time - dlr_test_start_time; if ( elapsed >= 1000 ){ dlr_test_average_bytes_per_sec = (int)((dlr_test_bytes_end-dlr_test_bytes_start)*1000/elapsed); if (rules.bDebugLog) { rules.log.log( dl.getTorrent(), LoggerChannel.LT_INFORMATION, "download speed test ends - average=" + dlr_test_average_bytes_per_sec ); } } dlr_test_active = false; } public long getDLRLastTestTime() { return( dlr_test_start_time ); } public int getDLRLastTestSpeed() { return( dlr_test_average_bytes_per_sec ); } public String getDLRTrace() { if ( dlr_test_active ){ return( "test in progress" ); }else if ( dlr_test_start_time > 0 ){ if ( dlr_test_average_bytes_per_sec >= 0 ){ return( "tested; " + TimeFormatter.format(( SystemTime.getMonotonousTime() - dlr_test_start_time )/1000) + " ago; " + "rate=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( dlr_test_average_bytes_per_sec )); }else{ return( "tested; " + TimeFormatter.format(( SystemTime.getMonotonousTime() - dlr_test_start_time )/1000) + " ago; " + "test did not complete" ); } }else{ return( "" ); } } public String toString() { return String.valueOf(dl.getSeedingRank()); } /** * Check Seeders for various changes not triggered by listeners * * @return True: something changed */ public boolean changeChecker() { if (getActivelySeeding()) { int shareRatio = dl.getStats().getShareRatio(); int numSeeds = rules.calcSeedsNoUs(dl,dl.getAggregatedScrapeResult()); int activeMaxSR = dlSpecificMaxShareRatio; if ( activeMaxSR <= 0 ){ activeMaxSR = iIgnoreShareRatio; } if (activeMaxSR != 0 && shareRatio >= activeMaxSR && (numSeeds >= iIgnoreShareRatio_SeedStart || !lastScrapeResultOk) && shareRatio != -1) { if (rules.bDebugLog) { rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: shareRatio changeChecker"); } return true; } } /* READY downloads are usually waiting for a seeding torrent to stop (the seeding torrent probably is within the "Minimum Seeding Time" setting) The rules may go through several cycles before a READY torrent is processed */ if (dl.getState() == Download.ST_READY) { if (rules.bDebugLog) rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: Download is ready"); return true; } if (staleCDSince > 0) { long now = SystemTime.getCurrentTime(); if (now - lastStaleCDRefresh > STALE_REFRESH_INTERVAL) { staleCDOffset += (now - lastStaleCDRefresh) / STALE_REFRESH_INTERVAL; lastStaleCDRefresh = now; if (rules.bDebugLog) { rules.log.log(dl.getTorrent(), LoggerChannel.LT_INFORMATION, "somethingChanged: staleCD changeChecker"); } return true; } } return false; } }