class TorrentClient(object): def __init__(self): self.conn = None def connect(self, host, username, password): if self.conn is not None: return self.conn if not host: return False try: if username and password: self.conn = Client(host, user=username, password=password) else: self.conn = Client(host) except: logger.error('Could not connect to %h' % host) return False return self.conn def find_torrent(self, hash): try: return self.conn.get_torrent(hash) except KeyError: logger.error('torrent %s does not exist') return False def get_torrent(self, torrent): torrent = self.conn.get_torrent(torrent.hashString) torrent_files = [] torrent_directory = os.path.normpath(torrent.downloadDir) for f in torrent.files().itervalues(): if not os.path.normpath(f['name']).startswith(torrent_directory): file_path = os.path.join(torrent_directory, f['name'].lstrip('/')) else: file_path = f['name'] torrent_files.append(file_path) torrent_info = { 'hash': torrent.hashString, 'name': torrent.name, 'folder': torrent.downloadDir, 'completed': torrent.progress == 100, 'label': 'None', ## labels not supported in transmission - for when it's in transmission 'files': torrent_files, 'upload_total': torrent.uploadedEver, 'download_total': torrent.downloadedEver, 'ratio': torrent.ratio, 'total_filesize': torrent.sizeWhenDone, 'time_started': torrent.date_started } logger.debug(torrent_info) return torrent_info if torrent_info else False def start_torrent(self, torrent): return torrent.start() def stop_torrent(self, torrent): return torrent.stop() def load_torrent(self, filepath): if any([ mylar.TRANSMISSION_DIRECTORY is None, mylar.TRANSMISSION_DIRECTORY == '', mylar.TRANSMISSION_DIRECTORY == 'None' ]): down_dir = mylar.CHECK_FOLDER else: down_dir = mylar.TRANSMISSION_DIRECTORY torrent = self.conn.add_torrent(filepath, download_dir=down_dir) torrent.start() return self.get_torrent(torrent) def delete_torrent(self, torrent): deleted = [] files = torrent.files() for file_item in files.itervalues(): file_path = os.path.join(torrent.downloadDir, file_item['name']) deleted.append(file_path) if len(files) > 1: torrent_path = os.path.join(torrent.downloadDir, torrent.name) for path, _, _ in os.walk(torrent_path, topdown=False): deleted.append(path) if self.conn.remove_torrent(torrent.hashString, delete_data=True): return deleted else: logger.error('Unable to delete %s' % torrent.name) return []
class TorrentClient(object): def __init__(self): self.conn = None def connect(self, host, username, password): if self.conn is not None: return self.conn if not host: return False try: if username and password: self.conn = Client( host, user=username, password=password ) else: self.conn = Client(host) except: logger.error('Could not connect to %h' % host) return False return self.conn def find_torrent(self, hash): try: return self.conn.get_torrent(hash) except KeyError: logger.error('torrent %s does not exist') return False def get_torrent(self, torrent): torrent = self.conn.get_torrent(torrent.hashString) torrent_files = [] torrent_directory = os.path.normpath(torrent.downloadDir) for f in torrent.files().itervalues(): if not os.path.normpath(f['name']).startswith(torrent_directory): file_path = os.path.join(torrent_directory, f['name'].lstrip('/')) else: file_path = f['name'] torrent_files.append(file_path) torrent_info = { 'hash': torrent.hashString, 'name': torrent.name, 'folder': torrent.downloadDir, 'completed': torrent.progress == 100, 'label': 'None', ## labels not supported in transmission - for when it's in transmission 'files': torrent_files, 'upload_total': torrent.uploadedEver, 'download_total': torrent.downloadedEver, 'ratio': torrent.ratio, 'total_filesize': torrent.sizeWhenDone, 'time_started': torrent.date_started } logger.debug(torrent_info) return torrent_info if torrent_info else False def start_torrent(self, torrent): return torrent.start() def stop_torrent(self, torrent): return torrent.stop() def load_torrent(self, filepath): if any([mylar.TRANSMISSION_DIRECTORY is None, mylar.TRANSMISSION_DIRECTORY == '', mylar.TRANSMISSION_DIRECTORY == 'None']): down_dir = mylar.CHECK_FOLDER else: down_dir = mylar.TRANSMISSION_DIRECTORY if filepath.startswith('magnet'): torrent = self.conn.add_torrent('%s' % filepath, download_dir=down_dir) else: torrent = self.conn.add_torrent('file://%s' % filepath, download_dir=down_dir) torrent.start() return self.get_torrent(torrent) def delete_torrent(self, torrent): deleted = [] files = torrent.files() for file_item in files.itervalues(): file_path = os.path.join(torrent.downloadDir, file_item['name']) deleted.append(file_path) if len(files) > 1: torrent_path = os.path.join(torrent.downloadDir, torrent.name) for path, _, _ in os.walk(torrent_path, topdown=False): deleted.append(path) if self.conn.remove_torrent(torrent.hashString, delete_data=True): return deleted else: logger.error('Unable to delete %s' % torrent.name) return []
class TransmissionClient(client.TorrentClient): """ Backend implementation for transmission """ config_key = "client_transmission" _torrent_list_args = None def __init__(self, host=None, port=None, user=None, password=None): super(TransmissionClient, self).__init__() if not host: host = config.get_default(self.config_key, "host", "localhost") self.host = host if not port: port = config.get_default(self.config_key, "port", DEFAULT_PORT, int) self.port = port if not user: user = config.get_default(self.config_key, "user", None) self.user = user if not password: password = config.get_default(self.config_key, "password", None) self.password = password self.client = None self.connect() def client_version(self): version = "{}.{} ({})".format(*self.client.server_version) return version def connect(self): try: self.client = Client(address=self.host, port=self.port, user=self.user, password=self.password) except TransmissionError as err: if err.original.code == 111: self.log.error("Failed to connect to transmission-daemon, is it running?") elif err.original.code == 113: self.log.error("No route to host") else: self.log.exception("Error connecting to transmission server") raise def add(self, data, download_dir=None): """ Add a torrent to the client :param data: Torrent data to load in :type data: TorrentData :param download_dir: Path on deluge server to store download :type download_dir: basestring :return: Status of successful load (according to deluge) :rtype: bool """ try: torrent = Torrent.from_str(data.torrent_data) try: self.torrent_status(torrent.info_hash) except KeyError: pass else: self.log.warn("Tried to load duplicate info hash: {}".format(torrent.info_hash)) return True torrent_data = b64encode(data.torrent_data) res = self.client.add_torrent(torrent_data, download_dir=download_dir) except TransmissionError as err: try: msg = err._message except AttributeError: msg = err.message if "duplicate torrent" in msg: self.log.warning("Tried to add duplicate torrent file") return True self.log.exception(err) return False return res torrent_add = add def current_speeds(self): """ Fetch the speeds from the session instance :return: Upload, Download speeds in bytes/s :rtype: tuple """ ses = self.client.session_stats() return ses.uploadSpeed, ses.downloadSpeed def torrent_list(self): """ Get a list of currently loaded torrents from the client :return: :rtype: """ if not self._torrent_list_args: self._torrent_list_args = get_arguments('torrent-get', self.client.rpc_version) self._torrent_list_args.extend(['seeders', 'peersKnown', 'peersGettingFromUs', 'peersSendingToUs', 'isPrivate']) torrents = self.client.get_torrents(arguments=self._torrent_list_args) torrent_data = list() for torrent in torrents: data = client.ClientTorrentData( info_hash=torrent.hashString, name=torrent.name, ratio=torrent.ratio, up_rate=torrent.rateUpload, dn_rate=torrent.rateDownload, up_total=torrent.uploadedEver, dn_total=torrent.downloadedEver, size=torrent.sizeWhenDone, size_completed=torrent.sizeWhenDone-torrent.leftUntilDone, # TODO peer values are wrong peers=torrent.peersGettingFromUs, total_peers=0, seeders=torrent.peersSendingToUs, total_seeders=0, priority=torrent.priority, private=torrent.isPrivate, state=torrent.status, progress=torrent.progress ) torrent_data.append(data) return torrent_data def torrent_remove(self, torrent_id, remove_data=False): """ Remove a torrent from the backend client via its torrentID supplied by the torrent daemon :param remove_data: Remove the torrent data file as well as .torrent :type remove_data: bool :param torrent_id: TorrentID provided by transmission :type torrent_id: int """ self.client.remove_torrent(torrent_id, delete_data=remove_data) def torrent_peers(self, info_hash): torrent = self.client.get_torrent(info_hash, arguments=['id', 'hashString', 'peers']) peers = [] session = Session() # TODO country code lookup for peer in torrent.peers: peers.append({ 'client': peer['clientName'], 'down_speed': peer['rateToClient'], 'up_speed': peer['rateToPeer'], 'progress': peer['progress'], 'ip': peer['address'], 'country': geoip.find_country_code(session, peer['address']) }) return peers def torrent_start(self, info_hash): self.client.start_torrent(info_hash) return True def torrent_pause(self, info_hash): self.client.stop(info_hash) return True def torrent_files(self, info_hash): files = [] file_set = self.client.get_files(info_hash) for v in file_set.values(): for file_info in [f for f in v.values()]: files.append(client.ClientFileData( path=file_info['name'], progress=file_info['size'] - file_info['completed'], size=file_info['size'], priority=file_info['priority'] )) break return files def torrent_speed(self, info_hash): speed = self.client.get_torrent(info_hash, arguments=['id', 'hashString', 'rateDownload', 'rateUpload']) return speed.rateDownload, speed.rateUpload def disconnect(self): return True def torrent_status(self, info_hash): key_map = { 'info_hash': 'hashString', 'up_rate': 'rateUpload', 'dn_rate': 'rateDownload', 'up_total': 'uploadedEver', 'dn_total': 'downloadedEver', 'size': 'totalSize', 'size_completed': 'percentDone', # wrong 'seeders': lambda t: len(t.peers), 'total_seeders': lambda t: len(t.peers), 'peers': 'peersConnected', 'total_peers': lambda t: len(t.peers), 'priority': 'queue_position', 'private': 'isPrivate', 'state': 'status', 'progress': '', 'tracker_status': '', 'next_announce': lambda t: t.trackerStats[0]['nextAnnounceTime'], 'save_path': 'downloadDir', 'piece_length': 'pieceSize', 'num_pieces': 'pieceCount', 'time_added': 'addedDate', 'distributed_copies': lambda t: functools.reduce(lambda a,b: a+b, [p['progress'] for p in t.peers], 0), 'active_time': '', #'seeding_time': 'secondsSeeding', Not found in my version? trans 2.8.4 'num_files': lambda t: len(torrent.files()), 'queue_position': 'queue_position' } torrent = self.client.get_torrent(info_hash) detail = client.ClientTorrentDataDetail(info_hash=info_hash) for key in detail.key_list: val = key_map.get(key, None) if val: if callable(val): detail[key] = val(torrent) else: detail[key] = getattr(torrent, val) return detail def torrent_recheck(self, info_hash): self.client.verify_torrent(info_hash) return True def torrent_reannounce(self, info_hash): self.client.reannounce_torrent(info_hash) return True def torrent_queue_up(self, info_hash): self.client.queue_up(info_hash) return True def torrent_queue_down(self, info_hash): self.client.queue_down(info_hash) return True def torrent_queue_top(self, info_hash): self.client.queue_top(info_hash) return True def torrent_queue_bottom(self, info_hash): self.client.queue_bottom(info_hash) return True def torrent_move_data(self, info_hash, dest): self.client.move_torrent_data(info_hash, dest) return True
class TransmissionClient(client.TorrentClient): """ Backend implementation for transmission """ config_key = "client_transmission" _torrent_list_args = None def __init__(self, host=None, port=None, user=None, password=None): super(TransmissionClient, self).__init__() if not host: host = config.get_default(self.config_key, "host", "localhost") self.host = host if not port: port = config.get_default(self.config_key, "port", DEFAULT_PORT, int) self.port = port if not user: user = config.get_default(self.config_key, "user", None) self.user = user if not password: password = config.get_default(self.config_key, "password", None) self.password = password self.client = None self.connect() def client_version(self): version = "{}.{} ({})".format(*self.client.server_version) return version def connect(self): try: self.client = Client(address=self.host, port=self.port, user=self.user, password=self.password) except TransmissionError as err: if err.original.code == 111: self.log.error( "Failed to connect to transmission-daemon, is it running?") elif err.original.code == 113: self.log.error("No route to host") else: self.log.exception("Error connecting to transmission server") raise def add(self, data, download_dir=None): """ Add a torrent to the client :param data: Torrent data to load in :type data: TorrentData :param download_dir: Path on deluge server to store download :type download_dir: basestring :return: Status of successful load (according to deluge) :rtype: bool """ try: torrent = Torrent.from_str(data.torrent_data) try: self.torrent_status(torrent.info_hash) except KeyError: pass else: self.log.warn("Tried to load duplicate info hash: {}".format( torrent.info_hash)) return True torrent_data = b64encode(data.torrent_data) res = self.client.add_torrent(torrent_data, download_dir=download_dir) except TransmissionError as err: try: msg = err._message except AttributeError: msg = err.message if "duplicate torrent" in msg: self.log.warning("Tried to add duplicate torrent file") return True self.log.exception(err) return False return res torrent_add = add def current_speeds(self): """ Fetch the speeds from the session instance :return: Upload, Download speeds in bytes/s :rtype: tuple """ ses = self.client.session_stats() return ses.uploadSpeed, ses.downloadSpeed def torrent_list(self): """ Get a list of currently loaded torrents from the client :return: :rtype: """ if not self._torrent_list_args: self._torrent_list_args = get_arguments('torrent-get', self.client.rpc_version) self._torrent_list_args.extend([ 'seeders', 'peersKnown', 'peersGettingFromUs', 'peersSendingToUs', 'isPrivate' ]) torrents = self.client.get_torrents(arguments=self._torrent_list_args) torrent_data = list() for torrent in torrents: data = client.ClientTorrentData( info_hash=torrent.hashString, name=torrent.name, ratio=torrent.ratio, up_rate=torrent.rateUpload, dn_rate=torrent.rateDownload, up_total=torrent.uploadedEver, dn_total=torrent.downloadedEver, size=torrent.sizeWhenDone, size_completed=torrent.sizeWhenDone - torrent.leftUntilDone, # TODO peer values are wrong peers=torrent.peersGettingFromUs, total_peers=0, seeders=torrent.peersSendingToUs, total_seeders=0, priority=torrent.priority, private=torrent.isPrivate, state=torrent.status, progress=torrent.progress) torrent_data.append(data) return torrent_data def torrent_remove(self, torrent_id, remove_data=False): """ Remove a torrent from the backend client via its torrentID supplied by the torrent daemon :param remove_data: Remove the torrent data file as well as .torrent :type remove_data: bool :param torrent_id: TorrentID provided by transmission :type torrent_id: int """ self.client.remove_torrent(torrent_id, delete_data=remove_data) def torrent_peers(self, info_hash): torrent = self.client.get_torrent( info_hash, arguments=['id', 'hashString', 'peers']) peers = [] session = Session() # TODO country code lookup for peer in torrent.peers: peers.append({ 'client': peer['clientName'], 'down_speed': peer['rateToClient'], 'up_speed': peer['rateToPeer'], 'progress': peer['progress'], 'ip': peer['address'], 'country': geoip.find_country_code(session, peer['address']) }) return peers def torrent_start(self, info_hash): self.client.start_torrent(info_hash) return True def torrent_pause(self, info_hash): self.client.stop(info_hash) return True def torrent_files(self, info_hash): files = [] file_set = self.client.get_files(info_hash) for v in list(file_set.values()): for file_info in [f for f in list(v.values())]: files.append( client.ClientFileData(path=file_info['name'], progress=file_info['size'] - file_info['completed'], size=file_info['size'], priority=file_info['priority'])) break return files def torrent_speed(self, info_hash): speed = self.client.get_torrent( info_hash, arguments=['id', 'hashString', 'rateDownload', 'rateUpload']) return speed.rateDownload, speed.rateUpload def disconnect(self): return True def torrent_status(self, info_hash): key_map = { 'info_hash': 'hashString', 'up_rate': 'rateUpload', 'dn_rate': 'rateDownload', 'up_total': 'uploadedEver', 'dn_total': 'downloadedEver', 'size': 'totalSize', 'size_completed': 'percentDone', # wrong 'seeders': lambda t: len(t.peers), 'total_seeders': lambda t: len(t.peers), 'peers': 'peersConnected', 'total_peers': lambda t: len(t.peers), 'priority': 'queue_position', 'private': 'isPrivate', 'state': 'status', 'progress': '', 'tracker_status': '', 'next_announce': lambda t: t.trackerStats[0]['nextAnnounceTime'], 'save_path': 'downloadDir', 'piece_length': 'pieceSize', 'num_pieces': 'pieceCount', 'time_added': 'addedDate', 'distributed_copies': lambda t: functools.reduce(lambda a, b: a + b, [p['progress'] for p in t.peers], 0), 'active_time': '', #'seeding_time': 'secondsSeeding', Not found in my version? trans 2.8.4 'num_files': lambda t: len(torrent.files()), 'queue_position': 'queue_position' } torrent = self.client.get_torrent(info_hash) detail = client.ClientTorrentDataDetail(info_hash=info_hash) for key in detail.key_list: val = key_map.get(key, None) if val: if callable(val): detail[key] = val(torrent) else: detail[key] = getattr(torrent, val) return detail def torrent_recheck(self, info_hash): self.client.verify_torrent(info_hash) return True def torrent_reannounce(self, info_hash): self.client.reannounce_torrent(info_hash) return True def torrent_queue_up(self, info_hash): self.client.queue_up(info_hash) return True def torrent_queue_down(self, info_hash): self.client.queue_down(info_hash) return True def torrent_queue_top(self, info_hash): self.client.queue_top(info_hash) return True def torrent_queue_bottom(self, info_hash): self.client.queue_bottom(info_hash) return True def torrent_move_data(self, info_hash, dest): self.client.move_torrent_data(info_hash, dest) return True