# Written by John Hoffman # see LICENSE.txt for license information from Rerequester import Rerequester from urllib import quote from threading import Event from random import randrange from string import lower import sys import __init__ try: True except: True = 1 False = 0 DEBUG = True def excfunc(x): print x R_0 = lambda: 0 R_1 = lambda: 1 class T2TConnection: def __init__(self, myid, tracker, hash, interval, peers, timeout, rawserver, disallow, isdisallowed): self.tracker = tracker self.interval = interval self.hash = hash self.operatinginterval = interval self.peers = peers self.rawserver = rawserver self.disallow = disallow self.isdisallowed = isdisallowed self.active = True self.busy = False self.errors = 0 self.rejected = 0 self.trackererror = False self.peerlists = [] cfg = { 'min_peers': peers, 'max_initiate': peers, 'rerequest_interval': interval, 'http_timeout': timeout } self.rerequester = Rerequester( 0, myid, hash, [[tracker]], cfg, rawserver.add_task, rawserver.add_task, self.errorfunc, excfunc, self.addtolist, R_0, R_1, R_0, R_0, R_0, R_0, Event() ) if self.isactive(): rawserver.add_task(self.refresh, randrange(int(self.interval/10), self.interval)) # stagger announces def isactive(self): if self.isdisallowed(self.tracker): # whoops! self.deactivate() return self.active def deactivate(self): self.active = False def refresh(self): if not self.isactive(): return self.lastsuccessful = True self.newpeerdata = [] if DEBUG: print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash)) self.rerequester.snoop(self.peers, self.callback) def callback(self): self.busy = False if self.lastsuccessful: self.errors = 0 self.rejected = 0 if self.rerequester.announce_interval > (3*self.interval): # I think I'm stripping from a regular tracker; boost the number of peers requested self.peers = int(self.peers * (self.rerequester.announce_interval / self.interval)) self.operatinginterval = self.rerequester.announce_interval if DEBUG: print ("%s with info_hash=%s returned %d peers" % (self.tracker, quote(self.hash), len(self.newpeerdata))) self.peerlists.append(self.newpeerdata) self.peerlists = self.peerlists[-10:] # keep up to the last 10 announces if self.isactive(): self.rawserver.add_task(self.refresh, self.operatinginterval) def addtolist(self, peers): for peer in peers: self.newpeerdata.append((peer[1],peer[0][0],peer[0][1])) def errorfunc(self, r): self.lastsuccessful = False if DEBUG: print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash), r) if r == self.rerequester.rejectedmessage + 'disallowed': # whoops! if DEBUG: print ' -- disallowed - deactivating' self.deactivate() self.disallow(self.tracker) # signal other torrents on this tracker return if lower(r[:8]) == 'rejected': # tracker rejected this particular torrent self.rejected += 1 if self.rejected == 3: # rejected 3 times if DEBUG: print ' -- rejected 3 times - deactivating' self.deactivate() return self.errors += 1 if self.errors >= 3: # three or more errors in a row self.operatinginterval += self.interval # lengthen the interval if DEBUG: print ' -- lengthening interval to '+str(self.operatinginterval)+' seconds' def harvest(self): x = [] for list in self.peerlists: x += list self.peerlists = [] return x class T2TList: def __init__(self, enabled, trackerid, interval, maxpeers, timeout, rawserver): self.enabled = enabled self.trackerid = trackerid self.interval = interval self.maxpeers = maxpeers self.timeout = timeout self.rawserver = rawserver self.list = {} self.torrents = {} self.disallowed = {} self.oldtorrents = [] def parse(self, allowed_list): if not self.enabled: return # step 1: Create a new list with all tracker/torrent combinations in allowed_dir newlist = {} for hash, data in allowed_list.items(): if data.has_key('announce-list'): for tier in data['announce-list']: for tracker in tier: self.disallowed.setdefault(tracker, False) newlist.setdefault(tracker, {}) newlist[tracker][hash] = None # placeholder # step 2: Go through and copy old data to the new list. # if the new list has no place for it, then it's old, so deactivate it for tracker, hashdata in self.list.items(): for hash, t2t in hashdata.items(): if not newlist.has_key(tracker) or not newlist[tracker].has_key(hash): t2t.deactivate() # this connection is no longer current self.oldtorrents += [t2t] # keep it referenced in case a thread comes along and tries to access. else: newlist[tracker][hash] = t2t if not newlist.has_key(tracker): self.disallowed[tracker] = False # reset when no torrents on it left self.list = newlist newtorrents = {} # step 3: If there are any entries that haven't been initialized yet, do so. # At the same time, copy all entries onto the by-torrent list. for tracker, hashdata in newlist.items(): for hash, t2t in hashdata.items(): if t2t is None: hashdata[hash] = T2TConnection(self.trackerid, tracker, hash, self.interval, self.maxpeers, self.timeout, self.rawserver, self._disallow, self._isdisallowed) newtorrents.setdefault(hash,[]) newtorrents[hash] += [hashdata[hash]] self.torrents = newtorrents # structures: # list = {tracker: {hash: T2TConnection, ...}, ...} # torrents = {hash: [T2TConnection, ...]} # disallowed = {tracker: flag, ...} # oldtorrents = [T2TConnection, ...] def _disallow(self,tracker): self.disallowed[tracker] = True def _isdisallowed(self,tracker): return self.disallowed[tracker] def harvest(self,hash): harvest = [] if self.enabled: for t2t in self.torrents[hash]: harvest += t2t.harvest() return harvest