#!/usr/bin/env python import policy def get_config_dir(): return policy.get_policy().root_path from BitTorrent import platform platform.get_config_dir = get_config_dir platform.install_translation() #from BitTorrent import Rerequester #from BitTornado import bencode as _bencode #bencode.bencode = _bencode.bencode #bencode.bdecode = _bencode.bdecode #Rerequester.bencode = _bencode.bencode #Rerequester.bdecode = _bencode.bdecode from BitTorrent import RawServer try: basestring except NameError: class dummy: pass RawServer.basestring = dummy ######################################################################## # Rerequester.Rerequester ######################################################################## from BitTorrent import Rerequester from BitTorrent.zurllib import quote from binascii import b2a_hex from random import random, randrange from socket import error, gethostbyname, gethostname from BitTorrent.platform import bttime from BitTorrent.btformats import check_peers BTRerequester = Rerequester.Rerequester class _Rerequester(BTRerequester): def _makeurl(self, peerid, port): url = ('info_hash=%s&peer_id=%s&port=%s&key=%s' % (quote(self.infohash), quote(peerid), str(port), b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)])))) try: url += '&local_ip=%s' % quote(gethostbyname(gethostname())) except: pass if self.baseurl.find('?') >= 0: c = '&' else: c = '?' return '%s%s%s' % (self.baseurl,c,url) def _rerequest(self, url, peerid): get_thread().add_message(self.infohash,'ann: %s' % url) return BTRerequester._rerequest(self, url, peerid) def _postrequest(self, data=None, errormsg=None, peerid=None): self.current_started = None self.last_time = bttime() if errormsg is not None: self.errorfunc(WARNING, errormsg) self._fail() return try: r = bdecode(data) check_peers(r) except BTFailure, e: if data != '': self.errorfunc(ERROR, _("bad data from tracker - ") + str(e)) self._fail() return if type(r.get('complete')) in (int, long) and \ type(r.get('incomplete')) in (int, long): self.tracker_num_seeds = r['complete'] self.tracker_num_peers = r['incomplete'] else: self.tracker_num_seeds = self.tracker_num_peers = None if r.has_key('failure reason'): if self.howmany() > 0: self.errorfunc(ERROR, _("rejected by tracker - ") + r['failure reason']) else: # sched shouldn't be strictly necessary def die(): self.diefunc(CRITICAL, _("Aborting the torrent as it was rejected by " "the tracker while not connected to any peers. ") + _(" Message from the tracker: ") + r['failure reason']) self.sched(die, 0) self._fail() else: self.fail_wait = None if r.has_key('warning message'): self.errorfunc(ERROR, _("warning from tracker - ") + r['warning message']) self.announce_interval = r.get('interval', self.announce_interval) self.config['rerequest_interval'] = r.get('min interval', self.config['rerequest_interval']) self.trackerid = r.get('tracker id', self.trackerid) self.last = r.get('last') p = r['peers'] peers = [] if type(p) == str: for x in xrange(0, len(p), 6): ip = '.'.join([str(ord(i)) for i in p[x:x+4]]) port = (ord(p[x+4]) << 8) | ord(p[x+5]) peers.append((ip, port, None)) else: for x in p: peers.append((x['ip'], x['port'], x.get('peer id'))) ps = len(peers) + self.howmany() if ps < self.config['max_initiate']: if self.doneflag.isSet(): if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2: self.last = None else: if r.get('num peers', 1000) > ps * 1.2: self.last = None thread = get_thread() if thread: thread.got_peers(self.infohash,peers) for x in peers: self.connect((x[0], x[1]), x[2]) if peerid == self.wanted_peerid: self.successfunc() self._check() Rerequester.Rerequester = _Rerequester BTDHTRerequester = Rerequester.DHTRerequester class _DHTRerequester(_Rerequester): def __init__(self, config, sched, howmany, connect, externalsched, amount_left, up, down, port, myid, infohash, errorfunc, doneflag, upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc, dht): self.dht = dht _Rerequester.__init__(self, "http://localhost/announce", config, sched, howmany, connect, externalsched, amount_left, up, down, port, myid, infohash, errorfunc, doneflag, upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc) def _announce(self, event=None): self.current_started = bttime() self._rerequest("", self.peerid) def _rerequest(self, url, peerid): self.peers = "" try: self.dht.getPeersAndAnnounce(self.infohash, self.port, self._got_peers) except Exception, e: self._postrequest(errormsg=_("Trackerless lookup failed: ") + str(e), peerid=self.wanted_peerid) def _got_peers(self, peers): if not self.howmany: return if not peers: self._postrequest(bencode({'peers':''}), peerid=self.wanted_peerid) else: self._postrequest(bencode({'peers':''.join(peers)}), peerid=None) def _announced_peers(self, nodes): pass def announce_stop(self): # don't do anything pass Rerequester.DHTRerequester = _DHTRerequester ######################################################################## # download.SingleTorrent ######################################################################## from BitTorrent import download download.Rerequester = _Rerequester download.DHTRerequester = _DHTRerequester BTSingleTorrent = download._SingleTorrent class _SingleTorrent(BTSingleTorrent): def __init__(self,*args,**kw): BTSingleTorrent.__init__(self,*args,**kw) self.policy = policy.get_policy() def _make_id(self): pid = self.policy(policy.PEER_ID).lower() if pid == 'btq': self._make_btq() elif pid == 'az': self._make_az() elif pid.startswith('bc'): self._make_bc(pid[2:]) elif pid == 'ml': self._make_bt() else: self._make_btq() return self.myid def _make_btq(self): from BitTorrent import PeerID ins = PeerID.make_id()[-3:] from __init__ import createPeerID self.myid = createPeerID(ins) def _make_bc(self,version='0062'): import random version = version[:4] or '0062' self.myid = '-BC%s-' % version while len(self.myid) < 20: self.myid += random.choice('0123456789') def _make_az(self): import random self.myid = '-AZ2400-' while len(self.myid) < 20: self.myid += random.choice('0123456789') def _make_bt(self): from BitTorrent import PeerID self.myid = PeerID.make_id() def reannounce(self): if self._rerequest._announce: self._rerequest._announce() download._SingleTorrent = _SingleTorrent ######################################################################## # ConvertedMetainfo.ConvertedMetainfo ######################################################################## from BitTorrent import ConvertedMetainfo BTConvertedMetainfo = ConvertedMetainfo.ConvertedMetainfo class _ConvertedMetainfo(BTConvertedMetainfo): def __init__(self,*args,**kw): self.policy = policy.get_policy() BTConvertedMetainfo.__init__(self,*args,**kw) def _enforce_utf8(self, s): encodings = ['utf-8']+self.policy(policy.DEFAULT_ENCODING).split(',')+['latin1'] for encoding in encodings: try: s = s.decode(encoding) break except: pass if type(s) == type(u''): s = s.encode('utf-8') return BTConvertedMetainfo._enforce_utf8(self,s) ConvertedMetainfo.ConvertedMetainfo = _ConvertedMetainfo from BitTorrent.bencode import bencode,bdecode from BitTorrent import BTFailure,status_dict from BitTorrent import configfile from BitTorrent.defaultargs import get_defaults from BitTorrent.IPC import ipc_interface from BitTorrent.prefs import Preferences from BitTorrent import TorrentQueue from BitTorrent.ConvertedMetainfo import ConvertedMetainfo, set_filesystem_encoding from BitTorrent.download import Feedback, Multitorrent from BitTorrent import INFO, WARNING, ERROR, CRITICAL, status_dict import copy from random import seed,randrange from socket import error as socketerror from threading import Event from sys import argv, exit import sys, os from __init__ import createPeerID, mapbase64, version from threading import Event,Thread from cStringIO import StringIO import time from queue import hours as fmttime from i18n import * try: True except: True = 1 False = 0 class Struct: pass class global_logger: def __init__(self,logger=None): self.logger = logger def __call__(self, severity,msg): if self.logger: self.logger(severity,msg) else: sys.stderr.write("%s: %s\n" % (status_dict[severity],msg)) global_log_func = global_logger() _global_thread = None def set_thread(o): global _global_thread _global_thread = o def get_thread(): return _global_thread class LaunchManyThread(Thread,Feedback): def __init__(self,output,args=[],banfunc=None,msgfunc=None,startflag=None): Thread.__init__(self) set_thread(self) self.startflag = startflag or Event() self.output = output self.output.set_controller(self) defaults = get_defaults('bittorrent') config,args = configfile.parse_configuration_and_args(defaults,'btqueue.py',args,0,None) self.config = Preferences().initWithDict(config) set_filesystem_encoding(self.config['filesystem_encoding'],self.global_error) # ui_options = [] # ipc = ipc_interface(self.config,global_log_func) # ipc.create() # self.torrentqueue = TorrentQueue.TorrentQueue(self.config,ui_options,ipc) # self.torrentqueue_finished = self.torrentqueue.finished # self.torrentqueue.finished = self.hook_finished # self.wrappedqueue = TorrentQueue.ThreadWrappedQueue(self.torrentqueue) self.doneflag = Event() self.multitorrent = Multitorrent(self.config,self.doneflag,self.global_error,listen_fail_ok=True) self.banfunc = banfunc self.msgfunc = msgfunc self.policy = policy.get_policy() self.stats_period = self.config['display_interval'] self.torrent_list = [] self.downloads = {} self.counter = 0 self.scheduler = None self.startqueue = [] self.busyflag = Event() self.emptyflag = Event() self.removing_list = [] self.removingflag = Event() self.starting_item = None def initialized(self): return 1 def set_scheduler(self,scheduler): self.scheduler = scheduler def remove(self, item): if item in self.startqueue: self.startqueue.remove(item) self.torrent_list.remove(item.infohash_bin) try: del self.downloads[item] except TypeError: pass return try: self.removing_list.append(item) self.removingflag.set() self.multitorrent.rawserver.external_add_task(item.dow.shutdown,0) self.multitorrent.rawserver.external_add_task(self.terminated,0,args=(item.dow,)) except (KeyError,AttributeError): pass def request_status(self, item, spew=1, file=1): try: status = item.dow.get_status(spew,file) self.run_task(self.update_status,item.infohash_bin,status) except AttributeError: pass # hash = item.infohash_bin # self.torrentqueue.request_status(hash,1,1) def add(self, item): hash,data = item.infohash_bin,item.get_metadata() pol = item.get_policy() try: item.metainfo = ConvertedMetainfo(item.get_meta()) except BTFailure,why: self.output.exception('FAILURE: %s %s' % (item.id,item.title)) self.output.exception(str(why)) raise why self.torrent_list.append(hash) self.downloads[hash] = item name = item.metainfo.name_fs item.config = copy.copy(self.config) item.saveas = os.path.join(item.dest_path,item.handle_name(name)) item.config['save_in'] = item.dest_path item.config['min_peers'] = pol(policy.MIN_PEER) item.config['max_initiate'] = pol(policy.MAX_INITIATE) item.config['rerequest_interval'] = pol(policy.REREQUEST_INTERVAL) item.config['snub_time'] = pol(policy.SNUB_TIME) item.config['socket_timeout'] = pol(policy.IDLE_TIMEOUT) item.config['timeout_check_interval'] = pol(policy.IDLE_TIMEOUT_CHECK_INTERVAL) item.config['start_trackerless_client'] = self.auto_dht(item) item.config['filesystem_encoding'] = pol(policy.FILESYSTEM_ENCODING) item.config['tracker_proxy'] = pol(policy.TRACKER_PROXY) self.start_check(item) # self.torrentqueue.start_new_torrent(data) # metainfo = self.torrentqueue.torrents[hash] # self.save_location(hash) def auto_dht(self, item): mode = item.get_policy().get(policy.ENABLE_DHT) if mode == 0: return 0 elif mode == 2: if not item.metainfo.is_trackerless: item.metainfo.is_trackerless = 1 item.metainfo.nodes = [] return 1 enable = 1 meta = item.get_meta() if meta.has_key('nodes'): enable = 1 if meta.has_key('azureus_properties'): azp = meta['azureus_properties'] enable = azp.get('dht_backup_enable',1) if meta.has_key('info'): info = meta['info'] enable = enable and not info.get('private',0) if item.announce.startswith('udp://'): enable = 2 return enable def start_check(self, item): if self.busyflag.isSet(): self.startqueue.append(item) else: self.busyflag.set() self._start(item) def _start(self, item): self.emptyflag.clear() self.starting_item = item item.dow = self.multitorrent.start_torrent(item.metainfo,item.config,self,item.saveas) def stop(self): for hash in self.torrent_list: item = self.downloads[hash] self.remove(item) if self.removingflag.isSet(): self.emptyflag.wait() self.doneflag.set() # def save_location(self, hash): # metainfo = self.torrentqueue.torrents[hash].metainfo # name = metainfo.name_fs # path = self.config['save_in'] # fullname = os.path.join(path,name) # self.got_location(hash,fullname) # def got_location(self, hash, fullpath): # self.torrentqueue.torrents[hash].dlpath = fullpath # self.torrentqueue.set_save_location(hash,fullpath) # def saveAs(self, hash, name, saveas, isdir): # item = self.downloads[hash] ## if saveas: ## saveas = os.path.join(saveas,x['file'][:-1-len(x['type'])]) ## else: ## saveas = x['path'][:-1-len(x['type'])] # if item.saveas: # saveas = os.path.join(saveas,item.saveas) # if isdir or not item.saveas: # saveas = os.path.join(saveas,name) # if isdir and not os.path.isdir(saveas): # try: # os.makedirs(saveas) # except: # raise OSError("couldn't create directory for "+item.title) # return saveas # def died(self, hash): # try: # item = self.downloads[hash] # self.output.message('DIED: '+item.id) # except KeyError: # self.failed('%s has not been started properly' % hash) def run(self): # self.torrentqueue.run(self,self.run_task,self.startflag) self.multitorrent.rawserver.listen_forever() if self.doneflag.isSet(): self.run_task(self.quit) self.multitorrent.close_listening_socket() def run_task(self,function,*args): return function(*args) def process_queue(self): if self.startqueue: item = self.startqueue.pop(0) self._start(item) else: self.busyflag.clear() def started(self,torrent): item = self.downloads.get(torrent.infohash,None) if not item: self.output.message('FAILURE: not found '+torrent.infohash) return self.output.message('started %s' % item.id) self.starting_item = None self.process_queue() def finished(self,torrent): item = self.downloads.get(torrent.infohash) if hasattr(item,'cb_finished'): item.cb_finished(item) item.state = STATE_SEEDING item.activity = None item.clear_stat() def terminated(self,torrent): item = self.downloads.get(torrent.infohash,None) try: self.torrent_list.remove(torrent.infohash) except ValueError: pass try: del self.downloads[torrent.infohash] except KeyError: pass if not self.torrent_list: self.emptyflag.set() if item: self.output.message('terminated %s' % item.id) self.removing_list.remove(item) if not self.removing_list: self.removingflag.clear() def failed(self,torrent,is_external=1): item = self.downloads.get(torrent.infohash) if not item: self.output.message('FAILURE: invalid item') return self.output.message('FAILURE: %s %s' % (item.id,item.title)) if torrent.errors: def format_error(t,l,msg): return '%s %s %s' % (time.strftime('%Y/%m/%d %H:%M:%S'),status_dict[l],msg) self.output.message(' %s' % format_error(*torrent.errors[-1])) self.scheduler.pause(item) if item == self.starting_item: self.starting_item = None self.process_queue() def got_peers(self,infohash,peers): item = self.downloads.get(infohash,None) if item: peer_list = [] for ip,port,peer_id in peers: peer_list.append({'ip':ip,'port':port,'id':peer_id}) item.update_peers(peer_list) def add_message(self,infohash,msg): item = self.downloads.get(infohash,None) if item: self.msgfunc(item,msg) def exception(self,torrent,s): self.output.exception(str(s)) def error(self,torrent,severity,text): item = self.downloads.get(torrent.infohash,None) if item: item.error = text def set_config(self,option,value): print 'set_config',option,value def global_error(self,severity,text): self.output.message('FAILURE: '+text) # def check_version(self): # print 'check_version' # def stop_queue(self): # print 'stop_queue' # def restart_queue(self): # print 'restart_queue' def update_status(self,torrent,statistics): item = self.downloads.get(torrent) # metainfo = self.torrentqueue.torrents.get(torrent) metainfo = item.metainfo if item: activity = statistics['activity'] status = '' progress = statistics['fractionDone'] or 0.0 t = 0 if not item.dow.started: if item.dow._hashcheck_thread: status = 'checking hash' else: status = 'waiting for hash check' elif item.dow.is_seed: status = '' elif statistics.get('numSeeds',0)+statistics.get('numPeers',0) > 0: t = statistics.get('timeEst',None) if t is None: t = -1 status = '' else: t = -1 status = 'connecting to peers' dnrate = statistics.get('downRate',0.0) uprate = statistics.get('upRate',0.0) s = Struct() s.downTotal = statistics.get('downTotal',0) s.upTotal = statistics.get('upTotal',0) s.numSeeds = statistics.get('numSeeds',0) s.numPeers = statistics.get('numPeers',0) s.numCopies = statistics.get('numCopies',0) s.percentDone = statistics.get('fractionDone',0.0) s.torrentRate = reduce(lambda a,b:a+b,[i.get('speed',0) for i in statistics.get('spew',[])],0) spew = statistics.get('spew',None) if spew: for i in spew: i['direction'] = i['initiation'] i['utotal'],i['uprate'],i['uinterested'],i['uchoked'] = i['upload'] i['dtotal'],i['downrate'],i['dinterested'],i['dchoked'],i['snubbed'] = i['download'] item.update_info(fractionDone=progress, timeEst=t, downRate=dnrate, upRate=uprate, activity=status, statistics=s, spew=spew) # def new_displayed_torrent(self,infohash,metainfo,dlpath,state,config, # completion=None,uptotal=0,downtotal=0): # print 'new_displayed_torrent',state,completion,uptotal,downtotal # def torrent_state_changed(self,infohash,dlpath,state,completion, # uptotal,downtotal,queuepos=None): # print 'torrent_state_changed',state,completion,uptotal,downtotal # def reorder_torrent(self,infohash,queuepos): # print 'reorder_torrent' # def update_completion(self,infohash,completion, # files_left=None,files_allocated=None): # print 'update_completion' # def removed_torrent(self,infohash): # print 'removed_torrent' # def change_torrent_state(self,infohash,newstate,index=None,replaced=None,force_running=False): # print 'change_torrent_state' def quit(self): self.output.message('internal queue stopped') def set_upload_rate(self,rate,item=None): if not item: pass #self.ratelimiter.set_upload_rate(rate) else: item.dow.setUploadRate(rate) def set_download_rate(self,rate,item=None): if not item: pass #for hash in self.torrent_list: # item = self.downloads[hash] # item.dow.setDownloadRate(rate) else: item.dow.setDownloadRate(rate)