Blame | Last modification | View Log | Download
# Written by John Hoffman# see LICENSE.txt for license informationfrom Rerequester import Rerequesterfrom urllib import quotefrom threading import Eventfrom random import randrangefrom string import lowerimport sysimport __init__try:Trueexcept:True = 1False = 0DEBUG = Truedef excfunc(x):print xclass T2TConnection:def __init__(self, myid, tracker, hash, interval, peers, timeout,rawserver, disallow, isdisallowed):self.tracker = trackerself.interval = intervalself.hash = hashself.operatinginterval = intervalself.peers = peersself.rawserver = rawserverself.disallow = disallowself.isdisallowed = isdisallowedself.active = Trueself.busy = Falseself.errors = 0self.rejected = 0self.trackererror = Falseself.peerlists = []self.rerequester = Rerequester([[tracker]], interval,rawserver.add_task, lambda: 0, peers, self.addtolist,rawserver.add_task, lambda: 1, 0, 0, 0, '',myid, hash, timeout, self.errorfunc, excfunc, peers, Event(),lambda: 0, lambda: 0)if self.isactive():rawserver.add_task(self.refresh, randrange(int(self.interval/10), self.interval))# stagger announcesdef isactive(self):if self.isdisallowed(self.tracker): # whoops!self.deactivate()return self.activedef deactivate(self):self.active = Falsedef refresh(self):if not self.isactive():returnself.lastsuccessful = Trueself.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 = Falseif self.lastsuccessful:self.errors = 0self.rejected = 0if self.rerequester.announce_interval > (3*self.interval):# I think I'm stripping from a regular tracker; boost the number of peers requestedself.peers = int(self.peers * (self.rerequester.announce_interval / self.interval))self.operatinginterval = self.rerequester.announce_intervalif 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 announcesif 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 = Falseif 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 trackerreturnif lower(r[:8]) == 'rejected': # tracker rejected this particular torrentself.rejected += 1if self.rejected == 3: # rejected 3 timesif DEBUG:print ' -- rejected 3 times - deactivating'self.deactivate()returnself.errors += 1if self.errors >= 3: # three or more errors in a rowself.operatinginterval += self.interval # lengthen the intervalif DEBUG:print ' -- lengthening interval to '+str(self.operatinginterval)+' seconds'def harvest(self):x = []for list in self.peerlists:x += listself.peerlists = []return xclass T2TList:def __init__(self, enabled, trackerid, interval, maxpeers, timeout, rawserver):self.enabled = enabledself.trackerid = trackeridself.interval = intervalself.maxpeers = maxpeersself.timeout = timeoutself.rawserver = rawserverself.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_dirnewlist = {}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 itfor 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 currentself.oldtorrents += [t2t]# keep it referenced in case a thread comes along and tries to access.else:newlist[tracker][hash] = t2tif not newlist.has_key(tracker):self.disallowed[tracker] = False # reset when no torrents on it leftself.list = newlistnewtorrents = {}# 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] = Truedef _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