FileDocCategorySizeDatePackage
StartStopRulesDefaultPlugin.javaAPI DocAzureus 3.0.3.463863Tue Sep 04 11:13:08 BST 2007com.aelitis.azureus.plugins.startstoprules.defaultplugin

StartStopRulesDefaultPlugin

public class StartStopRulesDefaultPlugin extends Object implements org.gudy.azureus2.core3.config.COConfigurationListener, AEDiagnosticsEvidenceGenerator, Plugin
Handles Starting and Stopping of torrents. TODO: RANK_TIMED is quite a hack and is spread all over. It needs to be redone, probably with a timer on each seeding torrent which triggers when time is up and it needs to stop. BUG: When "AutoStart 0 Peers" is on, and minSpeedForActivelySeeding is enabled, the 0 peer torrents will continuously switch from seeding to queued, probably due to the connection attempt registering speed. This might be fixed by the "wait XX ms before switching active state" code. Other Notes: "CD" is often used to refer to "Seed" or "Seeding", because "C" sounds like "See"

Fields Summary
private static final String
sStates
public static final int
RANK_NONE
Do not rank completed torrents
public static final int
RANK_SPRATIO
Rank completed torrents using Seeds:Peer Ratio
public static final int
RANK_SEEDCOUNT
Rank completed torrents using Seed Count method
public static final int
RANK_TIMED
Rank completed torrents using a timed rotation of minTimeAlive
private static final int
FORCE_CHECK_PERIOD
Force at least one check every period of time (in ms). Used in ChangeFlagCheckerTask
private static final int
CHECK_FOR_GROSS_CHANGE_PERIOD
Check for non triggerable changes ever period of time (in ms)
private static final int
PROCESS_CHECK_PERIOD
Interval in ms between checks to see if the {@link #somethingChanged} flag changed
private static final int
MIN_SEEDING_STARTUP_WAIT
Wait xx ms before starting completed torrents (so scrapes can come in)
private static final int
MIN_FIRST_SCRAPE_WAIT
Wait at least xx ms for first scrape, before starting completed torrents
private AEMonitor
this_mon
private PluginInterface
pi
protected PluginConfig
plugin_config
private DownloadManager
download_manager
protected org.gudy.azureus2.plugins.logging.LoggerChannel
log
private RecalcSeedingRanksTask
recalcSeedingRanksTask
Used only for RANK_TIMED. Recalculate ranks on a timer
private static Map
downloadDataMap
Map to relate downloadData to a Download
private volatile DefaultRankCalculator[]
sortedArrayCache
this is used to reduce the number of comperator invocations by keeping a mostly sorted copy around, must be nulled whenever the map is changed
private volatile boolean
closingDown
private volatile boolean
somethingChanged
private Set
ranksToRecalc
private long
startedOn
When rules class started. Used for initial waiting logic
protected boolean
bDebugLog
Whether Debug Info is written to the log and tooltip
private int
iRankType
Ranking System to use. One of RANK_* constants
private int
minSpeedForActiveSeeding
private int
numPeersAsFullCopy
private int
iFakeFullCopySeedStart
private int
_maxActive
private boolean
_maxActiveWhenSeedingEnabled
private int
_maxActiveWhenSeeding
private int
maxDownloads
private int
minDownloads
private boolean
bAutoReposition
private long
minTimeAlive
private boolean
bAutoStart0Peers
private int
iMaxUploadSpeed
private static boolean
bAlreadyInitialized
private TableColumn
seedingRankColumn
private TableContextMenuItem
debugMenuItem
private boolean
bSWTUI
private com.aelitis.azureus.core.util.CopyOnWriteList
listenersFP
private long
changeCheckCount
private long
changeCheckTotalMS
private long
changeCheckMaxMS
private long
processCount
private long
processTotalMS
private long
processMaxMS
private long
processLastComplete
private long
processTotalGap
private long
processTotalRecalcs
private long
processTotalZeroRecalcs
private long
processMergeCount
Request that the startstop rules process. Used when it's known that something has changed that will effect torrent's state/position/rank.
Constructors Summary
Methods Summary
public voidaddListener(StartStopRulesFPListener listener)

		listenersFP.add(listener);
	
private java.lang.StringboolDebug(boolean b)

		return b ? "Y" : "N";
	
private intcalcMaxSeeders(int iDLs)

		// XXX put in subtraction logic here
		int maxActive = getMaxActive();
		if (maxActive == 0) {
			return 999999;
		}
		return maxActive - iDLs;
	
public intcalcPeersNoUs(Download download)
Get # of peers not including us

		int numPeers = 0;
		DownloadScrapeResult sr = download.getLastScrapeResult();
		if (sr.getScrapeStartTime() > 0) {
			numPeers = sr.getNonSeedCount();
			// If we've scraped after we started downloading
			// Remove ourselves from count
			if ((numPeers > 0) && (download.getState() == Download.ST_DOWNLOADING)
					&& (sr.getScrapeStartTime() > download.getStats().getTimeStarted()))
				numPeers--;
		}
		if (numPeers == 0) {
			// Fallback to the # of peers we know of
			DownloadAnnounceResult ar = download.getLastAnnounceResult();
			if (ar != null
					&& ar.getResponseType() == DownloadAnnounceResult.RT_SUCCESS)
				numPeers = ar.getNonSeedCount();

			if (numPeers == 0) {
				DownloadActivationEvent activationState = download.getActivationState();
				if (activationState != null) {
					numPeers = activationState.getActivationCount();
				}
			}
		}
		return numPeers;
	
public intcalcSeedsNoUs(Download download)
Get # of seeds, not including us, AND including fake full copies

param
download Download to get # of seeds for
return
seed count

		return calcSeedsNoUs(download, calcPeersNoUs(download));
	
public intcalcSeedsNoUs(Download download, int numPeers)
Get # of seeds, not including us, AND including fake full copies

param
download Download to get # of seeds for
param
numPeers # peers we know of, required to calculate Fake Full Copies
return
seed count

		int numSeeds = 0;
		DownloadScrapeResult sr = download.getLastScrapeResult();
		if (sr.getScrapeStartTime() > 0) {
			long seedingStartedOn = download.getStats().getTimeStartedSeeding();
			numSeeds = sr.getSeedCount();
			// If we've scraped after we started seeding
			// Remove ourselves from count
			if ((numSeeds > 0) && (seedingStartedOn > 0)
					&& (download.getState() == Download.ST_SEEDING)
					&& (sr.getScrapeStartTime() > seedingStartedOn))
				numSeeds--;
		}
		if (numSeeds == 0) {
			// Fallback to the # of seeds we know of
			DownloadAnnounceResult ar = download.getLastAnnounceResult();
			if (ar != null
					&& ar.getResponseType() == DownloadAnnounceResult.RT_SUCCESS)
				numSeeds = ar.getSeedCount();
		}

		if (numPeersAsFullCopy != 0 && numSeeds >= iFakeFullCopySeedStart)
			numSeeds += numPeers / numPeersAsFullCopy;

		return numSeeds;
	
public voidconfigurationSaved()

		reloadConfigParams();
	
public voidgenerate(IndentWriter writer)

		writer.println("StartStopRules Manager");

		try {
			writer.indent();
			writer.println("Started " + (SystemTime.getCurrentTime() - startedOn)
					+ "ms ago");
			writer.println("downloadDataMap size = " + downloadDataMap.size());
			if (changeCheckCount > 0) {
				writer.println("changeCheck CPU ms: avg="
						+ (changeCheckTotalMS / changeCheckCount) + "; max = "
						+ changeCheckMaxMS);
			}

			if (processCount > 0) {
				writer.println("# process cycles: " + processCount);

				writer.println("process CPU ms: avg=" + (processTotalMS / processCount)
						+ "; max = " + processMaxMS);
				if (processCount > 1) {
					writer.println("process avg gap: "
							+ (processTotalGap / ((processCount - 1))) + "ms");
				}
				writer.println("Avg # recalcs per process cycle: "
						+ (processTotalRecalcs / processCount));
				if (processTotalZeroRecalcs > 0) {
					writer.println("# process cycle with 0 recalcs: "
							+ processTotalZeroRecalcs);
				}
			}

		} catch (Exception e) {
			// ignore
		} finally {
			writer.exdent();
		}
	
public java.util.ListgetFPListeners()

		return listenersFP.getList();
	
protected intgetMaxActive()

		if (!_maxActiveWhenSeedingEnabled)
			return (_maxActive);

		if (download_manager.isSeedingOnly()) {

			if (_maxActiveWhenSeeding <= _maxActive)
				return (_maxActiveWhenSeeding);

			// danger here if we are in a position where allowing more to start when seeding
			// allows a non-seeding download to start (looping occurs)

			Download[] downloads = download_manager.getDownloads();

			boolean danger = false;

			for (int i = 0; i < downloads.length && !danger; i++) {

				Download download = downloads[i];

				int state = download.getState();

				if (state == Download.ST_DOWNLOADING || state == Download.ST_SEEDING
						|| state == Download.ST_STOPPED || state == Download.ST_STOPPING
						|| state == Download.ST_ERROR) {

					// not interesting, they can't potentially cause trouble

				} else {

					// look for incomplete files

					DiskManagerFileInfo[] files = download.getDiskManagerFileInfo();

					for (int j = 0; j < files.length; j++) {

						DiskManagerFileInfo file = files[j];

						if ((!file.isSkipped()) && file.getDownloaded() != file.getLength()) {

							danger = true;

							break;
						}
					}
				}
			}

			if (!danger)
				return (_maxActiveWhenSeeding);
		}

		return (_maxActive);
	
public static DefaultRankCalculatorgetRankCalculator(Download dl)

		return (DefaultRankCalculator) downloadDataMap.get(dl);
	
private voidhandleCompletedDownload(DefaultRankCalculator[] dlDataArray, DefaultRankCalculator dlData, com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin$ProcessVars vars, com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin$TotalsStats totals)
Process Completed (Seeding) downloads, starting and stopping as needed

param
dlDataArray All download data (rank objects) we handle
param
dlData Current download data (rank object) we are processing
param
vars Running calculations
param
totals Summary values used in logic

		if (!totals.bOkToStartSeeding)
			return;

		Download download = dlData.dl;
		int state = download.getState();

		String[] debugEntries = null;
		String sDebugLine = "";
		// Queuing process:
		// 1) Torrent is Queued (Stopped)
		// 2) Slot becomes available
		// 3) Queued Torrent changes to Waiting
		// 4) Waiting Torrent changes to Ready
		// 5) Ready torrent changes to Seeding (with startDownload)
		// 6) Trigger stops Seeding torrent
		//    a) Queue Ranking drops
		//    b) User pressed stop
		//    c) other
		// 7) Seeding Torrent changes to Queued.  Go to step 1.

		int numPeers = dlData.lastModifiedScrapeResultPeers;
		boolean isFP = false;

		if (bDebugLog) {
			isFP = dlData.isFirstPriority();
			debugEntries = new String[] {
				"CD state=" + sStates.charAt(state),
				"shareR=" + download.getStats().getShareRatio(),
				"nWorCDing=" + vars.numWaitingOrSeeding,
				"nWorDLing=" + vars.numWaitingOrDLing,
				"sr=" + download.getSeedingRank(),
				"hgherQd=" + boolDebug(vars.higherCDtoStart),
				"maxCDrs=" + totals.maxSeeders,
				"FP=" + boolDebug(isFP),
				"nActCDing=" + totals.activelyCDing,
				"ActCDing=" + boolDebug(dlData.getActivelySeeding())
			};
		}

		try {
			boolean bScrapeOk = dlData.lastScrapeResultOk;

			// Ignore rules and other auto-starting rules do not apply when 
			// bAutoStart0Peers and peers == 0. So, handle starting 0 peers 
			// right at the beginning, and loop early
			if (bAutoStart0Peers && numPeers == 0 && bScrapeOk) {
				if (state == Download.ST_QUEUED) {
					try {
						if (bDebugLog)
							sDebugLine += "\nrestart() 0Peers";
						download.restart(); // set to Waiting
						totals.waitingToSeed++;
						vars.numWaitingOrSeeding++;

						state = download.getState();
						if (state == Download.ST_READY) {
							if (bDebugLog)
								sDebugLine += "\nstart(); 0Peers";
							download.start();
							totals.activelyCDing++;
						}
					} catch (Exception ignore) {/*ignore*/
					}
				}
				if (state == Download.ST_READY) {
					try {
						if (bDebugLog)
							sDebugLine += "\nstart(); 0Peers";
						download.start();
						totals.activelyCDing++;
						vars.numWaitingOrSeeding++;
					} catch (Exception ignore) {/*ignore*/
					}
				}
				return;
			}

			if (bDebugLog && bAutoStart0Peers && numPeers == 0 && !bScrapeOk
					&& (state == Download.ST_QUEUED || state == Download.ST_READY)) {
				sDebugLine += "\n  NOT starting 0 Peer torrent because scrape isn't ok";
			}

			if (!bDebugLog) {
				// In debug mode, we already calculated FP
				isFP = dlData.isFirstPriority();
			}

			boolean bActivelySeeding = dlData.getActivelySeeding();
			boolean okToQueue = (state == Download.ST_READY || state == Download.ST_SEEDING)
					&& (!isFP || (isFP && ((totals.maxActive != 0 && vars.numWaitingOrSeeding >= totals.maxActive
							- minDownloads))))
					//&& (!isFP || (isFP && ((vars.numWaitingOrSeeding >= totals.maxSeeders) || (!bActivelySeeding && (vars.numWaitingOrSeeding + totals.totalStalledSeeders) >= totals.maxSeeders))) )
					&& (!download.isForceStart());
			int rank = download.getSeedingRank();

			// in RANK_TIMED mode, we use minTimeAlive for rotation time, so
			// skip check
			// XXX do we want changes to take effect immediately  ?
			if (okToQueue && (state == Download.ST_SEEDING)
					&& iRankType != RANK_TIMED) {
				long timeAlive = (SystemTime.getCurrentTime() - download.getStats().getTimeStarted());
				okToQueue = (timeAlive >= minTimeAlive);

				if (!okToQueue && bDebugLog)
					sDebugLine += "\n  Torrent can't be stopped yet, timeAlive("
							+ timeAlive + ") < minTimeAlive(" + minTimeAlive + ")";
			}

			if (state != Download.ST_QUEUED && // Short circuit.
					(state == Download.ST_READY || state == Download.ST_WAITING
							|| state == Download.ST_PREPARING ||
					// Forced Start torrents are pre-included in count
					(state == Download.ST_SEEDING && bActivelySeeding && !download.isForceStart()))) {
				vars.numWaitingOrSeeding++;
				if (bDebugLog)
					sDebugLine += "\n  Torrent is waiting or seeding";
			}

			// Note: First Priority are sorted to the top, 
			//       so they will always start first

			// XXX Change to waiting if queued and we have an open slot
			if (!okToQueue
					&& (state == Download.ST_QUEUED)
					&& (totals.maxActive == 0 || vars.numWaitingOrSeeding < totals.maxSeeders)
					//&& (totals.maxActive == 0 || (activeSeedingCount + activeDLCount) < totals.maxActive) &&
					&& (rank >= DefaultRankCalculator.SR_IGNORED_LESS_THAN)
					&& !vars.higherCDtoStart) {
				try {
					if (bDebugLog)
						sDebugLine += "\n  restart: ok2Q=" + okToQueue
								+ "; QUEUED && numWaitingOrSeeding( "
								+ vars.numWaitingOrSeeding + ") < maxSeeders ("
								+ totals.maxSeeders + ")";

					download.restart(); // set to Waiting
					okToQueue = false;
					totals.waitingToSeed++;
					vars.numWaitingOrSeeding++;
					if (iRankType == RANK_TIMED)
						dlData.recalcSeedingRank();
				} catch (Exception ignore) {/*ignore*/
				}
				state = download.getState();
			} else if (bDebugLog && state == Download.ST_QUEUED) {
				sDebugLine += "\n  NOT restarting:";
				if (rank < DefaultRankCalculator.SR_IGNORED_LESS_THAN)
					sDebugLine += " torrent is being ignored";
				else if (vars.higherCDtoStart)
					sDebugLine += " a torrent with a higher rank is queued or starting";
				else {
					if (okToQueue)
						sDebugLine += " no starting of okToQueue'd;";

					if (vars.numWaitingOrSeeding >= totals.maxSeeders)
						sDebugLine += " at limit, numWaitingOrSeeding("
								+ vars.numWaitingOrSeeding + ") >= maxSeeders("
								+ totals.maxSeeders + ")";
				}
			}

			boolean bForceStop = false;
			// Start download if ready and slot is available
			if (state == Download.ST_READY
					&& totals.activelyCDing < totals.maxSeeders) {

				if (rank >= DefaultRankCalculator.SR_IGNORED_LESS_THAN
						|| download.isForceStart()) {
					try {
						if (bDebugLog)
							sDebugLine += "\n  start: READY && total activelyCDing("
									+ totals.activelyCDing + ") < maxSeeders("
									+ totals.maxSeeders + ")";

						download.start();
						okToQueue = false;
					} catch (Exception ignore) {
						/*ignore*/
					}
					state = download.getState();
					totals.activelyCDing++;
					bActivelySeeding = true;
					vars.numWaitingOrSeeding++;
				} else if (okToQueue) {
					// In between switching from STATE_WAITING and STATE_READY,
					// and ignore rule was met, so move it back to Queued
					bForceStop = true;
				}
			}

			// if there's more torrents waiting/seeding than our max, or if
			// there's a higher ranked torrent queued, stop this one
			if (okToQueue || bForceStop) {

				boolean okToStop = bForceStop;
				if (!okToStop) {
					// break up the logic into variables to make more readable
					boolean bOverLimit = vars.numWaitingOrSeeding > totals.maxSeeders
							|| (vars.numWaitingOrSeeding >= totals.maxSeeders && vars.higherCDtoStart);
					boolean bSeeding = state == Download.ST_SEEDING;

					// not checking AND (at limit of seeders OR rank is set to ignore) AND
					// (Actively Seeding OR StartingUp OR Seeding a non-active download) 
					okToStop = !download.isChecking()
							&& (bOverLimit || rank < DefaultRankCalculator.SR_IGNORED_LESS_THAN)
							&& (bActivelySeeding || !bSeeding || (!bActivelySeeding && bSeeding));

					if (bDebugLog) {
						if (okToStop) {
							sDebugLine += "\n  stopAndQueue: ";
							if (bOverLimit) {
								if (vars.higherCDtoStart)
									sDebugLine += "higherQueued (it should be seeding instead of this one)";
								else
									sDebugLine += "over limit";
							} else if (rank < DefaultRankCalculator.SR_IGNORED_LESS_THAN)
								sDebugLine += "ignoreRule met";

							sDebugLine += " && ";
							if (bActivelySeeding)
								sDebugLine += "activelySeeding";
							else if (!bSeeding)
								sDebugLine += "not SEEDING";
							else if (!bActivelySeeding && bSeeding)
								sDebugLine += "SEEDING, but not actively";
						}
					} else {
						sDebugLine += "\n  NOT queuing: ";
						if (download.isChecking())
							sDebugLine += "can't auto-queue a checking torrent";
						else if (!bOverLimit)
							sDebugLine += "not over limit.  numWaitingOrSeeding("
									+ vars.numWaitingOrSeeding + ") <= maxSeeders("
									+ totals.maxSeeders + ")";
						else
							sDebugLine += "bActivelySeeding=" + bActivelySeeding
									+ ";bSeeding" + bSeeding;
					}
				} else {
					if (bDebugLog)
						sDebugLine += "\n  Forcing a stop..";
				}

				if (okToStop) {
					try {
						if (state == Download.ST_READY)
							totals.waitingToSeed--;

						download.stopAndQueue();
						vars.bStopAndQueued = true;
						// okToQueue only allows READY and SEEDING state.. and in both cases
						// we have to reduce counts
						if (bActivelySeeding) {
							totals.activelyCDing--;
							bActivelySeeding = false;
							vars.numWaitingOrSeeding--;
						}
						// force stop allows READY states in here, so adjust counts 
						if (state == Download.ST_READY)
							totals.waitingToSeed--;
					} catch (Exception ignore) {
						/*ignore*/
					}

					state = download.getState();
				}
			}

			// move completed timed rank types to bottom of the list
			if (vars.bStopAndQueued && iRankType == RANK_TIMED) {
				for (int j = 0; j < dlDataArray.length; j++) {
					Download dl = dlDataArray[j].getDownloadObject();
					int sr = dl.getSeedingRank();
					if (sr > 0 && sr < DefaultRankCalculator.SR_TIMED_QUEUED_ENDS_AT) {
						// Move everyone up
						// We always start by setting SR to SR_TIMED_QUEUED_ENDS_AT - position
						// then, the torrent with the biggest starts seeding which is
						// (SR_TIMED_QUEUED_ENDS_AT - 1), leaving a gap.
						// when it's time to stop the torrent, move everyone up, and put 
						// us at the end
						dl.setSeedingRank(sr + 1);
					}
				}
				rank = DefaultRankCalculator.SR_TIMED_QUEUED_ENDS_AT - totals.complete;
				download.setSeedingRank(rank);
			}

			state = download.getState();
			if (rank >= 0
					&& (state == Download.ST_QUEUED || state == Download.ST_READY
							|| state == Download.ST_WAITING || state == Download.ST_PREPARING)) {
				vars.higherCDtoStart = true;
			}

		} finally {
			if (bDebugLog) {
				String[] debugEntries2 = new String[] {
					"CD state=" + sStates.charAt(download.getState()),
					"shareR=" + download.getStats().getShareRatio(),
					"nWorCDing=" + vars.numWaitingOrSeeding,
					"nWorDLing=" + vars.numWaitingOrDLing,
					"sr=" + download.getSeedingRank(),
					"hgherQd=" + boolDebug(vars.higherCDtoStart),
					"maxCDrs=" + totals.maxSeeders,
					"FP=" + boolDebug(isFP),
					"nActCDing=" + totals.activelyCDing,
					"ActCDing=" + boolDebug(dlData.getActivelySeeding())
				};
				printDebugChanges("", debugEntries, debugEntries2, sDebugLine, "  ",
						true, dlData);
			}
		}
	
private voidhandleInCompleteDownload(DefaultRankCalculator dlData, com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin$ProcessVars vars, com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin$TotalsStats totals)

param
dlData
param
vars
param
totals

		Download download = dlData.dl;
		int state = download.getState();

		if (download.isForceStart()) {
			if (bDebugLog) {
				String s = "isForceStart.. rules skipped";
				log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
				dlData.sTrace += s + "\n";
			}
			return;
		}

		// Don't mess with preparing torrents.  they could be in the 
		// middle of resume-data building, or file allocating.
		if (state == Download.ST_PREPARING) {
			vars.numWaitingOrDLing++;
			if (bDebugLog) {
				String s = "ST_PREPARING.. rules skipped. numW8tngorDLing="
						+ vars.numWaitingOrDLing;
				log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
				dlData.sTrace += s + "\n";
			}
			return;
		}

		int maxDLs = 0;
		if (totals.maxActive == 0) {
			maxDLs = maxDownloads;
		} else {
			int DLmax = 0;
			DLmax = totals.stalledFPSeeders + totals.forcedActive + totals.maxActive
					- totals.firstPriority - totals.forcedSeedingNonFP;
			maxDLs = (DLmax <= 0) ? 0 : maxDownloads - DLmax <= 0 ? maxDownloads
					: DLmax;
		}

		if (maxDLs < minDownloads) {
			maxDLs = minDownloads;
		}

		if (bDebugLog) {
			String s = ">> DL state=" + sStates.charAt(download.getState())
					+ ";shareRatio=" + download.getStats().getShareRatio()
					+ ";numW8tngorDLing=" + vars.numWaitingOrDLing + ";maxCDrs="
					+ totals.maxSeeders + ";forced=" + boolDebug(download.isForceStart())
					+ ";actvDLs=" + totals.activelyDLing + ";maxDLs=" + maxDLs
					+ ";ActDLing=" + boolDebug(dlData.getActivelyDownloading())
					+ ";hgherQd=" + boolDebug(vars.higherDLtoStart) + ";isCmplt="
					+ boolDebug(download.isComplete());
			log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
			dlData.sTrace += s + "\n";
		}

		// must use fresh getActivelyDownloading() in case state changed to 
		// downloading
		if ((state == Download.ST_DOWNLOADING && dlData.getActivelyDownloading())
				|| state == Download.ST_READY || state == Download.ST_WAITING
				|| state == Download.ST_PREPARING) {
			vars.numWaitingOrDLing++;
		}

		if (state == Download.ST_READY || state == Download.ST_DOWNLOADING
				|| state == Download.ST_WAITING) {

			boolean bActivelyDownloading = dlData.getActivelyDownloading();

			// Stop torrent if over limit
			boolean bOverLimit = vars.numWaitingOrDLing > maxDLs
					|| (vars.numWaitingOrDLing >= maxDLs && vars.higherDLtoStart);

			boolean bDownloading = state == Download.ST_DOWNLOADING;

			if ((maxDownloads != 0)
					&& bOverLimit
					&& (bActivelyDownloading || !bDownloading || (bDownloading
							&& totals.maxActive != 0 && !bActivelyDownloading && totals.activelyCDing
							+ totals.activelyDLing >= totals.maxActive))) {
				try {
					if (bDebugLog) {
						String s = "   stopAndQueue: " + vars.numWaitingOrDLing
								+ " waiting or downloading, when limit is " + maxDLs + "("
								+ maxDownloads + ")";
						if (vars.higherDLtoStart) {
							s += " and higher DL is starting";
						}
						log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
						dlData.sTrace += s + "\n";
					}
					download.stopAndQueue();

					// reduce counts
					vars.numWaitingOrDLing--;
					if (state == Download.ST_DOWNLOADING) {
						totals.downloading--;
						if (bActivelyDownloading)
							totals.activelyDLing--;
					} else {
						totals.waitingToDL--;
					}
					totals.maxSeeders = calcMaxSeeders(totals.activelyDLing
							+ totals.waitingToDL);
				} catch (Exception ignore) {
					/*ignore*/
				}

				state = download.getState();

			} else if (bDebugLog) {
				String s = "NOT queuing: ";
				if (maxDownloads == 0) {
					s += "maxDownloads = " + maxDownloads;
				} else if (!bOverLimit) {
					s += "not over limit.  numWaitingOrDLing(" + vars.numWaitingOrDLing
							+ ") <= maxDLs(" + maxDLs + ")";
				} else if (!bActivelyDownloading || bDownloading) {
					s += "not actively downloading";
				} else if (totals.maxActive == 0) {
					s += "unlimited active allowed (set)";
				} else {
					s += "# active(" + (totals.activelyCDing + totals.activelyDLing)
							+ ") < maxActive(" + totals.maxActive + ")";
				}
				log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
				dlData.sTrace += s + "\n";
			}
		}

		if (state == Download.ST_READY) {
			if ((maxDownloads == 0) || (totals.activelyDLing < maxDLs)) {
				try {
					if (bDebugLog) {
						String s = "   start: READY && activelyDLing ("
								+ totals.activelyDLing + ") < maxDLs (" + maxDownloads + ")";
						log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
						dlData.sTrace += s + "\n";
					}
					download.start();

					// adjust counts
					totals.waitingToDL--;
					totals.activelyDLing++;
					totals.maxSeeders = calcMaxSeeders(totals.activelyDLing
							+ totals.waitingToDL);
				} catch (Exception ignore) {
					/*ignore*/
				}
				state = download.getState();
			}
		}

		if (state == Download.ST_QUEUED) {
			if ((maxDownloads == 0) || (vars.numWaitingOrDLing < maxDLs)) {
				try {
					if (bDebugLog) {
						String s = "   restart: QUEUED && numWaitingOrDLing ("
								+ vars.numWaitingOrDLing + ") < maxDLS (" + maxDLs + ")";
						log.log(LoggerChannel.LT_INFORMATION, s);
						dlData.sTrace += s + "\n";
					}
					download.restart();

					// increase counts
					vars.numWaitingOrDLing++;
					totals.waitingToDL++;
					totals.maxSeeders = calcMaxSeeders(totals.activelyDLing
							+ totals.waitingToDL);
				} catch (Exception ignore) {/*ignore*/
				}
				state = download.getState();
			}
		}

		state = download.getState();
		if (download.getSeedingRank() >= 0
				&& (state == Download.ST_QUEUED || state == Download.ST_READY
						|| state == Download.ST_WAITING || state == Download.ST_PREPARING)) {
			vars.higherDLtoStart = true;
		}

		if (bDebugLog) {
			String s = "<< DL state=" + sStates.charAt(download.getState())
					+ ";shareRatio=" + download.getStats().getShareRatio()
					+ ";numW8tngorDLing=" + vars.numWaitingOrDLing + ";maxCDrs="
					+ totals.maxSeeders + ";forced=" + boolDebug(download.isForceStart())
					+ ";actvDLs=" + totals.activelyDLing + ";hgherQd="
					+ boolDebug(vars.higherDLtoStart) + ";ActDLing="
					+ boolDebug(dlData.getActivelyDownloading());
			log.log(download.getTorrent(), LoggerChannel.LT_INFORMATION, s);
			dlData.sTrace += s + "\n";
		}
	
public voidinitialize(PluginInterface _plugin_interface)

	
	    
		if (bAlreadyInitialized) {
			System.err.println("StartStopRulesDefaultPlugin Already initialized!!");
		} else {
			bAlreadyInitialized = true;
		}

		AEDiagnostics.addEvidenceGenerator(this);

		startedOn = SystemTime.getCurrentTime();

		pi = _plugin_interface;
		download_manager = pi.getDownloadManager();

		pi.getPluginProperties().setProperty("plugin.version", "1.0");
		pi.getPluginProperties().setProperty("plugin.name", "Start/Stop Rules");
		pi.getPluginconfig().setPluginConfigKeyPrefix("");

		// Create a configModel for StartStopRules
		// We always need to do this in order to set up configuration defaults
		UIManager manager = pi.getUIManager();
		// TODO: don't name it Q
		final BasicPluginConfigModel configModel = manager.createBasicPluginConfigModel(
				ConfigSection.SECTION_ROOT, "Q");
		setupConfigModel(configModel);

		pi.addListener(new PluginListener() {
			public void initializationComplete() {
				// CPU Intensive, delay until a little after all plugin initializations
				// XXX Would be better if we could delay it until UI is done,
				//     but there may be no UI..
				new DelayedEvent("StartStop:initComp", 12000, new AERunnable() {
					public void runSupport() {
						download_manager.addListener(new StartStopDMListener());
						SimpleTimer.addPeriodicEvent("StartStop:gross",
								CHECK_FOR_GROSS_CHANGE_PERIOD, new ChangeCheckerTimerTask());
						SimpleTimer.addPeriodicEvent("StartStop:check",
								PROCESS_CHECK_PERIOD, new ChangeFlagCheckerTask());
					}
				});
			}

			public void closedownInitiated() {
				closingDown = true;

				// we don't want to go off recalculating stuff when config is saved
				// on closedown
				COConfigurationManager.removeListener(StartStopRulesDefaultPlugin.this);
			}

			public void closedownComplete() { /* not implemented */
			}
		});

		log = pi.getLogger().getChannel("StartStopRules");
		log.log(LoggerChannel.LT_INFORMATION,
				"Default StartStopRules Plugin Initialisation");

		COConfigurationManager.addListener(this);

		plugin_config = pi.getPluginconfig();

		try {
			pi.getUIManager().addUIListener(new UIManagerListener() {
				public void UIAttached(UIInstance instance) {
					TableManager tm = pi.getUIManager().getTableManager();
					seedingRankColumn = tm.createColumn(
							TableManager.TABLE_MYTORRENTS_COMPLETE, "SeedingRank");
					seedingRankColumn.initialize(TableColumn.ALIGN_TRAIL,
							TableColumn.POSITION_LAST, 80, TableColumn.INTERVAL_LIVE);

					SeedingRankColumnListener columnListener = new SeedingRankColumnListener(
							downloadDataMap, plugin_config);
					seedingRankColumn.addCellRefreshListener(columnListener);
					tm.addColumn(seedingRankColumn);

					if (instance instanceof UISWTInstance) {
						bSWTUI = true;
						// We have our own config model :)
						configModel.destroy();
						new StartStopRulesDefaultPluginSWTUI(pi);
					}
				}

				public void UIDetached(UIInstance instance) {

				}
			});
		} catch (Throwable e) {
			Debug.printStackTrace(e);
		}
		reloadConfigParams();
	
private voidprintDebugChanges(java.lang.String sPrefixFirstLine, java.lang.String[] oldEntries, java.lang.String[] newEntries, java.lang.String sDebugLine, java.lang.String sPrefix, boolean bAlwaysPrintNoChangeLine, DefaultRankCalculator dlData)

		boolean bAnyChanged = false;
		String sDebugLineNoChange = sPrefixFirstLine;
		String sDebugLineOld = "";
		String sDebugLineNew = "";
		for (int j = 0; j < oldEntries.length; j++) {
			if (oldEntries[j].equals(newEntries[j]))
				sDebugLineNoChange += oldEntries[j] + ";";
			else {
				sDebugLineOld += oldEntries[j] + ";";
				sDebugLineNew += newEntries[j] + ";";
				bAnyChanged = true;
			}
		}
		String sDebugLineOut = ((bAlwaysPrintNoChangeLine || bAnyChanged)
				? sDebugLineNoChange : "")
				+ (bAnyChanged ? "\nOld:" + sDebugLineOld + "\nNew:" + sDebugLineNew
						: "") + sDebugLine;
		if (!sDebugLineOut.equals("")) {
			String[] lines = sDebugLineOut.split("\n");
			for (int i = 0; i < lines.length; i++) {
				String s = sPrefix + ((i > 0) ? "  " : "") + lines[i];
				if (dlData == null) {
					log.log(LoggerChannel.LT_INFORMATION, s);
				} else {
					log.log(dlData.dl.getTorrent(), LoggerChannel.LT_INFORMATION, s);
					dlData.sTrace += s + "\n";
				}
			}
		}
	
protected voidprocess()


	   
		long now = 0;
		try {
			this_mon.enter();

			now = SystemTime.getCurrentTime();

			somethingChanged = false;
			Object[] recalcArray = ranksToRecalc.toArray();
			ranksToRecalc.clear();
			for (int i = 0; i < recalcArray.length; i++) {
				DefaultRankCalculator rankObj = (DefaultRankCalculator) recalcArray[i];
				if (bDebugLog) {
					long oldSR = rankObj.dl.getSeedingRank();
					rankObj.recalcSeedingRank();
					String s = "recalc seeding rank.  old/new=" + oldSR + "/"
							+ rankObj.dl.getSeedingRank();
					log.log(rankObj.dl.getTorrent(), LoggerChannel.LT_INFORMATION, s);
				} else {
					rankObj.recalcSeedingRank();
				}
			}
			processTotalRecalcs += recalcArray.length;
			if (recalcArray.length == 0) {
				processTotalZeroRecalcs++;
			}

			// pull the data into a local array, so we don't have to lock/synchronize
			DefaultRankCalculator[] dlDataArray;			
			if(sortedArrayCache != null && sortedArrayCache.length == downloadDataMap.size())
				dlDataArray = sortedArrayCache;
			else
				dlDataArray = sortedArrayCache = (DefaultRankCalculator[]) downloadDataMap.values().toArray(
					new DefaultRankCalculator[downloadDataMap.size()]);

			TotalsStats totals = new TotalsStats(dlDataArray);

			String[] mainDebugEntries = null;
			if (bDebugLog) {
				log.log(LoggerChannel.LT_INFORMATION, ">>process()");
				mainDebugEntries = new String[] {
					"ok2Start=" + boolDebug(totals.bOkToStartSeeding),
					"tFrcdCding=" + totals.forcedSeeding,
					"actvCDs=" + totals.activelyCDing,
					"tW8tingToCd=" + totals.waitingToSeed,
					"tDLing=" + totals.downloading,
					"actvDLs=" + totals.activelyDLing,
					"tW8tingToDL=" + totals.waitingToDL,
					"tCom=" + totals.complete,
					"tIncQd=" + totals.incompleteQueued,
					"mxCdrs=" + totals.maxSeeders,
					"tFP=" + totals.firstPriority,
					"maxT=" + totals.maxTorrents
				};
			}

			// Sort
			Arrays.sort(dlDataArray);

			ProcessVars vars = new ProcessVars();

			// pre-included Forced Start torrents so a torrent "above" it doesn't 
			// start (since normally it would start and assume the torrent below it
			// would stop)
			vars.numWaitingOrSeeding = totals.forcedSeeding; // Running Count
			vars.numWaitingOrDLing = 0; // Running Count
			vars.higherCDtoStart = false;
			vars.higherDLtoStart = false;
			vars.posComplete = 0;

			// Loop 2 of 2:
			// - Start/Stop torrents based on criteria

			for (int i = 0; i < dlDataArray.length; i++) {
				DefaultRankCalculator dlData = dlDataArray[i];
				Download download = dlData.getDownloadObject();
				vars.bStopAndQueued = false;
				dlData.sTrace = "";

				// Initialize STATE_WAITING torrents
				if ((download.getState() == Download.ST_WAITING)) {
					try {
						download.initialize();
					} catch (Exception ignore) {
						/*ignore*/
					}
					if (bDebugLog && download.getState() == Download.ST_WAITING) {
						dlData.sTrace += "still in waiting state after initialize!\n";
					}
				}

				if (bAutoReposition && (iRankType != RANK_NONE)
						&& download.isComplete()
						&& (totals.bOkToStartSeeding || totals.firstPriority > 0))
					download.setPosition(++vars.posComplete);

				int state = download.getState();

				// Never do anything to stopped entries
				if (state == Download.ST_STOPPING || state == Download.ST_STOPPED
						|| state == Download.ST_ERROR) {
					continue;
				}
				
				if (download.isForceStart()) {
					if (state == Download.ST_STOPPED || state == Download.ST_QUEUED) {
						try {
							download.restart();
							String s = "restart: isForceStart";
							log.log(LoggerChannel.LT_INFORMATION, s);
							dlData.sTrace += s + "\n";
						} catch (DownloadException e) {
						}

						state = download.getState();
					}

					if (state == Download.ST_READY) {
						try {
							download.start();
							String s = "Start: isForceStart";
							log.log(LoggerChannel.LT_INFORMATION, s);
							dlData.sTrace += s + "\n";
						} catch (DownloadException e) {
							/* ignore */
						}
					}
				}

				// Handle incomplete DLs
				if (!download.isComplete()) {
					handleInCompleteDownload(dlData, vars, totals);
				} else {
					handleCompletedDownload(dlDataArray, dlData, vars, totals);
				}
			} // Loop 2/2 (Start/Stopping)

			if (bDebugLog) {
				String[] mainDebugEntries2 = new String[] {
					"ok2Start=" + boolDebug(totals.bOkToStartSeeding),
					"tFrcdCding=" + totals.forcedSeeding,
					"actvCDs=" + totals.activelyCDing,
					"tW8tingToCd=" + totals.waitingToSeed,
					"tDLing=" + totals.downloading,
					"actvDLs=" + totals.activelyDLing,
					"tW8tingToDL=" + totals.waitingToDL,
					"tCom=" + totals.complete,
					"tIncQd=" + totals.incompleteQueued,
					"mxCdrs=" + totals.maxSeeders,
					"tFP=" + totals.firstPriority,
					"maxT=" + totals.maxTorrents
				};
				printDebugChanges("<<process() ", mainDebugEntries, mainDebugEntries2,
						"", "", true, null);
			}
		} finally {
			if (now > 0) {
				processCount++;
				long timeTaken = (SystemTime.getCurrentTime() - now);
				processTotalMS += timeTaken;
				if (timeTaken > processMaxMS) {
					processMaxMS = timeTaken;
				}
				if (processLastComplete > 0) {
					processTotalGap += (now - processLastComplete);
				}
				processLastComplete = now;
			}

			this_mon.exit();
		}
	
private voidrecalcAllSeedingRanks(boolean force)

		if (closingDown) {
			return;
		}

		try {
			this_mon.enter();

			DefaultRankCalculator[] dlDataArray = (DefaultRankCalculator[]) downloadDataMap.values().toArray(
					new DefaultRankCalculator[0]);

			// Check Group #1: Ones that always should run since they set things
			for (int i = 0; i < dlDataArray.length; i++) {
				if (force)
					dlDataArray[i].getDownloadObject().setSeedingRank(0);
				dlDataArray[i].recalcSeedingRank();
			}
		} finally {

			this_mon.exit();
		}
	
private voidreloadConfigParams()

		try {
			this_mon.enter();

			int iNewRankType = plugin_config.getIntParameter("StartStopManager_iRankType");
			minSpeedForActiveSeeding = plugin_config.getIntParameter("StartStopManager_iMinSpeedForActiveSeeding");
			_maxActive = plugin_config.getIntParameter("max active torrents");
			_maxActiveWhenSeedingEnabled = plugin_config.getBooleanParameter("StartStopManager_bMaxActiveTorrentsWhenSeedingEnabled");
			_maxActiveWhenSeeding = plugin_config.getIntParameter("StartStopManager_iMaxActiveTorrentsWhenSeeding");

			minDownloads = plugin_config.getIntParameter("min downloads");
			maxDownloads = plugin_config.getIntParameter("max downloads");
			numPeersAsFullCopy = plugin_config.getIntParameter("StartStopManager_iNumPeersAsFullCopy");
			iFakeFullCopySeedStart = plugin_config.getIntParameter("StartStopManager_iFakeFullCopySeedStart");
			bAutoReposition = plugin_config.getBooleanParameter("StartStopManager_bAutoReposition");
			minTimeAlive = plugin_config.getIntParameter("StartStopManager_iMinSeedingTime") * 1000;
			bDebugLog = plugin_config.getBooleanParameter("StartStopManager_bDebugLog");

			bAutoStart0Peers = plugin_config.getBooleanParameter("StartStopManager_bAutoStart0Peers");
			iMaxUploadSpeed = plugin_config.getIntParameter("Max Upload Speed KBs", 0);

			boolean move_top = plugin_config.getBooleanParameter("StartStopManager_bNewSeedsMoveTop");
			plugin_config.setBooleanParameter(
					PluginConfig.CORE_PARAM_BOOLEAN_NEW_SEEDS_START_AT_TOP, move_top);

			if (iNewRankType != iRankType) {
				iRankType = iNewRankType;

				// shorten recalc for timed rank type, since the calculation is fast and we want to stop on the second
				if (iRankType == RANK_TIMED) {
					if (recalcSeedingRanksTask == null) {
						recalcSeedingRanksTask = new RecalcSeedingRanksTask();
						SimpleTimer.addPeriodicEvent("StartStop:recalcSR", 1000,
								recalcSeedingRanksTask);
					}
				} else if (recalcSeedingRanksTask != null) {
					recalcSeedingRanksTask.cancel();
					recalcSeedingRanksTask = null;
				}
			}

			/*	    
			 // limit _maxActive and maxDownloads based on TheColonel's specs
			 
			 // maxActive = max_upload_speed / (slots_per_torrent * min_speed_per_peer)
			 if (_maxActive > 0) {
			 int iSlotsPerTorrent = plugin_config.getIntParameter("Max Uploads");
			 // TODO: Track upload speed, storing the max upload speed over a minute
			 //        and use that for "unlimited" setting, or huge settings (like 200)
			 if (iSlotsPerTorrent > 0) {
			 int iMinSpeedPerPeer = 3; // for now.  TODO: config value
			 int _maxActiveLimit = iMaxUploadSpeed / (iSlotsPerTorrent * iMinSpeedPerPeer);
			 if (_maxActive > _maxActiveLimit) {
			 _maxActive = _maxActiveLimit;
			 plugin_config.setIntParameter(PluginConfig.CORE_PARAM_INT_MAX_ACTIVE, _maxActive);
			 }
			 }

			 if (maxDownloads > _maxActive) {
			 maxDownloads = _maxActive;
			 plugin_config.setIntParameter(PluginConfig.CORE_PARAM_INT_MAX_DOWNLOADS, maxDownloads);
			 }
			 }
			 */

			// force a recalc on all downloads by setting SR to 0, scheduling
			// a recalc on next process, and requsting a process cycle
			Collection allDownloads = downloadDataMap.values();
			DefaultRankCalculator[] dlDataArray = (DefaultRankCalculator[]) allDownloads.toArray(new DefaultRankCalculator[0]);
			for (int i = 0; i < dlDataArray.length; i++) {
				dlDataArray[i].getDownloadObject().setSeedingRank(0);
			}
			ranksToRecalc.addAll(allDownloads);
			requestProcessCycle(null);

			if (bDebugLog) {
				log.log(LoggerChannel.LT_INFORMATION, "somethingChanged: config reload");
				try {
					if (debugMenuItem == null) {
						final String DEBUG_MENU_ID = "StartStopRules.menu.viewDebug";
						MenuItemListener menuListener = new MenuItemListener() {
							public void selected(MenuItem menu, Object target) {
								if (!(target instanceof TableRow))
									return;

								TableRow tr = (TableRow) target;
								Object ds = tr.getDataSource();

								if (!(ds instanceof Download))
									return;

								DefaultRankCalculator dlData = (DefaultRankCalculator) downloadDataMap.get(ds);

								if (dlData != null) {
									if (bSWTUI)
										StartStopRulesDefaultPluginSWTUI.openDebugWindow(dlData);
									else
										pi.getUIManager().showTextMessage(
												null,
												null,
												"FP:\n" + dlData.sExplainFP + "\n" + "SR:"
														+ dlData.sExplainSR + "\n" + "TRACE:\n"
														+ dlData.sTrace);
								}
							}
						};
						TableManager tm = pi.getUIManager().getTableManager();
						TableContextMenuItem menu;
						menu = tm.addContextMenuItem(
								TableManager.TABLE_MYTORRENTS_COMPLETE, DEBUG_MENU_ID);
						menu.addListener(menuListener);
						menu = tm.addContextMenuItem(
								TableManager.TABLE_MYTORRENTS_INCOMPLETE, DEBUG_MENU_ID);
						menu.addListener(menuListener);
					}
				} catch (Throwable t) {
					Debug.printStackTrace(t);
				}
			}

		} finally {
			this_mon.exit();
		}
	
public voidremoveListener(StartStopRulesFPListener listener)

		listenersFP.remove(listener);
	
public voidrequestProcessCycle(DefaultRankCalculator rankToRecalc)


	    
		if (rankToRecalc != null) {
			try {
				this_mon.enter();

				ranksToRecalc.add(rankToRecalc);
			} finally {
				this_mon.exit();
			}
		}

		if (somethingChanged) {
			processMergeCount++;
		} else {
			somethingChanged = true;
		}
	
private booleanscrapeResultOk(Download download)

		DownloadScrapeResult sr = download.getLastScrapeResult();
		return (sr.getResponseType() == DownloadScrapeResult.RT_SUCCESS);
	
private voidsetupConfigModel(org.gudy.azureus2.plugins.ui.model.BasicPluginConfigModel configModel)

param
configModel

		String PREFIX_RES = "ConfigView.label.seeding.";

		configModel.addIntParameter2(
				"StartStopManager_iRankType",
				"ConfigView.label.seeding.rankType",
				com.aelitis.azureus.plugins.startstoprules.defaultplugin.StartStopRulesDefaultPlugin.RANK_SPRATIO);
		configModel.addIntParameter2("StartStopManager_iRankTypeSeedFallback",
				"ConfigView.label.seeding.rankType.seed.fallback", 0);

		configModel.addBooleanParameter2("StartStopManager_bAutoReposition",
				"ConfigView.label.seeding.autoReposition", false);
		configModel.addIntParameter2("StartStopManager_iMinSeedingTime",
				"ConfigView.label.minSeedingTime", 60 * 3);

		// ignore rules subsection
		// ---------
		configModel.addBooleanParameter2("StartStopManager_bIgnore0Peers",
				"ConfigView.label.seeding.ignore0Peers", true);
		configModel.addIntParameter2("StartStopManager_iIgnoreSeedCount",
				"ConfigView.label.ignoreSeeds", 0);

		// for "Stop Peers Ratio" ignore rule
		configModel.addIntParameter2("StartStopManager_iIgnoreRatioPeersSeedStart",
				"ConfigView.label.seeding.fakeFullCopySeedStart", 0);

		// for "Stop Ratio" ignore rule
		configModel.addIntParameter2("StartStopManager_iIgnoreShareRatioSeedStart",
				"ConfigView.label.seeding.fakeFullCopySeedStart", 0);

		// Auto Starting
		// ---------
		configModel.addBooleanParameter2("StartStopManager_bPreferLargerSwarms",
				"ConfigView.label.seeding.preferLargerSwarms", true);
		configModel.addBooleanParameter2("StartStopManager_bAutoStart0Peers",
				"ConfigView.label.seeding.autoStart0Peers", false);
		configModel.addIntParameter2("StartStopManager_iMinPeersToBoostNoSeeds",
				"ConfigView.label.minPeersToBoostNoSeeds", 1);

		// queue section
		// ---------
		configModel.addIntParameter2("StartStopManager_iMinSpeedForActiveDL",
				"ConfigView.label.minSpeedForActiveDL", 512);
		configModel.addIntParameter2("StartStopManager_iMinSpeedForActiveSeeding",
				"ConfigView.label.minSpeedForActiveSeeding", 512);

		configModel.addBooleanParameter2("StartStopManager_bDebugLog",
				"ConfigView.label.queue.debuglog", false);
		configModel.addBooleanParameter2("StartStopManager_bNewSeedsMoveTop",
				"ConfigView.label.queue.newseedsmovetop", true);

		configModel.addIntParameter2(
				"StartStopManager_iMaxActiveTorrentsWhenSeeding",
				"ConfigView.label.queue.maxactivetorrentswhenseeding", 0);
		configModel.addBooleanParameter2(
				"StartStopManager_bMaxActiveTorrentsWhenSeedingEnabled",
				"ConfigView.label.queue.maxactivetorrentswhenseeding", false);

		// first Priority subsection
		// ---------
		configModel.addIntParameter2("StartStopManager_iFirstPriority_Type",
				"ConfigView.label.seeding.firstPriority",
				DefaultRankCalculator.FIRSTPRIORITY_ANY);

		configModel.addIntParameter2("StartStopManager_iFirstPriority_ShareRatio",
				"ConfigView.label.seeding.firstPriority.shareRatio", 500);

		configModel.addIntParameter2(
				"StartStopManager_iFirstPriority_SeedingMinutes",
				"ConfigView.label.seeding.firstPriority.seedingMinutes", 0);

		configModel.addIntParameter2("StartStopManager_iFirstPriority_DLMinutes",
				"ConfigView.label.seeding.firstPriority.DLMinutes", 0);

		// for ignore FP rules
		configModel.addIntParameter2(
				"StartStopManager_iFirstPriority_ignoreSPRatio",
				"ConfigView.label.seeding.firstPriority.ignoreSPRatio", 0);

		configModel.addBooleanParameter2(
				"StartStopManager_bFirstPriority_ignore0Peer",
				"ConfigView.label.seeding.firstPriority.ignore0Peer", false);

		// seeding subsection
		configModel.addIntParameter2("StartStopManager_iAddForSeedingDLCopyCount",
				"ConfigView.label.seeding.addForSeedingDLCopyCount", 1);
		configModel.addIntParameter2("StartStopManager_iNumPeersAsFullCopy",
				PREFIX_RES + "numPeersAsFullCopy", 0);
		configModel.addIntParameter2("StartStopManager_iFakeFullCopySeedStart",
				PREFIX_RES + "fakeFullCopySeedStart", 1);

		configModel.destroy();