- All Implemented Interfaces:
Runnable
This class manages tracker announcements and peer discovery by:
- Announcing torrent status to HTTP/HTTPS trackers
- Processing tracker responses and extracting peer information
- Managing UDP tracker communication
- Integrating with DHT for trackerless peer discovery
- Handling Peer Exchange (PEX) when available
- Managing announcement intervals and retry logic
- Processing various tracker events (started, completed, stopped)
Threading Model: The start() method creates a thread that runs one complete announcement cycle through all trackers, PEX, and DHT, then queues a new timed event and exits. This approach prevents thread accumulation while ensuring regular announcements.
The client can be restarted after halt(boolean) and handles various tracker responses including registration failures and error conditions.
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classDHT peer lookup and announce, run off the announce loop thread so a slow or unreachable DHT cannot delay HTTP tracker announces or PEX.private classQueues the next announce on the shared timer.private static classOne tracker's URL and per-tracker state.private classSend "stopped" to a single tracker.private static classUDP before HTTP.Field Summary
Modifier and TypeFieldDescriptionprivate intPeers seen in the last DHT fetch, updated by that thread.private ThreadThe running DHT fetch thread, kept for interrupts.private SimpleTimer2.TimedEventThe queued timer event, kept for cancellation.private booleanSet to unannounce from the trackers early.private booleanSet once the trackers are registered.private final LogLogger for this torrent's tracker traffic.private intNumber of loop runs so far.private longWhen this client was started.private ThreadThe running tracker thread, kept for interrupts.private final StringName prefix for this torrent's threads.Recent request times per tracker, for spacing requests.private CountDownLatchReleased when this client's last unannounce dispatch finishes; awaited by halt() callers.private static final ThreadPoolExecutorUnannounces are sent from a shared, bounded pool instead of one thread per tracker, so stopping thousands of torrents cannot exhaust threads.private final I2PSnarkUtilUtil providing the session and proxy settings.private final StringExtra tracker from the user's config, may be null.private static final SemaphoreGlobal cap on tracker HTTP requests in flight across ALL torrents, so simultaneous announce/scrape rounds cannot stampede a tracker (or our own netdb lookup path) when many TrackerClient threads wake together.private final List<TrackerClient.TCTracker> Backup trackers used when the others fail.private booleanWhether the torrent is complete, not necessarily seeding, as files may be skipped.private static final StringEvent sent when the download finishes.private intConsecutive failed requests in the loop.private final PeerCoordinatorCoordinator sharing peers with other torrents.private static final intUDP tracker port used when none is given.private static final intTracker requests are spaced at least 2 seconds apart.private static final intRandom delay added to the minimum, up to 6 seconds.private static final intRandom jitter added toMIN_DHT_ANNOUNCE_INTERVALso DHT announces from many torrents do not fire in lockstep; the effective threshold is 15m..19m.private static final intNo guidance in BEP 5; standard practice is K (=8).private static final intA DHT with fewer than this many known nodes is still bootstrapping and unreliable, so a seeding trackerless torrent falls back to its backup trackers to find peers.private static final StringError when the tracker returns an HTML page instead of data.private final StringHex info hash identifying the torrent.private static final intA failed registration is retried after 90 seconds.private longWhen the last DHT announce ran, written by the fetch thread.private booleanWhether the last response carried fresh metadata.private static final intSleep after lots of fails — shortened from 10m so post-recovery tracker retries fire within ~2m instead of waiting a full cycle.private static final intMax random spread added to the LONG_SLEEP failure floor so torrents that failed together don't re-announce in lockstep @since 0.9.71+.(package private) static final intFirst-announce delay for a torrent with no metainfo (a magnet, including every zzzot name lookup).private static final intTracker requests slow down after this many consecutive failures.private static final intPer-tracker throttle: max 20 requests per tracker host per minute.private static final intFailed registrations before giving up on a tracker.private static final intTrackers used per torrent at most.private MetaInfoThe torrent's metadata, reassigned by reinitialize.private static final longShortest interval between DHT tracker announces, in milliseconds.private static final longShortest interval between HTTP tracker announces, in milliseconds.private static final StringEvent value when no event is being reported.private static final StringTracker error for an unregistered torrent, from ByteMonsoon.private static final StringTracker error for an unregistered torrent, from DifTracker.private static final StringTracker error for an unregistered torrent, from Vuze.private static final StringEvent sent when the torrent is paused.private final StringRandom peer id sent to the trackers.private final intListen port advertised to the trackers.static final intDefault BitTorrent tracker port.private booleanSet while the loop body is running.private static final StringBEP 48 scrape path segment.private static final longAfter a scrape with a non-zero swarm size, defer the next scrape this long.private static final longPeriodic scrape interval: refresh the swarm size (seeds + leeches) between announces (BEP 15/48).private static final intRandom jitter added to each scheduled scrape so scrapes from many torrents to a shared tracker spread out instead of firing in lockstep.private static final intMinutes between tracker status checks.private final SnarkThe snark downloading this torrent.private booleanSet once the started event has been sent.private static final StringEvent sent when the torrent starts downloading.private booleanSet to stop the tracker loop.private static final StringEvent sent when the torrent stops.private static final longRequests to one tracker are spaced across a 1 minute window.private final List<TrackerClient.TCTracker> The active trackers for this torrent.private static final longAn unannounce thread exits after 1 minute idle.private static final longAn unannounce waits at most 10 seconds for a session.private static final longDispatch window is scaled to the batch, clamped between these.private static final intWhen the queue is this many waves deep, only the primary tracker per torrent gets a slot.private static final intUnannounce requests held before they are dropped.private static final intThreads draining the unannounce queue.private static final longOne wave of unannounces, latency-bound, through an established tunnel.Constructor Summary
ConstructorsConstructorDescriptionTrackerClient(I2PSnarkUtil util, MetaInfo meta, String additionalTrackerURL, PeerCoordinator coordinator, Snark snark) Call start() to start it.Method Summary
Modifier and TypeMethodDescriptionvoidawaitUnannounces(long ms) Wait, capped at ms, for the unannounces submitted by the last halt() to dispatch.private TrackerInfodoRequest(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) Announce - blocking, dispatches to HTTP or UDP by tracker type.private TrackerInfodoRequestHTTP(TrackerClient.TCTracker tr, String infoHash, String peerID, long uploaded, long downloaded, long left, int event) HTTP - blocking.private TrackerInfodoRequestUDP(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) UDP - blocking.private TrackerInfoScrape for swarm composition from a tracker, BEP 15 (UDP) or BEP 48 (HTTP).private TrackerInfodoScrapeHTTP(TrackerClient.TCTracker tr, String infoHash) Scrape for swarm composition from an HTTP tracker, BEP 48: the last path segment of the announce URL is replaced with "scrape" and any query string is dropped.private static StringeventToString(int event) The HTTP announce event name for a UDP-style event code.private voidStart a DHT peer fetch in a separate thread, if one is not already running.private HashgetHostHash(String ann) This also validates the URL.private intFetch peers from Peer Exchange (PEX).private intFetch peers from a list of trackers.private intValid web peers from the metainfo.voidhalt(boolean fast) Interrupts this Thread to stop it.booleanhalted()Check if this tracker client has been halted.(package private) static longinitialAnnounceDelayMs(boolean hasMeta, int fullSpread, int magnetSpread) Delay before a torrent's first announce.private static booleanWhether the key was already in the dictionary.private booleanisNewValidTracker(Set<Hash> existing, String ann) Whether the announce URL already exists in the list.(package private) static booleanisRegistrationFailure(String trackerProblem) Whether a tracker problem means this tracker will never accept the torrent, as opposed to a transient failure worth retrying.static booleanisValidAnnounce(String ann) Whether the announce URL is an I2P host.(package private) static longlongSleepWithJitter(long currentInterval, long randomJitter) Failure backoff for a tracker that just hit MAX_CONSEC_FAILS: floor the announce interval at LONG_SLEEP and add up to LONG_SLEEP_JITTER of randomness so many torrents failing together don't re-announce in lockstep every 2 minutes.private voidloop()Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event.(package private) static booleanneedBackupTrackers(boolean havePrimary, boolean haveBackup, boolean dhtEnabled, int dhtSize, boolean downloading) Whether a trackerless torrent should consult its backup trackers this round.(package private) static StringnormalizeTrackerProblem(String trackerProblem) Lower-cases a tracker problem message for prefix matching.private booleanprocessScrapeResponse(TrackerClient.TCTracker tr, TrackerInfo scrape) Handle a successful scrape response: refresh the swarm composition and clear the tracker's error indicator — the response proves the tracker is reachable, so a "No response from..." announce failure no longer applies.private voidqueueLoop(long delay) Queues the next loop run after the delay.voidCall after editing torrent.voidrun()Run the tracker client thread.private voidscheduleNextScrape(TrackerClient.TCTracker tr, boolean good) Schedule the next scrape for a tracker: the standard interval, or the longer deferral after a good response, plus random jitter so scrapes from many torrents to a shared tracker do not fire in lockstep.(package private) static StringBuild the BEP 48 scrape URL from the announce URL: drop any query string and replace the last path segment with "scrape".private voidsetup()Do this one time only (not every time it is started).(package private) static booleanshouldTryFallbackTrackers(int peersFromPrimaries, boolean haveBackup, boolean downloading) Should the fallback trackers be tried after a round that produced no peers?private booleansmallAnnounce(long left, int event) The announce needs few peers, either the download is complete, we are stopping, or we do not need outbound peers.voidstart()Start the tracker client thread.booleanstarted()Check if this tracker client has been started.private static voidthrottleForTracker(String host) Block until this tracker host has capacity under the 20/min throttle.private voidtryAddPeers(Collection<Peer> peers) Try to connect to the given peers, spreading the connection attempts over time.private voidCreates a thread for each tracker in parallel if tunnel is still open.(package private) static longHow long a stop should keep its sessions open for unannounces to dispatch: one wave per 32-tracker batch, so small swarms wait ~3s and bigger swarms scale up to UNANNOUNCE_MAX_WAIT.private voidupdateInterval(TrackerClient.TCTracker tr, TrackerInfo info) Clamp the tracker interval to the minimum and record it.private voidUpdate the torrent-level tracker problem state.static Stringurlencode(byte[] bs) Very lazy byte[] to URL encoder.private booleanChecks that the session is usable for a request.
Field Details
_log
Logger for this torrent's tracker traffic.NO_EVENT
Event value when no event is being reported.- See Also:
STARTED_EVENT
Event sent when the torrent starts downloading.- See Also:
COMPLETED_EVENT
Event sent when the download finishes.- See Also:
STOPPED_EVENT
Event sent when the torrent stops.- See Also:
PAUSED_EVENT
Event sent when the torrent is paused.- See Also:
NOT_REGISTERED
Tracker error for an unregistered torrent, from ByteMonsoon.- See Also:
NOT_REGISTERED_2
Tracker error for an unregistered torrent, from DifTracker.- See Also:
NOT_REGISTERED_3
Tracker error for an unregistered torrent, from Vuze.- See Also:
ERROR_GOT_HTML
Error when the tracker returns an HTML page instead of data.- See Also:
SCRAPE
SLEEP
UNANNOUNCE_THREADS
private static final int UNANNOUNCE_THREADSThreads draining the unannounce queue.- See Also:
UNANNOUNCE_QUEUE
private static final int UNANNOUNCE_QUEUEUnannounce requests held before they are dropped.- See Also:
UNANNOUNCE_KEEPALIVE
private static final long UNANNOUNCE_KEEPALIVEAn unannounce thread exits after 1 minute idle.- See Also:
UNANNOUNCE_WAVE
private static final long UNANNOUNCE_WAVEOne wave of unannounces, latency-bound, through an established tunnel.- See Also:
UNANNOUNCE_MIN_WAIT
private static final long UNANNOUNCE_MIN_WAITDispatch window is scaled to the batch, clamped between these.- See Also:
UNANNOUNCE_MAX_WAIT
private static final long UNANNOUNCE_MAX_WAITAn unannounce waits at most 10 seconds for a session.- See Also:
UNANNOUNCE_PRIMARY_THRESHOLD
private static final int UNANNOUNCE_PRIMARY_THRESHOLDWhen the queue is this many waves deep, only the primary tracker per torrent gets a slot.- See Also:
_unannouncers
Unannounces are sent from a shared, bounded pool instead of one thread per tracker, so stopping thousands of torrents cannot exhaust threads. Threads exist only while unannounces are pending and die back after UNANNOUNCE_KEEPALIVE of idle time; the pool is capped at UNANNOUNCE_THREADS, sized to saturate the I2P tunnel (requests are latency-bound, so a few dozen workers drain a burst as fast as the network allows). The bounded queue absorbs the burst; only when the queue and pool are full is the unannounce skipped and the tracker reset, the same degradation as the previous OutOfMemoryError fallback. Threads are daemons, so they never hold up router shutdown._unannounceLatch
Released when this client's last unannounce dispatch finishes; awaited by halt() callers.DELAY_MIN
private static final int DELAY_MINTracker requests are spaced at least 2 seconds apart.- See Also:
DELAY_RAND
private static final int DELAY_RANDRandom delay added to the minimum, up to 6 seconds.- See Also:
MAX_REGISTER_FAILS
private static final int MAX_REGISTER_FAILSFailed registrations before giving up on a tracker.- See Also:
INITIAL_SLEEP
private static final int INITIAL_SLEEPA failed registration is retried after 90 seconds.- See Also:
MAGNET_INITIAL_SLEEP
static final int MAGNET_INITIAL_SLEEPFirst-announce delay for a torrent with no metainfo (a magnet, including every zzzot name lookup). Short enough that a lookup budget is not spent asleep, long enough that a batch of magnets added at once still does not announce in lockstep.- See Also:
MAX_CONSEC_FAILS
private static final int MAX_CONSEC_FAILSTracker requests slow down after this many consecutive failures.- See Also:
ANNOUNCE_PERMITS
Global cap on tracker HTTP requests in flight across ALL torrents, so simultaneous announce/scrape rounds cannot stampede a tracker (or our own netdb lookup path) when many TrackerClient threads wake together. Requests beyond the cap queue on the permit; the fetch timeout clock only starts once the permit is held. Increased from 3 to 10 to allow parallel DHT metadata lookups (e.g. zzzot hashlist) while per-tracker throttle below prevents hammering any single tracker.MAX_PER_TRACKER_PER_MINUTE
private static final int MAX_PER_TRACKER_PER_MINUTEPer-tracker throttle: max 20 requests per tracker host per minute. Shared across all torrents to cap load on popular trackers (e.g. postman, torrfreedom) even when many torrents use the same tracker. Guarded by itself.- See Also:
TRACKER_THROTTLE_WINDOW_MS
private static final long TRACKER_THROTTLE_WINDOW_MSRequests to one tracker are spaced across a 1 minute window.- See Also:
_trackerRequestTimes
LONG_SLEEP
private static final int LONG_SLEEPSleep after lots of fails — shortened from 10m so post-recovery tracker retries fire within ~2m instead of waiting a full cycle.- See Also:
LONG_SLEEP_JITTER
private static final int LONG_SLEEP_JITTERMax random spread added to the LONG_SLEEP failure floor so torrents that failed together don't re-announce in lockstep @since 0.9.71+.- See Also:
MIN_TRACKER_ANNOUNCE_INTERVAL
private static final long MIN_TRACKER_ANNOUNCE_INTERVALShortest interval between HTTP tracker announces, in milliseconds.- See Also:
MIN_DHT_ANNOUNCE_INTERVAL
private static final long MIN_DHT_ANNOUNCE_INTERVALShortest interval between DHT tracker announces, in milliseconds.- See Also:
DHT_ANNOUNCE_JITTER
private static final int DHT_ANNOUNCE_JITTERRandom jitter added toMIN_DHT_ANNOUNCE_INTERVALso DHT announces from many torrents do not fire in lockstep; the effective threshold is 15m..19m.- See Also:
SCRAPE_INTERVAL
private static final long SCRAPE_INTERVALPeriodic scrape interval: refresh the swarm size (seeds + leeches) between announces (BEP 15/48). The announce loop wakes every 5m, so this fires on every other wake per active torrent.- See Also:
SCRAPE_GOOD_DEFER
private static final long SCRAPE_GOOD_DEFERAfter a scrape with a non-zero swarm size, defer the next scrape this long.- See Also:
SCRAPE_JITTER
private static final int SCRAPE_JITTERRandom jitter added to each scheduled scrape so scrapes from many torrents to a shared tracker spread out instead of firing in lockstep.- See Also:
DHT_ANNOUNCE_PEERS
private static final int DHT_ANNOUNCE_PEERSNo guidance in BEP 5; standard practice is K (=8).- See Also:
DHT_BOOTSTRAP_NODES
private static final int DHT_BOOTSTRAP_NODESA DHT with fewer than this many known nodes is still bootstrapping and unreliable, so a seeding trackerless torrent falls back to its backup trackers to find peers.- See Also:
PORT
DEFAULT_UDP_TRACKER_PORT
private static final int DEFAULT_UDP_TRACKER_PORTUDP tracker port used when none is given.- See Also:
MAX_TRACKERS
private static final int MAX_TRACKERSTrackers used per torrent at most.- See Also:
_util
Util providing the session and proxy settings.meta
The torrent's metadata, reassigned by reinitialize.infoHash
Hex info hash identifying the torrent.peerID
Random peer id sent to the trackers.additionalTrackerURL
Extra tracker from the user's config, may be null.coordinator
Coordinator sharing peers with other torrents.snark
The snark downloading this torrent.port
private final int portListen port advertised to the trackers._threadName
Name prefix for this torrent's threads.stop
private volatile boolean stopSet to stop the tracker loop.started
private volatile boolean startedSet once the started event has been sent._initialized
private volatile boolean _initializedSet once the trackers are registered._runCount
private volatile int _runCountNumber of loop runs so far._thread
The running tracker thread, kept for interrupts._dhtThread
The running DHT fetch thread, kept for interrupts._dhtPeersSeen
private volatile int _dhtPeersSeenPeers seen in the last DHT fetch, updated by that thread._event
The queued timer event, kept for cancellation.runStarted
private volatile boolean runStartedSet while the loop body is running.consecutiveFails
private volatile int consecutiveFailsConsecutive failed requests in the loop.lastHadMeta
private volatile boolean lastHadMetaWhether the last response carried fresh metadata.completed
private boolean completedWhether the torrent is complete, not necessarily seeding, as files may be skipped._fastUnannounce
private volatile boolean _fastUnannounceSet to unannounce from the trackers early.lastDHTAnnounce
private volatile long lastDHTAnnounceWhen the last DHT announce ran, written by the fetch thread.trackers
The active trackers for this torrent.backupTrackers
Backup trackers used when the others fail._startedOn
private long _startedOnWhen this client was started.
Constructor Details
TrackerClient
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, String additionalTrackerURL, PeerCoordinator coordinator, Snark snark) Call start() to start it.- Parameters:
util- the I2PSnarkUtil instance shared with the rest of the clientmeta- null if in magnet modeadditionalTrackerURL- may be null, from the ?tr= param in magnet mode, otherwise ignoredcoordinator- the peer coordinator supplying completion state and requesting outbound peerssnark- the Snark for this torrent, supplying the info hash and peer ID
Method Details
unannounceDispatchWait
static long unannounceDispatchWait()How long a stop should keep its sessions open for unannounces to dispatch: one wave per 32-tracker batch, so small swarms wait ~3s and bigger swarms scale up to UNANNOUNCE_MAX_WAIT.- Returns:
- the wait in milliseconds, never below UNANNOUNCE_MIN_WAIT and never above UNANNOUNCE_MAX_WAIT
throttleForTracker
Block until this tracker host has capacity under the 20/min throttle. Must be called before acquiring ANNOUNCE_PERMITS so queued threads don't hold global permits while waiting for per-tracker quota.- Parameters:
host- tracker host (from TCTracker.host), nullable
longSleepWithJitter
static long longSleepWithJitter(long currentInterval, long randomJitter) Failure backoff for a tracker that just hit MAX_CONSEC_FAILS: floor the announce interval at LONG_SLEEP and add up to LONG_SLEEP_JITTER of randomness so many torrents failing together don't re-announce in lockstep every 2 minutes. Intervals already at or above the floor and out-of-range random values are handled: the jitter is clamped to [0, LONG_SLEEP_JITTER] and only applied when the floor was raised.- Parameters:
currentInterval- tracker's current announce interval in msrandomJitter- raw random jitter in ms, before clamping- Returns:
- the new announce interval in ms
start
public void start()Start the tracker client thread.halted
public boolean halted()Check if this tracker client has been halted.- Returns:
- true if halted
started
public boolean started()Check if this tracker client has been started.- Returns:
- true if started
halt
public void halt(boolean fast) Interrupts this Thread to stop it.- Parameters:
fast- if true, limit the life of the unannounce threads
awaitUnannounces
public void awaitUnannounces(long ms) Wait, capped at ms, for the unannounces submitted by the last halt() to dispatch. Returns immediately when nothing was submitted or they are done.- Parameters:
ms- the longest to wait, in milliseconds, for the last halt()'s
queueLoop
private void queueLoop(long delay) Queues the next loop run after the delay.- Parameters:
delay- the seconds to wait
verifyConnected
private boolean verifyConnected()Checks that the session is usable for a request.- Returns:
- true if the session is connected
run
reinitialize
public void reinitialize()Call after editing torrent.setup
private void setup()Do this one time only (not every time it is started). Unless torrent was edited.isNewValidTracker
loop
private void loop()Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event. This will take several seconds to several minutes.normalizeTrackerProblem
Lower-cases a tracker problem message for prefix matching.Never null: several
IOExceptionsubtypes return a nullgetMessage(), and the announce loop calls this on the result directly. Letting that NPE escape would abort the announce for every remaining tracker because of one malformed response from the first one.- Parameters:
trackerProblem- message from the exception, may be null- Returns:
- lower-cased message, or the empty string when there was none
isRegistrationFailure
Whether a tracker problem means this tracker will never accept the torrent, as opposed to a transient failure worth retrying.- Parameters:
trackerProblem- message already passed throughnormalizeTrackerProblem(String), never null- Returns:
- true if the tracker is rejecting the torrent outright
initialAnnounceDelayMs
static long initialAnnounceDelayMs(boolean hasMeta, int fullSpread, int magnetSpread) Delay before a torrent's first announce.Pure decision, separated so the magnet case can be pinned by test: the spread exists to protect the trackers from a thundering herd, and that concern does not apply to a single lookup, which is actively waiting for an answer.
- Parameters:
hasMeta- true once the torrent has its metainfofullSpread- random value in [0, INITIAL_SLEEP)magnetSpread- random value in [0, MAGNET_INITIAL_SLEEP)- Returns:
- ms to wait before the first announce
shouldTryFallbackTrackers
static boolean shouldTryFallbackTrackers(int peersFromPrimaries, boolean haveBackup, boolean downloading) Should the fallback trackers be tried after a round that produced no peers?needBackupTrackers(boolean, boolean, boolean, int, boolean)deliberately refuses to consult backups while a primary tracker is configured, which is right for a download that is already finding peers but leaves a magnet with nothing: every announce failed and the next cycle is 90s away. A different transport is the only remaining option, and the caller has already paid for the round that failed.- Parameters:
peersFromPrimaries- peers returned by the primary trackers this cyclehaveBackup- whether any fallback tracker is configureddownloading- whether the torrent still wants peers- Returns:
- true if the fallback round should run
needBackupTrackers
static boolean needBackupTrackers(boolean havePrimary, boolean haveBackup, boolean dhtEnabled, int dhtSize, boolean downloading) Whether a trackerless torrent should consult its backup trackers this round. A download always does; a seed does only while the DHT is still short of its bootstrap node count, since once the DHT is warm the trackers add nothing.- Parameters:
havePrimary- whether any primary tracker is configuredhaveBackup- whether any fallback tracker is configureddhtEnabled- whether a DHT instance is running for this torrentdhtSize- the number of known nodes in that DHTdownloading- whether the torrent still wants peers, false for a seed- Returns:
- true if the backup tracker round should run
getPeersFromTrackers
Fetch peers from a list of trackers.- Parameters:
trckrs- the list of trackers to query- Returns:
- max peers seen
- Throws:
Throwable- from a tracker announce or scrape, e.g. I/O or protocol errors
updateTrackerProblems
private void updateTrackerProblems()Update the torrent-level tracker problem state. Problems are only reported when announces to every active tracker have failed; a single working tracker clears the error state.processScrapeResponse
Handle a successful scrape response: refresh the swarm composition and clear the tracker's error indicator — the response proves the tracker is reachable, so a "No response from..." announce failure no longer applies.- Parameters:
tr- the tracker that answered, its error indicator is clearedscrape- the parsed scrape response carrying the swarm counts- Returns:
- true if the response reported a non-zero swarm size
scheduleNextScrape
Schedule the next scrape for a tracker: the standard interval, or the longer deferral after a good response, plus random jitter so scrapes from many torrents to a shared tracker do not fire in lockstep.- Parameters:
tr- the tracker record whose nextScrapeTime is setgood- true if the last response reported a non-zero swarm size
getPeersFromPEX
private int getPeersFromPEX()Fetch peers from Peer Exchange (PEX).- Returns:
- max peers seen
fetchPeersFromDHT
private void fetchPeersFromDHT()Start a DHT peer fetch in a separate thread, if one is not already running. The lookup and announce can take minutes, so it must not block the announce loop.getWebPeers
private int getWebPeers()Valid web peers from the metainfo.- Returns:
- valid web peers from metainfo
tryAddPeers
Try to connect to the given peers, spreading the connection attempts over time. The peers are shuffled first so no single peer is always first; the passed collection is not modified.- Parameters:
peers- the peers to try to connect to
unannounce
private void unannounce()Creates a thread for each tracker in parallel if tunnel is still open.doRequest
private TrackerInfo doRequest(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) throws IOException, Throwable Announce - blocking, dispatches to HTTP or UDP by tracker type.Note: IOException message text gets displayed in the UI
- Parameters:
tr- the tracker to announce to, its isUDP flag picks the transportuploaded- bytes uploaded by this clientdownloaded- bytes downloaded by this clientleft- bytes remaining to download, -1 in magnet modeevent- the BEP-3 announce event, e.g. started, completed or stopped- Returns:
- null if _fastUnannounce && event == STOPPED
- Throws:
IOException- if the HTTP announce fails, the message shows in the UIThrowable- if the UDP announce fails
doScrape
Scrape for swarm composition from a tracker, BEP 15 (UDP) or BEP 48 (HTTP).- Parameters:
tr- the tracker record, whose isUDP flag selects the BEP 15 or BEP 48 path- Returns:
- the scrape result, or null on failure
- Throws:
IOException- on HTTP scrape failure
doScrapeHTTP
Scrape for swarm composition from an HTTP tracker, BEP 48: the last path segment of the announce URL is replaced with "scrape" and any query string is dropped.- Parameters:
tr- the tracker record, supplying the announce URL and the host nameinfoHash- the hex info hash- Returns:
- the scrape result, or null on failure
- Throws:
IOException- on fetch or parse failure
scrapeURL
Build the BEP 48 scrape URL from the announce URL: drop any query string and replace the last path segment with "scrape". A trailing slash is stripped first, so the last non-empty segment is replaced.- Parameters:
announce- the announce URL- Returns:
- the scrape URL
isMyHash
Whether the key was already in the dictionary.- Parameters:
key- a bencoded dictionary keyih- the torrent's info hash- Returns:
- whether the key is the info hash, byte for byte
doRequestHTTP
private TrackerInfo doRequestHTTP(TrackerClient.TCTracker tr, String infoHash, String peerID, long uploaded, long downloaded, long left, int event) throws IOException, Throwable HTTP - blocking.Note: IOException message text gets displayed in the UI
- Parameters:
tr- the tracker to announce to, its announce URL gets the query parametersinfoHash- the hex info hash of the torrent, sent as the info_hash parameterpeerID- our peer id, sent as the peer_id parameteruploaded- bytes uploaded by this clientdownloaded- bytes downloaded by this clientleft- bytes remaining to download, -1 in magnet modeevent- the BEP-3 announce event, e.g. started, completed or stopped- Returns:
- the tracker's parsed announce response, never null
- Throws:
IOException- if the tracker is unreachable, returned HTML, or reported a failureThrowable- if the bencoded response cannot be parsed
doRequestUDP
private TrackerInfo doRequestUDP(TrackerClient.TCTracker tr, long uploaded, long downloaded, long left, int event) throws IOException UDP - blocking.- Parameters:
tr- the tracker to announce to over BEP 15uploaded- bytes uploaded by this clientdownloaded- bytes downloaded by this clientleft- bytes remaining to download, -1 in magnet modeevent- the BEP-3 announce event, e.g. started, completed or stopped- Returns:
- null if _fastUnannounce && event == STOPPED
- Throws:
IOException- if the UDP tracker client is unavailable or the exchange fails
smallAnnounce
private boolean smallAnnounce(long left, int event) The announce needs few peers, either the download is complete, we are stopping, or we do not need outbound peers.- Parameters:
left- the bytes left to download, -1 for magnetevent- the announce event- Returns:
- whether the announce should request few or no peers
updateInterval
Clamp the tracker interval to the minimum and record it.- Parameters:
tr- the tracker record whose interval is clamped and recordedinfo- the announce response
eventToString
The HTTP announce event name for a UDP-style event code.- Parameters:
event- the UDP event code- Returns:
- the event name
urlencode
Very lazy byte[] to URL encoder. Just encodes almost everything, even some "normal" chars. By not encoding about 1/4 of the chars, we make random data like hashes about 16% smaller.RFC1738: 0-9a-zA-Z$-_.+!*'(), Us: 0-9a-zA-Z
- Parameters:
bs- the bytes to encode- Returns:
- the URL-encoded string
isValidAnnounce
Whether the announce URL is an I2P host.- Parameters:
ann- an announce URL, may be null, returns false if null- Returns:
- true for i2p hosts only
getHostHash