#!/usr/bin/python from ConfigParser import ConfigParser,NoOptionError,NoSectionError from threading import Thread,Event from binascii import a2b_hex from sha import sha from log import get_logger import urllib,os import time try: from BitTorrent.bencode import bdecode,bencode except ImportError: try: from BitTornado.bencode import bdecode,bencode except ImportError: print 'BitTorrent not found' import sys sys.exit() from __init__ import mapbase64,decode_version,decode_build,cmp_version from scrape import getScrapeData import policy from i18n import * def hours(n): if n == -1: return '--:--:--' if n == 0: return '' n = long(n) h, r = divmod(n, 60 * 60) m, sec = divmod(r, 60) if h > 1000000: return '--:--:--' ret = '%d:%02d:%02d' % (h, m, sec) if len(ret) > 8: ret = '%d hrs' % (h) return ret class IDGenerator: def __init__(self,start=0): self.id = start def reset(self,id=0): self.id = id def generate(self): self.id += 1 return str(self.id) id_generator = IDGenerator() def decode_peer_id(id): try: client = _decode_peer_id(id) except Exception,why: log_unknown_id(id,id,'yes') client = 'N/A E' return client def _decode_peer_id(id): quoted_id = id if id[:3] == '0x ': id = a2b_hex(id[3:]) if id[0] == '"' and id[-1] == '"': id = id[1:-1] if len(id) == 40: id = a2b_hex(id) if id[0] == 'S': if id[6:6+3] == '---': ver = list(id[1:1+3]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'S '+'.'.join(ver) return name if ord(id[8]) == 0: ver = list(id[1:1+3]) for i in range(3): ver[i] = str(ord(ver[i])) name = 'S '+'.'.join(ver) return name if id[4:4+7] == 'Plus---': ver = list(id[1:1+3]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'S '+'.'.join(ver)+' Plus' return name if id[0] == 'T': if id[6:6+3] == '---': ver = list(id[1:4]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'BT '+'.'.join(ver) return name if id[6] in mapbase64 and id[7] in mapbase64 and id[8] in mapbase64: ver = list(id[1:4]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'BT '+'.'.join(ver)+' M' return name if id[0] == 'A': if id[6:6+3] == '---': ver = list(id[1:4]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'ABC '+'.'.join(ver) return name if id[6] in mapbase64 and id[7] in mapbase64 and id[8] in mapbase64: ver = list(id[1:4]) for i in range(3): ver[i] = str(int(ver[i],16)) name = 'ABC '+'.'.join(ver)+' M' return name if id[0] == 'Q': if id[6:6+3] == '---' or \ (id[6] in mapbase64 and id[7] in mapbase64 and id[8] in mapbase64): ver_str = decode_version(id[1:4]) name = 'BTQ '+ver_str if cmp_version(ver_str,'0.0.7') >= 0: build = decode_build(id[9:9+3]) if build: name += ' '+str(build) return name elif id[1] in mapbase64 and id[2] in mapbase64 and id[3] in mapbase64: ver_str = decode_version(id[1:4]) name = 'BTQ '+ver_str return name else: return 'BTQ' if id[0:0+9] == 'eXeXeemBT': name = 'eXeem' return name if id[0:0+3] == 'XBT': name = 'XBT '+'.'.join(list(id[3:3+3])) return name if id[1:1+2] == 'AZ': name = 'Az '+'.'.join(list(id[3:3+4])) return name if id[1:1+2] == 'UT': name = 'uT '+'.'.join(list(id[3:3+2]))+'.'+str(int(id[5:5+2])) return name if id[5:5+7] == 'Azureus': name = 'Az 2.0.3.2' return name if id[2:2+2] == 'BS': if ord(id[1]) == 0: name = 'BS v1' if ord(id[1]) == 2: name = 'BS v2' return name if id[0] == 'U': if id[8] == '-': name = 'UPnP '+'.'.join(list(id[1:1+3])) return name if id[0] == 'M' and id[2] == '-' and id[4] == '-' and id[6:6+2] == '--': name = 'ML %s.%s.%s' % (id[1],id[3],id[5]) return name if id[:4] == 'exbc': name = 'BC ' name += '%c.%c%c' % (str(ord(id[4])),str(ord(id[5])/10),str(ord(id[5])%10)) return name if id[1:3] == 'BC': name = 'BC ' name += '%d.%d' % (int(id[3:5]),int(id[5:7])) return name if id[:7] == 'turbobt': name = 'TBT '+id[7:7+5] return name if id[:12] == '-G3g3rmz ': name = 'G3' return name if id[:3] == '-G3': name = 'G3' return name if id[:7] == 'Plus---' or (id[:4] == 'Plus' and id[7] == '-'): name = 'BT Plus' return name if id[:16] == 'Deadman Walking-' or id[:6] == 'BTDWV-': name = 'Deadman' return name if id[1:1+2] == 'LT': name = 'libt '+'.'.join(list(id[3:3+4])) return name if id[1:1+2] == 'TS': name = 'TS '+'.'.join(list(id[3:3+4])) return name if id[1:1+2] == 'MT': name = 'MT '+'.'.join(list(id[3:3+4])) return name if id[:12] == '\000\000\000\000\000\000\000\000\000\003\003\003': name = 'Snark' return name if id[:5] == 'btuga': name = 'BTugaXP' return name if id[4:4+6] == 'btfans': name = 'SBT' return name if id[:10] == 'DansClient': name = 'XT' return name if id[:14] == '\000\000\000\000\000\000\000\000\000\000\000\000aa': name = 'Exp 3.2.1b2' return name if id[:14] == '\000\000\000\000\000\000\000\000\000\000\000\000\000\000': name = 'Exp 3.1' return name if id[:12] == '\000\000\000\000\000\000\000\000\000\000\000\000': name = 'Generic' return name if id[:2] == '[]': return 'N/A' log_unknown_id(id,quoted_id) return quoted_id def log_unknown_id(id,quoted_id,error='no'): pol = policy.get_policy() if pol(policy.LOG_UNKNOWN_ID): unknown_log = pol.get_path(policy.UNKNOWN_ID_FILE) try: fd = open(unknown_log,'a') fd.write(id+' '+quoted_id+' '+error+'\n') fd.close() except Exception,why: pass class QueueEntry: all_fields = ['filename','progress','btstatus','eta', 'dlspeed','ulspeed', 'ratio','peers','seeds','copies','dlsize','ulsize', 'peeravgprogress','totalspeed','totalsize', 'error','id','infohash','title','activity','announce', 'dest_path','saveas','peer_id', 'added_time','started_time','finished_time','stopped_time'] default_fields = ['filename','progress','btstatus','eta', 'dlspeed','ulspeed', 'ratio','peers','seeds','copies','dlsize','ulsize', 'peeravgprogress','totalspeed','totalsize','title', 'added_time','started_time','finished_time','stopped_time'] header_fields = {'filename': 'Filename', 'progress': 'Progress', 'btstatus': 'BT Status', 'eta': 'ETA', 'dlspeed': 'DL Speed', 'ulspeed': 'UL Speed', 'ratio': '%UL/DL', 'peers': '#Peers', 'seeds': '#Seeds', 'copies': '#Copies', 'dlsize': 'DL Size', 'ulsize': 'UL Size', 'peeravgprogress': 'Peer Avg Progress', 'totalspeed': 'Total Speed', 'totalsize': 'Total Size', 'error': 'Error', 'id': 'ID', 'peer_id': 'Peer ID', 'announce': 'Announce', 'infohash': 'Info Hash', 'title': 'Title', 'dest_path': 'Destination', 'saveas': 'Save As', 'activity': 'Activity', 'added_time': 'Added', 'started_time': 'Started', 'finished_time': 'Finished', 'stopped_time': 'Stopped'} modifiable_vars = ['title','dest_path','saveas','file','priority', 'dlsize','ulsize', 'old_dlsize','old_ulsize','recheck'] def __init__(self,file,priority=-1,dest_path='',id_generator=id_generator): if id_generator: self.id = id_generator.generate() else: self.id = 0 self.file = file self.global_policy = policy.get_policy() if priority >= 0: self.priority = priority else: self.priority = self.global_policy(policy.DEFAULT_PRIORITY) self.local_policy = policy.EntryPolicy() self.state = STATE_WAITING self.dest_path = os.path.abspath(dest_path) self.saveas = '' self.dlsize = self.ulsize = self.old_dlsize = self.old_ulsize = 0 self.error = '' self.gather_info() self.statistics = None self.spew = None self.dow = self.listen_port = None self.share_ratio = 0.0 self.currentseed = '?' self.currentpeer = '?' self.recheck = 0 self.peers = [] self.added_time = -1 self.started_time = -1 self.finished_time = -1 self.stopped_time = -1 self.added() self.update_info(fractionDone=0.0, timeEst=0.0,downRate=0.0,upRate=0.0, activity='None',statistics=None, spew=None,sizeDone=0,force=True) def get_spew(self): if not self.spew: return [] spew = [] for i in self.spew: d = {} d['ip'] = i['ip'] d['rport'] = i['rport'] d['direction'] = i['direction'] d['uprate'] = '%.1f' % (i['uprate']/1000.0) d['downrate'] = '%.1f' % (i['downrate']/1000.0) if i['dtotal'] is not None: d['dtotal'] = '%.2f' % (float(i['dtotal'])/(1 << 20)) else: d['dtotal'] = '%.2f' % 0 if i['utotal'] is not None: d['utotal'] = '%.2f' % (float(i['utotal'])/(1 << 20)) else: d['utotal'] = '%.2f' % 0 d['completed'] = '%.1f%%' % (float(i['completed'])*1000.0/10) if i['speed'] is not None: d['speed'] = '%.1f' % (i['speed']/1000.0) else: d['speed'] = '%.1f' % 0 d['id'] = i['id'] d['client'] = decode_peer_id(i['id']) spew.append(d) return spew def get_peers(self): if not self.peers: return [] peers = [] for i in self.peers: d = {} d['ip'] = i['ip'] d['rport'] = i['port'] d['id'] = i['id'] d['client'] = decode_peer_id(i['id']) peers.append(d) return peers def get(self,key=None): if key == 'filename': return self.file elif key == 'progress': return '%.1f%%' % (self.fraction_done*100.0,) elif key == 'btstatus': return self.state elif key == 'eta': return hours(self.time_est) elif key == 'dlspeed': return '%.1f %s' % (self.down_rate/1000.0,UNIT_kBps) elif key == 'ulspeed': return '%.1f %s' % (self.up_rate/1000.0,UNIT_kBps) elif key == 'ratio': #if not self.statistics: # return '0.0%' if self.dlsize == 0: return '---.-%' return '%.1f%%' % (self.ulsize*100.0/self.dlsize,) elif key == 'peers': if not self.statistics: return '0(%s)' % self.currentpeer return '%d(%s)' % (self.statistics.numPeers,self.currentpeer) elif key == 'seeds': if not self.statistics: return '0(%s)' % self.currentseed return '%d(%s)' % (self.statistics.numSeeds,self.currentseed) elif key == 'copies': if not self.statistics: return 0 return self.statistics.numCopies elif key == 'dlsize': #if not self.statistics: # return '0.00 %s' % UNIT_MB return '%.2f %s' % (self.dlsize/float(1 << 20),UNIT_MB) elif key == 'ulsize': #if not self.statistics: # return '0.00 %s' % UNIT_MB return '%.2f %s' % (self.ulsize/float(1 << 20),UNIT_MB) elif key == 'peeravgprogress': if not self.statistics: return '0.0%' return '%.1f%%' % self.statistics.percentDone elif key == 'totalspeed': if not self.statistics: return '0 %s' % UNIT_kBps return '%.0f %s' % (self.statistics.torrentRate/1000.0,UNIT_kBps) elif key == 'totalsize': #if not self.statistics: # return '%.2f %s' % (self.size/float(1 << 20),UNIT_MB) return '%.2f %s' % (self.size/float(1 << 20),UNIT_MB) elif key == 'activity': return self.activity elif key == 'infohash': return self.infohash elif key == 'title': return self.title elif key == 'dest_path': return self.dest_path elif key == 'saveas': return self.saveas elif key == 'id': return self.id elif key == 'error': return self.error elif key == 'announce': return self.announce elif key == 'peer_id': try: peer_id = getattr(self.dow,'myid','-'*20) except: peer_id = '-'*20 return peer_id elif key == 'added_time': return self.added_time elif key == 'started_time': return self.started_time elif key == 'finished_time': return self.finished_time elif key == 'stopped_time': return self.stopped_time elif key == None: dict = {} for key in self.all_fields: dict[key] = self.get(key) return dict else: return None def gather_info(self): self.fin = 0 resp = self.get_meta() info = resp['info'] self.announce = resp['announce'] infohash_sha = sha(bencode(info)) self.infohash_bin = infohash_sha.digest() self.infohash = infohash_sha.hexdigest() self.title = info['name'] if info.has_key('length'): self.size = info['length'] title = self.handle_name(self.title) if self.title != title: self.title = self.saveas = title self.length = 1 else: self.size = 0 for i in info['files']: self.size += i['length'] self.length = len(info['files']) self.piece_length = info['piece length'] self.size_mb = self.size/1024/1024 def handle_name(self,name): nameu = name.upper() if (nameu.startswith('AVSEQ') or nameu.startswith('MUSIC')) and \ nameu.endswith('.DAT') and \ len(nameu) == 11: name = self.infohash+'-'+name return name def get_metadata(self): fd = open(self.file,'rb') data = fd.read() fd.close() return data def get_meta(self): meta = bdecode(self.get_metadata()) if meta.has_key('announce-list'): announce = meta['announce'] found = 0 for tier in meta['announce-list']: for tracker in tier: if tracker == announce: found = 1 if not found: meta['announce-list'].append([announce]) return meta def update_scrape(self,interactive=0): if interactive: try: meta = self.get_meta() getScrapeData(meta,self) except: pass return if hasattr(self,'scrape_thread') and self.scrape_thread.isAlive(): return try: meta = self.get_meta() self.scrape_thread = Thread(target=getScrapeData,args=(meta,self)) self.scrape_thread.start() except ValueError: pass def update_peers(self,peers): self.peers = peers def update_info(self,fractionDone=None, timeEst=None,downRate=None,upRate=None, activity=None,statistics=None,spew=None,sizeDone=None, force=False, **kws): if not force and not self.state in [STATE_RUNNING,STATE_SEEDING]: return if fractionDone != None: self.fraction_done = fractionDone if timeEst != None: self.time_est = timeEst if downRate != None: self.down_rate = downRate if upRate != None: self.up_rate = upRate if activity != None: self.activity = activity if statistics != None: self.statistics = statistics self.dlsize = self.old_dlsize+self.statistics.downTotal self.ulsize = self.old_ulsize+self.statistics.upTotal #down = self.old_dlsize+self.statistics.downTotal #up = self.old_ulsize+self.statistics.upTotal try: self.share_ratio = float(self.ulsize)/float(self.dlsize) except ZeroDivisionError: self.share_ratio = 0.0 if spew != None: self.spew = spew if sizeDone == None: sizeDone = long(self.fraction_done*self.size) if sizeDone != None: self.size_done = sizeDone if self.size_done > self.old_dlsize+self.dlsize: self.dlsize = self.size_done-self.old_dlsize #if self.statistics: # if self.old_dlsize+self.dlsize > \ # self.statistics.downmeasure.get_total(): # self.statistics.downmeasure.total = self.old_dlsize+self.dlsize # if self.old_ulsize+self.ulsize > \ # self.statistics.upmeasure.get_total(): # self.statistics.upmeasure.total = self.old_ulsize+self.ulsize def clear_stat(self): self.down_rate = self.up_rate = 0 # self.error = '' self.activity = 'None' self.time_est = 0 self.currentpeer = self.currentseed = '?' self.spew = None self.statistics = None def get_policy(self): return self.local_policy or self.global_policy def paused(self): self.old_ulsize = self.ulsize self.old_dlsize = self.dlsize self.clear_stat() self.stopped() def __repr__(self): return '<%s,%s>' % (self.id,self.file) def __hash__(self): return self.id def __cmp__(self,other): if other is None: return 1 try: return cmp(int(self.id),int(other.id)) except ValueError: return cmp(self.id,other.id) def load(self,cfg,section=None): section = section or self.infohash try: self.id = cfg.get(section,'id') self.priority = cfg.getint(section,'priority') self.dest_path = cfg.get(section,'dest_path') self.old_dlsize = long(cfg.get(section,'old_dlsize')) self.old_ulsize = long(cfg.get(section,'old_ulsize')) self.added_time = float(cfg.get(section,'added_time')) self.started_time = float(cfg.get(section,'started_time')) self.finished_time = float(cfg.get(section,'finished_time')) self.stopped_time = float(cfg.get(section,'stopped_time')) self.local_policy = policy.EntryPolicy(cfg,section) except NoOptionError: pass except NoSectionError: pass def save(self,cfg,section=None): section = section or self.infohash cfg.add_section(section) cfg.set(section,'torrent',self.file) cfg.set(section,'id',self.id) cfg.set(section,'priority',str(self.priority)) cfg.set(section,'dest_path',self.dest_path) cfg.set(section,'old_dlsize',str(max(self.old_dlsize,self.dlsize))) cfg.set(section,'old_ulsize',str(max(self.old_ulsize,self.ulsize))) cfg.set(section,'added_time',str(self.added_time)) cfg.set(section,'started_time',str(self.started_time)) cfg.set(section,'finished_time',str(self.finished_time)) cfg.set(section,'stopped_time',str(self.stopped_time)) self.local_policy.save(cfg,section) def added(self): self.added_time = time.time() def started(self): self.started_time = time.time() def finished(self): if self.finished_time < 0: self.finished_time = time.time() def stopped(self): self.stopped_time = time.time() class Queue: def __init__(self): self.q = [] self.queue_file = policy.get_policy().get_path(policy.QUEUE_FILE) #self.queue_file = os.path.join(root_path,QUEUE_FILE) self.log = get_logger() def load(self,file=None): reassign_id = policy.get_policy().get(policy.REASSIGN_ID) file = file or self.queue_file cfg = ConfigParser() cfg.read(file) self.log.debug('read %s\n' % file) sections = cfg.sections() if reassign_id: def _cmp(sa,sb): ia,ib = int(cfg.get(sa,'id')),int(cfg.get(sb,'id')) return cmp(ia,ib) sections.sort(_cmp) for section in sections: self.log.debug('added %s\n' % section) try: j = QueueEntry(cfg.get(section,'torrent'),id_generator=None) j.load(cfg) if reassign_id: j.id = id_generator.generate() self.add(j,save=False) except NoOptionError: pass except IOError: pass except ValueError: self.log.debug('unable to load %s' % section) self.q.sort() # for line in fd.readlines(): # try: # j = QueueEntry(line.strip()) # self.add(j) # except IOError: # pass def save(self,file=None): file = file or self.queue_file cfg = ConfigParser() for j in self.q: j.save(cfg) # for j in self.q: # fd.write(j.file+'\n') try: fd = open(file,'w') cfg.write(fd) fd.close() except Exception,why: return def size(self): return len(self.q) def add(self,item,save=True): while self.get_from_id(item.id): item.id = id_generator.generate() if self.get_from_infohash(item.infohash): return self.q.append(item) if save: self.save() def remove(self,item): self.q.remove(item) self.save() def get(self,key=None): if key == None: return self.q elif type(key) == type(0): return self.get_from_index(key) elif len(key) == 40: return self.get_from_infohash(key) else: return self.get_from_id(key) def get_next(self): q = self.q def priority_cmp(a,b): return cmp(a.priority,a.priority) q.sort(priority_cmp) for j in q: if j.state == STATE_WAITING: return j return None def get_from_infohash(self,infohash): for j in self.q: if j.infohash == infohash: return j return None def get_from_index(self,index): return self.q[index] def get_from_id(self,id): for j in self.q: if j.id == id: return j return None class History: def __init__(self): self.history_file = policy.get_policy().get_path(policy.HISTORY_FILE) #self.history_file = os.path.join(root_path,HISTORY_FILE) self.new_history = [] self.history = [] def load(self): self.history = [] try: fd = open(self.history_file,'r') for line in fd.readlines(): line = line.strip() if line and not line in self.history: self.history.append(line) fd.close() except Exception,why: pass def add(self,item): if not self.exists(item): self.history.append(item) self.new_history.append(item) def exists(self,item): return item in self.history or item in self.new_history def save(self): try: fd = open(self.history_file,'a+') for item in self.new_history: fd.write(item+'\n') fd.close() except Exception,why: pass