# Written by Jie Yang, Arno Bakker # see LICENSE.txt for license information import sys import md5 import os from sha import sha from time import time, ctime from traceback import print_exc from BitTornado.bencode import bencode, bdecode from BitTornado.BT1.MessageID import * from Swapper.utilities import isValidInfohash, show_permid from Swapper.CacheDB.CacheDBHandler import TorrentDBHandler from Swapper.unicode import name2unicode # Python no recursive imports? # from overlayswarm import overlay_infohash overlay_infohash = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' DEBUG = False ## Arno: FIXME: 8MB too large, IMHO. Max_Torrent_Size = 8*1024*1024 # 8MB torrent = about 80G files class MetadataHandler: __single = None def __init__(self): if MetadataHandler.__single: raise RuntimeError, "MetadataHandler is singleton" MetadataHandler.__single = self def getInstance(*args, **kw): if MetadataHandler.__single is None: MetadataHandler(*args, **kw) return MetadataHandler.__single getInstance = staticmethod(getInstance) def register(self, secure_overlay, dlhelper, launchmany, config_dir): self.secure_overlay = secure_overlay self.dlhelper = dlhelper self.config_dir = os.path.join(config_dir, 'torrent2') #TODO: user can set it self.torrent_db = TorrentDBHandler() def handleMessage(self, permid, message): t = message[0] if t == GET_METADATA: if DEBUG: print >> sys.stderr,"metadata: Got GET_METADATA",len(message),show_permid(permid) return self.send_metadata(permid, message) elif t == METADATA: if DEBUG: print >> sys.stderr,"metadata: Got METADATA",len(message) return self.got_metadata(permid, message) else: if DEBUG: print >> sys.stderr,"metadata: UNKNOWN OVERLAY MESSAGE", ord(t) return False def send_metadata_request(self, permid, torrent_hash): if not isValidInfohash(torrent_hash): return False try: metadata_request = bencode(torrent_hash) self.secure_overlay.addTask(permid, GET_METADATA + metadata_request) except: return False return True def send_metadata(self, conn, message): try: torrent_hash = bdecode(message[1:]) except: print_exc() if DEBUG: print >> sys.stderr,"metadata: GET_METADATA: error becoding" return False if not isValidInfohash(torrent_hash): if DEBUG: print >> sys.stderr,"metadata: GET_METADATA: invalid hash" return False torrent_path = self.find_torrent(torrent_hash) if not torrent_path: if DEBUG: print >> sys.stderr,"metadata: GET_METADATA: not torrent path" return False torrent_data = self.read_torrent(torrent_path) if torrent_data: self.do_send_metadata(conn, torrent_hash, torrent_data) else: if DEBUG: print >> sys.stderr,"metadata: GET_METADATA: no torrent data to send" pass return True def do_send_metadata(self, permid, torrent_hash, torrent_data): torrent = {'torrent_hash':torrent_hash, 'metadata':torrent_data} metadata_request = bencode(torrent) if DEBUG: print >> sys.stderr,"metadata: send metadata", len(metadata_request) self.secure_overlay.addTask(permid,METADATA + metadata_request) def find_torrent(self, torrent_hash): """ lookup torrent file and return torrent path """ data = self.torrent_db.getTorrent(torrent_hash) if not data: return None try: filepath = os.path.join(data['torrent_dir'], data['torrent_name']) if os.path.isfile(filepath): return filepath except: return None def read_torrent(self, torrent_path): try: file = open(torrent_path, "rb") torrent_data = file.read() file.close() torrent_size = len(torrent_data) if DEBUG: print >> sys.stderr,"metadata: read torrent", torrent_path, torrent_size if torrent_size > Max_Torrent_Size: return None if DEBUG: print >> sys.stderr,"metadata: sending torrent", torrent_size, md5.new(torrent_data).hexdigest() return torrent_data except: return None def addTorrentToDB(self, src, torrent_hash, metadata): metainfo = bdecode(metadata) namekey = name2unicode(metainfo) # convert info['name'] to type(unicode) info = metainfo['info'] torrent = {} torrent['torrent_dir'], torrent['torrent_name'] = os.path.split(src) torrent_info = {} torrent_info['name'] = info.get(namekey, '') length = 0 nf = 0 if info.has_key('length'): length = info.get('length', 0) nf = 1 elif info.has_key('files'): for li in info['files']: nf += 1 if li.has_key('length'): length += li['length'] torrent_info['length'] = length torrent_info['num_files'] = nf torrent_info['announce'] = metainfo.get('announce', '') torrent_info['announce-list'] = metainfo.get('announce-list', '') torrent_info['creation date'] = metainfo.get('creation date', 0) torrent['info'] = torrent_info self.torrent_db.addTorrent(torrent_hash, torrent, new_metadata=True) self.torrent_db.sync() def save_torrent(self, torrent_hash, metadata): if DEBUG: print >> sys.stderr,"metadata: Store torrent", md5.new(torrent_hash).digest(), "on disk" #TODO: file_name = self.get_filename(metadata, torrent_hash) save_path = os.path.join(self.config_dir, file_name) self.addTorrentToDB(save_path, torrent_hash, metadata) self.write_torrent(metadata, self.config_dir, file_name) def get_filename(self, metadata, torrent_hash): # assign a name for the torrent. add a timestamp if it exists. metainfo = bdecode(metadata) file_name = sha(torrent_hash).hexdigest()+'.torrent' _path = os.path.join(self.config_dir, file_name) if os.path.exists(_path): file_name = str(time()) + '_' + file_name return file_name # exceptions will be handled by got_metadata() def write_torrent(self, metadata, dir, name): try: if not os.access(dir,os.F_OK): os.mkdir(dir) save_path = os.path.join(dir, name) file = open(save_path, 'wb') file.write(metadata) file.close() if DEBUG: print >> sys.stderr,"metadata: write torrent", save_path, len(metadata), hash(metadata) except: print_exc() print >> sys.stderr, "metadata: write torrent failed" def valid_metadata(self, torrent_hash, metadata): metainfo = bdecode(metadata) infohash = sha(bencode(metainfo['info'])).digest() if infohash != torrent_hash: print >> sys.stderr, "metadata: infohash doesn't match the torrent " + \ "hash. Required: " + `torrent_hash` + ", but got: " + `infohash` return False return True def got_metadata(self, conn, message): try: message = bdecode(message[1:]) except: return False if not isinstance(message, dict): return False try: torrent_hash = message['torrent_hash'] if not isValidInfohash(torrent_hash): return False metadata = message['metadata'] if not self.valid_metadata(torrent_hash, metadata): return False if DEBUG: torrent_size = len(metadata) print >> sys.stderr,"metadata: Recvd torrent", torrent_size, md5.new(metadata).hexdigest() self.save_torrent(torrent_hash, metadata) if self.dlhelper is not None: self.dlhelper.call_dlhelp_task(torrent_hash, metadata) except Exception, msg: print_exc() print >> sys.stderr,"metadata: Received metadata is broken", msg return False return True