class MagnetLink: def __init__(self, url, callback, timeout, max_connections=30): """ If the URL conforms to a magnet link, the .torrent info is downloaded and returned to CALLBACK. """ # _callback is called when the metadata is retrieved. self._callback = callback dn, xt, trs = self.parse_url(url) # _name is the unicode name suggested for the swarm. assert dn is None or isinstance(dn, unicode), "DN has invalid type: %s" % type(dn) self._name = dn # _info_hash is the 20 byte binary info hash that identifies # the swarm. assert isinstance(xt, str), "XT has invalid type: %s" % type(xt) assert len(xt) == 20, "XT has invalid length: %d" % len(xt) self._info_hash = xt # _tracker is an optional tracker address. self._trackers = trs # _swarm is a MiniBitTorrent.MiniSwarm instance that connects # to peers to retrieve the metadata. self.magnet_handler = MagnetHandler.get_instance() self.magnet_handler.add_magnet(self, timeout) self._swarm = MiniSwarm(self._info_hash, self.magnet_handler.get_raw_server(), self.metainfo_retrieved, max_connections=max_connections) def get_infohash(self): return self._info_hash def get_name(self): return self._name def retrieve(self): """ Start retrieving the metainfo Returns True when attempting to obtain the metainfo, in this case CALLBACK will always be called. Otherwise False is returned, in this case CALLBACK will not be called. """ if self._info_hash: # todo: catch the result from get_peers and call its stop # method. note that this object does not yet contain a # stop method... dht = mainlineDHT.dht dht.get_peers(self._info_hash, Id(self._info_hash), self.potential_peers_from_dht, 0) try: if self._trackers and any(tracker.startswith("http") for tracker in self._trackers): MiniTracker(self._swarm, self._trackers) except: print_exc() return True else: print >> sys.stderr, "No Infohash" return False def potential_peers_from_dht(self, lookup_id, peers, node=None): if peers: self._swarm.add_potential_peers(peers) def metainfo_retrieved(self, metainfo, peers=[]): """ Called when info part for metadata is retrieved. If we have more metadata, we will add it at this point. PEERS optionally contains a list of valid BitTorrent peers, found during metadata download, to help bootstrap the download. """ assert isinstance(metainfo, dict) assert isinstance(peers, list) if __debug__: for address in peers: assert isinstance(address, tuple) assert len(address) == 2 assert isinstance(address[0], str) assert isinstance(address[1], int) # create metadata metadata = {"info":metainfo} if self._trackers: if len(self._trackers) > 1: metadata["announce-list"] = [self._trackers] metadata["announce"] = self._trackers[0] else: metadata["nodes"] = [] if peers: metadata["initial peers"] = peers self._callback(metadata) self.close() def close(self): self.magnet_handler.remove_magnet(self) if DEBUG: print >> sys.stderr, "Magnet.close()" # close all MiniBitTorrent activities self._swarm.close() @staticmethod def parse_url(url): # url must be a magnet link dn = None xt = None trs = [] if DEBUG: print >> sys.stderr, "Magnet.parse_url()", url schema, netloc, path, query, fragment = urlsplit(url) if schema == "magnet": # magnet url's do not conform to regular url syntax (they # do not have a netloc.) This causes path to contain the # query part. if "?" in path: pre, post = path.split("?", 1) if query: query = "&".join((post, query)) else: query = post for key, value in parse_qsl(query): if key == "dn": # convert to unicode dn = value.decode() elif key == "xt" and value.startswith("urn:btih:"): #vliegendhart: Adding support for base32 in magnet links (BEP 0009) encoded_infohash = value[9:49] if len(encoded_infohash) == 32: xt = b32decode(encoded_infohash) else: xt = unhexlify(encoded_infohash) elif key == "tr": trs.append(value) if DEBUG: print >> sys.stderr, "Magnet.parse_url() NAME:", dn if DEBUG: print >> sys.stderr, "Magnet.parse_url() HASH:", xt if DEBUG: print >> sys.stderr, "Magnet.parse_url() TRACS:", trs return (dn, xt, trs)