def __init__(self, config, persistence): self.conn = Client(config.address, port=config.port, user=config.user, password=config.password) self.secret = config.secret self.persistence = persistence
def __init__(self, session): Screen.__init__(self, session) HelpableScreen.__init__(self) try: self.transmission = Client( address = config.plugins.emission.hostname.value, port = config.plugins.emission.port.value, user = config.plugins.emission.username.value, password = config.plugins.emission.password.value ) except TransmissionError as te: self.transmission = None self["SetupActions"] = HelpableActionMap(self, "SetupActions", { "ok": (self.ok, _("show details")), "cancel": (self.close, _("close")), }) self["ColorActions"] = HelpableActionMap(self, "ColorActions", { "green": (self.bandwidth, _("open bandwidth settings")), "yellow": (self.prevlist, _("show previous list")), "blue": (self.nextlist, _("show next list")), }) self["MenuActions"] = HelpableActionMap(self, "MenuActions", { "menu": (self.menu, _("open context menu")), }) self["key_red"] = StaticText(_("Close")) self["key_green"] = StaticText(_("Bandwidth")) self["key_yellow"] = StaticText("") self["key_blue"] = StaticText("") self["all_text"] = StaticText(_("All")) self["downloading_text"] = StaticText(_("DL")) self["seeding_text"] = StaticText(_("UL")) self["upspeed"] = StaticText("") self["downspeed"] = StaticText("") self["torrents"] = StaticText("") self["all_sel"] = Pixmap() self["downloading_sel"] = Pixmap() self["seeding_sel"] = Pixmap() self['list'] = List([]) self.list_type = config.plugins.emission.last_tab.value self.sort_type = config.plugins.emission.last_sort.value self.showHideSetTextMagic() self.timer = eTimer() self.timer.callback.append(self.updateList) self.timer.start(0, 1)
def reader(): """Read data and dispatch""" client = Client(user=USERNAME, password=PASSWORD) stats = client.session_stats() dispatch_value('transmission_speed', [ stats.cumulative_stats['downloadedBytes'], stats.cumulative_stats['uploadedBytes'] ]) dispatch_value('transmission_count', [stats.activeTorrentCount, stats.pausedTorrentCount])
def reader(): """Read data and dispatch""" client = Client(user=USERNAME, password=PASSWORD) stats = client.session_stats() dispatch_value('transmission_speed', [ stats.cumulative_stats['downloadedBytes'], stats.cumulative_stats['uploadedBytes'] ]) dispatch_value('transmission_count', [ stats.activeTorrentCount, stats.pausedTorrentCount ])
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 configureCallback(self): try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value) except TransmissionError as te: self.transmission = None self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) else: self.updateList()
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 createClient(*args, **kwargs): test_name = None if 'test_name' in kwargs: test_name = kwargs['test_name'] del kwargs['test_name'] kwargs['http_handler'] = TestHTTPHandler(test_name) return Client(*args, **kwargs)
class transmission_client: def __init__(self, host, port, username, password): self.client = Client(host, port=port, user=username, password=password) self.torrent = os.path.join( os.path.expanduser('~'), 'Downloads', 'E5340FB5C061E4E53618F41B48D7E1CEA445BB02.torrent') print(self.torrent) def download(self): try: self.client.add_torrent(self.torrent, download_dir='/home/goku/Documents') print( 'Add torrent into the download queue, the file will be saved at ' ) except ImportError: pass
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?") else: self.log.exception("Error connecting to transmission server")
def connect(self, address, port, user, password): try: self.client = TClient(address, port, user, password) except TransmissionError as err: raise BTCexception(" {} TransmissionError {}".format(address, err)) except socket.timeout as err: raise BTCexception(" {} Socket error {}".format(address, err)) else: self.dict_client['name'] = address
def __init__(self, session): Screen.__init__(self, session) HelpableScreen.__init__(self) try: self.transmission = Client( address = config.plugins.emission.hostname.value, port = config.plugins.emission.port.value, user = config.plugins.emission.username.value, password = config.plugins.emission.password.value ) except TransmissionError as te: self.transmission = None self["SetupActions"] = HelpableActionMap(self, "SetupActions", { "ok": (self.ok, _("show details")), "cancel": (self.close, _("close")), }) self["ColorActions"] = HelpableActionMap(self, "ColorActions", { "green": (self.bandwidth, _("open bandwidth settings")), "yellow": (self.prevlist, _("show previous list")), "blue": (self.nextlist, _("show next list")), }) self["MenuActions"] = HelpableActionMap(self, "MenuActions", { "menu": (self.menu, _("open context menu")), }) self["key_red"] = StaticText(_("Close")) self["key_green"] = StaticText(_("Bandwidth")) self["key_yellow"] = StaticText("") self["key_blue"] = StaticText("") self["all_text"] = StaticText(_("All")) self["downloading_text"] = StaticText(_("DL")) self["seeding_text"] = StaticText(_("UL")) self["upspeed"] = StaticText("") self["downspeed"] = StaticText("") self["torrents"] = StaticText("") self["all_sel"] = Pixmap() self["downloading_sel"] = Pixmap() self["seeding_sel"] = Pixmap() self['list'] = List([]) self.list_type = config.plugins.emission.last_tab.value self.sort_type = config.plugins.emission.last_sort.value self.showHideSetTextMagic() self.timer = eTimer() self.timer_conn = self.timer.timeout.connect(self.updateList) self.timer.start(0, 1)
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 run(config, logger): logger.debug('Daemonized successfully') c = Client(address=config['address'],user=config['username'],password=config['password']) logger.debug('Connected to transmission successfully') while True: logger.debug('Starting run') torrents=c.info() torrentnames = map(lambda x: x.fields['name'], torrents.values()) todelete = find_torrents_to_delete(torrents, logger, config['max_days'], config['series_max_days']) toadd = find_torrents_to_add(config['feeds'], torrentnames, logger) if config['dry_run'] == False: # Let's delete all old torrents if len(todelete) >0: c.remove(todelete, delete_data=True, timeout=config['timeout']) if len(toadd) > 0: for a in toadd: try: c.add_uri(a) except (TransmissionError, URLError) as e: if isinstance(e, TransmissionError): e.message = e._message if isinstance(e, URLError): e.message = e.reason if e.message == 'Query failed with result "duplicate torrent".': logger.info('Torrent: %s already downloading' % (a)) else: logger.warning('Failed to add: %s. Reason: %s' % (a, e.message)) logger.debug('Ending run') time.sleep(config['interval'])
def simplerss_update_callback(id=None): try: from Plugins.Extensions.SimpleRSS.plugin import rssPoller except ImportError as e: pass # should not happen since the poller has to be active for us to be called :-) else: errors = 0 # we only check the "new items" feed currently since we do not keep track of the files we downloaded if id is None: client = None for item in rssPoller.newItemFeed.history: for file in item[3]: if file.mimetype == "application/x-bittorrent": if client is None: client = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value ) try: # XXX: we might want to run this in the background cause this might block... client.add_url(file.path) except TransmissionError: errors += 1 # Inform the user if an error occured # XXX: we could also either open a notification per torrent (success/failure) # or create a custom screen which gives this information in a # more understandable and less annyoing way (e.g. a list of torrent # names with an X or hook symbol next to it...) if errors > 0: from Tools.Notifications import AddPopup from Screens.MessageBox import MessageBox AddPopup( _("Failed to add %d torrents.") % (errors), MessageBox.TYPE_WARNING, 5, NOTIFICATIONID)
def filescan_open(item, session, **kwargs): client = Client(address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value) added = 0 errors = 0 # XXX: a list of failed/added torrents would be nice, see comment in simplerss_update_callback for each in item: try: if client.add_url(each): added += 1 except TransmissionError: errors += 1 from Screens.MessageBox import MessageBox session.open(MessageBox, _("%d Torrents(s) were scheduled for download, %d failed.") % (added, errors), type=MessageBox.TYPE_INFO, timeout=5)
class TransmissionBroker: def __init__(self, config, persistence): self.conn = Client(config.address, port=config.port, user=config.user, password=config.password) self.secret = config.secret self.persistence = persistence @staticmethod def pretty_torrents_list(torrents): info_list = list() for torrent in torrents: info_list.append('%s: %s, %s : %d%%' % (torrent.id, torrent.name, torrent.status, torrent.percentDone * 100)) return '\n'.join(info_list) def retrieve_list(self, chat_id): torrents = self.conn.get_torrents() return TransmissionBroker.pretty_torrents_list(torrents) def add_torrent(self, chat_id, url): self.conn.add_torrent(url) def remove_torrent(self, chat_id, torrent_ids): # Check is not embedded to transmissionrpc module, so we have to do it ourselves missing_torrents = list() torrents = self.conn.get_torrents() for tid in torrent_ids: id_found = False for torrent in torrents: if tid == torrent.id: id_found = True break if not id_found: missing_torrents.append(tid) if len(missing_torrents) > 0: raise TransmissionError('Torrents %s not found' % missing_torrents) self.conn.remove_torrent(torrent_ids) def check_chat_authorization(self, chat_id): if not self.persistence.check_chat_id(chat_id): raise NotAuthorizedChatException() def authorize_chat(self, chat_id, secret): if self.secret == secret: self.persistence.add_chat_id(chat_id) return True else: return False
class TransmissionClient(client.TorrentClient): _config_key = "transmission" def __init__(self, host=None, port=None, user=None, password=None): 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.connect() def client_version(self): return "Transmission XX" def connect(self): try: self.client = Client(self.host, port=self.port, user=self.user, password=self.password) except TransmissionError as err: if err.original.code == 111: logger.error("Failed to connect to transmission-daemon, is it running?") else: logger.exception("Error connecting to transmission server") def add(self, data, download_dir=None): try: encoded_data = b64encode(data) res = self.client.add(encoded_data, download_dir=download_dir) except TransmissionError, err: try: msg = err._message except AttributeError: msg = err.message if "duplicate torrent" in msg: logger.warning("Tried to add duplicate torrent file") return True logger.exception(err) return False return res
def configureCallback(self): try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value, ) except TransmissionError as te: self.transmission = None self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) else: self.updateList()
class TransmissionBroker: def __init__(self): print(f"user={TS_USER}, password={PASSWORD}") self.conn = Client(ADDRESS, PORT, user=TS_USER, password=PASSWORD) self.persistence = PERSISTENCE_FILE @staticmethod def pretty_torrents_list(torrents): info_list = [] for torrent in torrents: percent = torrent.percentDone * 100 info_list.append( f'{torrent.id}:{torrent.name}, {torrent.status}:{percent}' ) return '\n'.join(info_list) def retrieve_list(self, chat_id): torrents = self.conn.get_torrents() return TransmissionBroker.pretty_torrents_list(torrents) def add_torrent(self, chat_id, url): self.conn.add_torrent(url) def remove_torrent(self, chat_id, torrent_ids): # Check is not embedded to transmissionrpc module, so we have to do it ourselves missing_torrents = list() torrents = self.conn.get_torrents() for tid in torrent_ids: id_found = False for torrent in torrents: if tid == torrent.id: id_found = True break if not id_found: missing_torrents.append(tid) if len(missing_torrents) > 0: raise TransmissionError(f'Torrents {missing_torrents} not found') self.conn.remove_torrent(torrent_ids)
class TorC: MANAGE_ACTION_INIT = 1 MANAGE_ACTION_CLEAN = 2 TOR_TYPE_SINGLE = 1 TOR_TYPE_SERIES = 2 Videos = ["mp4", "mkv", "avi", "mpeg", "mpg", "mov", "mov", "wmv"] def __init__(self, user, password): #TODO: host/port/url self.client = Client(user=user, password=password) self.torrent_props = {} for t in self.client.get_torrents(): self.sort_by_episodes(t) def get_files(self, torrent = None): if not torrent: ret = [] for t, tinfo in self.client.get_files().items(): for fid, finfo in tinfo.items(): if finfo["name"].split(".")[-1] in TorC.Videos: ret.append(finfo) return ret return [x for x in list(torrent.files().values()) if x["name"].split(".")[-1] in TorC.Videos ] def download_dir(self): return self.client.session.download_dir def add_magnet(self, magnet): torrent = self.client.add_torrent(magnet) while not torrent.files(): time.sleep(.1) torrent.update() torrent.uploadLimit = 20 torrent.stop() torrent.update() self.manage_files(torrent, TorC.MANAGE_ACTION_INIT) return torrent def status(self): ret = [] for t in self.client.get_torrents(): t.update() tor_data = [t.name, float(t.rateDownload), []] if t.hashString not in self.torrent_props: tor_data[2].append({ "name": t.name, "completed": float(t.percentDone)*100}) else: files_list = t.files() for f in self.torrent_props[t.hashString]["episodes"]: fdict = files_list[f[0]] if fdict["size"] == fdict["completed"] or not fdict["selected"]: continue tor_data[2].append({"name": fdict["name"], "completed": float(100*fdict["completed"])/fdict["size"]}), ret.append(tor_data) return ret def clean(self): toremove = [] for t in self.client.get_torrents(): t.update() if t.hashString not in self.torrent_props: if t.status == "seeding": toremove.append(t.id) else: if not self.start_using_priority(t): toremove.append(t.id) if toremove: self.client.remove_torrent(toremove) def init_torrent_download(self, torrent): files_list = torrent.files() self.torrent_props[torrent.hashString] = {} #Skip all files: skip_files = {torrent.id : {}} for idx, f in enumerate(files_list): skip_files[torrent.id][idx] = {"selected": False} self.client.set_files(skip_files) video_files = { k:v for k,v in files_list.items() if v["name"].split(".")[-1].lower() in TorC.Videos } if len(video_files) > 1: sizesorted_files_list = sorted(video_files.values(), key=lambda x: x["size"], reverse=True) if sizesorted_files_list[0]["size"] > 5* sizesorted_files_list[1]["size"]: video_files = [sizesorted_files_list[0]] self.torrent_props[torrent.hashString]["episodes"] = self.sort_by_episodes(torrent) if len(video_files) > 2: #Type is series self.sort_by_episodes(torrent) self.start_using_priority(torrent) else: start_files = {} for idx,f in video_files.items(): start_files[torrent.id] = {idx:{"selected": True}} self.client.set_files(start_files) def start_using_priority(self, torrent, startat=0): #TODO startat function files_list = torrent.files() pos = startat f = files_list[self.torrent_props[torrent.hashString]["episodes"][pos][0]] while pos < len(self.torrent_props[torrent.hashString]["episodes"]) and f["size"] == f["completed"]: pos += 1 try: f = files_list[self.torrent_props[torrent.hashString]["episodes"][pos][0]] except IndexError: pass if pos == len(self.torrent_props[torrent.hashString]["episodes"]): return False mod_files = {torrent.id:{self.torrent_props[torrent.hashString]["episodes"][pos][0]:{"selected": True, "priority": "high"}}} if pos < len(self.torrent_props[torrent.hashString]["episodes"]) - 1: mod_files[torrent.id][self.torrent_props[torrent.hashString]["episodes"][pos+1][0]] = {"priority":"normal", "selected": True} self.client.set_files(mod_files) return True def manage_files(self, torrent, action): files_list = torrent.files() if action == TorC.MANAGE_ACTION_INIT: self.init_torrent_download(torrent) def sort_by_episodes(self, torrent): files_list = torrent.files() video_files = { k:v for k,v in files_list.items() if v["name"].split(".")[-1].lower() in TorC.Videos } p = re.compile("S\d+E\d+", re.IGNORECASE) episodes = { re.search(p, v["name"]).group().upper(): (k, v["name"]) for k,v in video_files.items()} self.torrent_props[torrent.hashString]= {"episodes": [y[1] for y in sorted(episodes.items(), key = lambda x: x[0])]} def start_all(self): for t in self.client.get_torrents(): t.start() def stop_all(self): for t in self.client.get_torrents(): t.stop()
def __init__(self, user, password): #TODO: host/port/url self.client = Client(user=user, password=password) self.torrent_props = {} for t in self.client.get_torrents(): self.sort_by_episodes(t)
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
def __init__(self, host, port, username, password): self.client = Client(host, port=port, user=username, password=password) self.torrent = os.path.join( os.path.expanduser('~'), 'Downloads', 'E5340FB5C061E4E53618F41B48D7E1CEA445BB02.torrent') print(self.torrent)
def upload_torrents(): parser = ArgumentParser(description='Upload .torrent files to a Transmission server.') parser.add_argument('host', type=str, help='Transmission host[:port] (port defaults to 9091)') parser.add_argument('-u', '--user', type=str, default=getuser(), help='Transmission username (defaults to current user)') parser.add_argument('-d', '--directory', type=str, default='~/Downloads', help='directory to search for .torrent files (defaults to ~/Downloads)') parser.add_argument('-k', '--keep', action='store_true', help='do not trash .torrent files after uploading') args = parser.parse_args() t = Terminal() directory = Path(normpath(expanduser(expandvars(args.directory)))).resolve() print('\nScanning {} for {} files...'.format(t.bold(str(directory)), t.bold('.torrent')), end='') torrent_files = sorted(directory.glob('*.torrent')) if torrent_files: print(t.bold_bright_green(' Found {}'.format(len(torrent_files)))) else: print(t.bold_bright_red(' None found')) return password = getpass('\n{}\'s password: '******':' in args.host: hostname, port = args.host.split(':') client = Client(address=hostname, port=port, user=args.user, password=password) else: client = Client(address=args.host, user=args.user, password=password) print(t.bold_bright_green('Connected')) except TransmissionError as e: print(t.bold_bright_red('Connection failed') + ' to Transmission at ' + t.bold(args.host)) return uploaded, failed = [], [] # pad the index so that the brackets are all vertically aligned width = len(str(len(torrent_files))) for i, f in enumerate(torrent_files): prefix = ('[{:>{width}}]').format(i + 1, width=width) print('\n' + t.bold(prefix + ' Uploading') + '\t' + f.name) try: torrent = client.add_torrent('file://' + str(f)) uploaded.append(f) print(t.bold_bright_green(prefix + ' Started') + '\t' + t.bright_cyan(torrent.name)) except TransmissionError as e: failed.append(f) print(t.bold_bright_red(prefix + ' Error') + '\t' + t.bright_black(str(e))) if not args.keep: # convert to list to force iteration trash = lambda f: send2trash(str(f)) list(map(trash, uploaded)) print('') if uploaded: print('{} file{} uploaded successfully{}'.format( t.bold_bright_green(str(len(uploaded))), 's' if len(uploaded) != 1 else '', ' and moved to trash' if not args.keep else '' )) if failed: print('{} file{} failed to upload'.format( t.bold_bright_red(str(len(failed))), 's' if len(failed) != 1 else '' ))
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 []
def __init__(self): print(f"user={TS_USER}, password={PASSWORD}") self.conn = Client(ADDRESS, PORT, user=TS_USER, password=PASSWORD) self.persistence = PERSISTENCE_FILE
class EmissionOverview(Screen, HelpableScreen): skin = """<screen name="EmissionOverview" title="Torrent Overview" position="75,135" size="565,330"> <ePixmap position="0,0" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" /> <ePixmap position="140,0" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" /> <ePixmap position="280,0" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" /> <ePixmap position="420,0" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" /> <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="all_sel" pixmap="skin_default/epg_now.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="5,47" zPosition="2" source="all_text" render="Label" halign="center" font="Regular;18" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="downloading_sel" pixmap="skin_default/epg_next.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="111,47" zPosition="2" source="downloading_text" render="Label" halign="center" font="Regular;18" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="seeding_sel" pixmap="skin_default/epg_more.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="212,47" zPosition="2" source="seeding_text" render="Label" halign="center" font="Regular;18" /> <widget source="torrents" render="Label" size="240,22" position="320,47" halign="right" font="Regular;18" /> <!--ePixmap size="550,230" alphatest="on" position="5,65" pixmap="skin_default/border_epg.png" /--> <widget source="list" render="Listbox" position="5,70" size="550,225" scrollbarMode="showAlways"> <convert type="TemplatedMultiContent"> {"template": [ MultiContentEntryText(pos=(2,2), size=(555,22), text = 1, font = 0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER), MultiContentEntryText(pos=(2,26), size=(555,18), text = 2, font = 1, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER), (eListboxPythonMultiContent.TYPE_PROGRESS, 0, 44, 537, 6, -3), ], "fonts": [gFont("Regular", 20),gFont("Regular", 16)], "itemHeight": 51 } </convert> </widget> <widget source="upspeed" render="Label" size="150,20" position="5,300" halign="left" font="Regular;18" /> <widget source="downspeed" render="Label" size="150,20" position="410,300" halign="right" font="Regular;18" /> </screen>""" def __init__(self, session): Screen.__init__(self, session) HelpableScreen.__init__(self) try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value, ) except TransmissionError as te: self.transmission = None self["SetupActions"] = HelpableActionMap( self, "SetupActions", {"ok": (self.ok, _("show details")), "cancel": (self.close, _("close"))} ) self["ColorActions"] = HelpableActionMap( self, "ColorActions", { "green": (self.bandwidth, _("open bandwidth settings")), "yellow": (self.prevlist, _("show previous list")), "blue": (self.nextlist, _("show next list")), }, ) self["MenuActions"] = HelpableActionMap(self, "MenuActions", {"menu": (self.menu, _("open context menu"))}) self["key_red"] = StaticText(_("Close")) self["key_green"] = StaticText(_("Bandwidth")) self["key_yellow"] = StaticText("") self["key_blue"] = StaticText("") self["all_text"] = StaticText(_("All")) self["downloading_text"] = StaticText(_("DL")) self["seeding_text"] = StaticText(_("UL")) self["upspeed"] = StaticText("") self["downspeed"] = StaticText("") self["torrents"] = StaticText("") self["all_sel"] = Pixmap() self["downloading_sel"] = Pixmap() self["seeding_sel"] = Pixmap() self["list"] = List([]) self.list_type = config.plugins.emission.last_tab.value self.sort_type = config.plugins.emission.last_sort.value self.showHideSetTextMagic() self.timer = eTimer() self.timer.callback.append(self.updateList) self.timer.start(0, 1) def bandwidthCallback(self, ret=None): if self.transmission is not None and ret: try: self.transmission.set_session(**ret) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) self.updateList() def menuCallback(self, ret=None): ret and ret[1]() def newDlCallback(self, ret=None): if self.transmission is not None and ret: try: res = self.transmission.add_url(ret) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) else: if not res: self.session.open( MessageBox, _("Torrent could not be scheduled not download!"), type=MessageBox.TYPE_ERROR, timeout=5, ) self.updateList() def newDl(self): self.timer.stop() self.session.openWithCallback(self.newDlCallback, TorrentLocationBox) def sortCallback(self, ret=None): if ret is not None: self.sort_type = config.plugins.emission.last_sort.value = ret[1] config.plugins.emission.last_sort.save() self.updateList() def sort(self): self.timer.stop() self.session.openWithCallback( self.sortCallback, ChoiceBox, _("Which sorting method do you prefer?"), [ (_("by eta"), SORT_TYPE_TIME), (_("by progress"), SORT_TYPE_PROGRESS), (_("by age"), SORT_TYPE_ADDED), (_("by speed"), SORT_TYPE_SPEED), ], ) def pauseShown(self): if self.transmission is not None: self.transmission.stop([x[0].id for x in self.list]) def unpauseShown(self): if self.transmission is not None: self.transmission.start([x[0].id for x in self.list]) def pauseAll(self): if self.transmission is None: return try: self.transmission.stop([x.id for x in self.transmission.list().values()]) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) def unpauseAll(self): if self.transmission is None: return try: self.transmission.start([x.id for x in self.transmission.list().values()]) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) def configure(self): # reload(EmissionSetup) self.timer.stop() self.session.openWithCallback(self.configureCallback, EmissionSetup.EmissionSetup) def menu(self): self.session.openWithCallback( self.menuCallback, ChoiceBox, _("What do you want to do?"), [ (_("Configure connection"), self.configure), (_("Change sorting"), self.sort), (_("Add new download"), self.newDl), (_("Pause shown"), self.pauseShown), (_("Unpause shown"), self.unpauseShown), (_("Pause all"), self.pauseAll), (_("Unpause all"), self.unpauseAll), ], ) def showHideSetTextMagic(self): list_type = self.list_type if list_type == LIST_TYPE_ALL: self["all_sel"].show() self["downloading_sel"].hide() self["seeding_sel"].hide() self["key_yellow"].setText(_("Seeding")) self["key_blue"].setText(_("Download")) elif list_type == LIST_TYPE_DOWNLOADING: self["all_sel"].hide() self["downloading_sel"].show() self["seeding_sel"].hide() self["key_yellow"].setText(_("All")) self["key_blue"].setText(_("Seeding")) else: # if list_type == LIST_TYPE_SEEDING: self["all_sel"].hide() self["downloading_sel"].hide() self["seeding_sel"].show() self["key_yellow"].setText(_("Download")) self["key_blue"].setText(_("All")) def prevlist(self): self.timer.stop() list_type = self.list_type if list_type == LIST_TYPE_ALL: self.list_type = LIST_TYPE_SEEDING elif list_type == LIST_TYPE_DOWNLOADING: self.list_type = LIST_TYPE_ALL else: # if list_type == LIST_TYPE_SEEDING: self.list_type = LIST_TYPE_DOWNLOADING self.showHideSetTextMagic() self.updateList() def nextlist(self): self.timer.stop() list_type = self.list_type if list_type == LIST_TYPE_ALL: self.list_type = LIST_TYPE_DOWNLOADING elif list_type == LIST_TYPE_DOWNLOADING: self.list_type = LIST_TYPE_SEEDING else: # if list_type == LIST_TYPE_SEEDING: self.list_type = LIST_TYPE_ALL self.showHideSetTextMagic() self.updateList() def prevItem(self): self["list"].selectPrevious() cur = self["list"].getCurrent() return cur and cur[0] def nextItem(self): self["list"].selectNext() cur = self["list"].getCurrent() return cur and cur[0] def bandwidth(self): if self.transmission is None: return # reload(EmissionBandwidth) self.timer.stop() try: sess = self.transmission.get_session() rpc_version = self.transmission.rpc_version except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) # XXX: this seems silly but cleans the gui and restarts the timer :-) self.updateList() else: self.session.openWithCallback( self.bandwidthCallback, EmissionBandwidth.EmissionBandwidth, sess, False, rpc_version ) def configureCallback(self): try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value, ) except TransmissionError as te: self.transmission = None self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5, ) else: self.updateList() def updateList(self, *args, **kwargs): # XXX: if we are not connected do NOT restart timer, it's useless anyway if self.transmission is None: return try: lst = list(self.transmission.list().values()) session = self.transmission.session_stats() except TransmissionError: # XXX: some hint in gui would be nice self["list"].setList([]) self["torrents"].setText("") self["upspeed"].setText("") self["downspeed"].setText("") else: sort_type = self.sort_type if sort_type == SORT_TYPE_TIME: def cmp_func(x, y): x_eta = x.fields["eta"] y_eta = y.fields["eta"] if x_eta > -1 and y_eta < 0: return 1 if x_eta < 0 and y_eta > -1: return -1 # note: cmp call inversed because lower eta is "better" return cmp(y_eta, x_eta) or cmp(x.progress, y.progress) lst.sort(cmp=cmp_func, reverse=True) elif sort_type == SORT_TYPE_PROGRESS: lst.sort(key=lambda x: x.progress, reverse=True) elif sort_type == SORT_TYPE_SPEED: lst.sort(key=lambda x: (x.rateDownload, x.rateUpload), reverse=True) # SORT_TYPE_ADDED is what we already have list_type = self.list_type if list_type == LIST_TYPE_ALL: lst = [ (x, x.name.encode("utf-8", "ignore"), str(x.eta or "?:??:??").encode("utf-8"), int(x.progress)) for x in lst ] elif list_type == LIST_TYPE_DOWNLOADING: lst = [ (x, x.name.encode("utf-8", "ignore"), str(x.eta or "?:??:??").encode("utf-8"), int(x.progress)) for x in lst if x.status == "downloading" ] else: # if list_type == LIST_TYPE_SEEDING: lst = [ (x, x.name.encode("utf-8", "ignore"), str(x.eta or "?:??:??").encode("utf-8"), int(x.progress)) for x in lst if x.status == "seeding" ] self["torrents"].setText(_("Active Torrents: %d/%d") % (session.activeTorrentCount, session.torrentCount)) self["upspeed"].setText(_("UL: %d kb/s") % (session.uploadSpeed / 1024)) self["downspeed"].setText(_("DL: %d kb/s") % (session.downloadSpeed / 1024)) # XXX: this is a little ugly but this way we have the least # visible distortion :-) index = min(self["list"].index, len(lst) - 1) self["list"].setList(lst) self["list"].index = index self.list = lst self.timer.startLongTimer(10) def ok(self): cur = self["list"].getCurrent() if self.transmission is not None and cur: # reload(EmissionDetailview) self.timer.stop() self.session.openWithCallback( self.updateList, EmissionDetailview.EmissionDetailview, self.transmission, cur[0], self.prevItem, self.nextItem, ) def close(self): self.timer.stop() config.plugins.emission.last_tab.value = self.list_type config.plugins.emission.last_tab.save() Screen.close(self)
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
#!/usr/bin/env python3 from transmissionrpc import Client if __name__ == "__main__": client = Client('localhost', port=9091) torrents = client.get_torrents() for torrent in torrents: if "S02E05" in torrent.name: client.remove_torrent(torrent.id)
class TransmissionClient(BitTorrentClient): def __init__(self): BitTorrentClient.__init__(self, TRANSMISSION_CLIENT) self.dict_client = {} def connect(self, address, port, user, password): try: self.client = TClient(address, port, user, password) except TransmissionError as err: raise BTCexception(" {} TransmissionError {}".format(address, err)) except socket.timeout as err: raise BTCexception(" {} Socket error {}".format(address, err)) else: self.dict_client['name'] = address def get_torrents(self): tr_torrents = self.client.get_torrents() self.dict_client['torrents'] = [] for _torrent in tr_torrents: _torrent_dict = { u'name': _torrent.name, u'hash': _torrent.hashString, u'progress': _torrent.progress, u'status': _torrent.status, u'date_active': _torrent.date_active, u'date_added': _torrent.date_added, u'date_started': _torrent.date_started, u'date_done': _torrent.date_done, u'eta': -1, u'error': _torrent.error, u'errorString': _torrent.errorString[:350], } #TODO: Warning eta can return exception ValuerError try: _torrent_dict[u'eta'] = timedelta.total_seconds(_torrent.eta) except ValueError: pass self.dict_client['torrents'].append(_torrent_dict) if _torrent_dict[u'error'] == 3: _torrent_dict[u'status'] = u'error_torrentclient' if _torrent_dict[u'error'] in [1, 2]: _torrent_dict[u'status'] = u'error_torrenttracker' return self.dict_client def add_torrent(self, torrent_path): pass def remove(self, torrent_hash): self.client.remove_torrent(torrent_hash) def verify(self, torrent_hash): self.client.verify_torrent(torrent_hash) def free_space_bytes(self): free_space = None try: session = self.client.get_session() except TransmissionError as err: raise BTCexception(" {} TransmissionError {}".format( self.dict_client['name'], err)) except socket.timeout as err: raise BTCexception(" {} Socket error {}".format( self.dict_client['name'], err)) except Exception as err: raise BTCexception("{}: {}".format(self.dict_client['name'], err.message)) else: free_space = session.download_dir_free_space return free_space
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 []
def connect(settings): return Client(**to_rpcclient_settings(settings))
class EmissionOverview(Screen, HelpableScreen): skin = """<screen name="EmissionOverview" title="Torrent Overview" position="75,135" size="565,330"> <ePixmap position="0,0" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" /> <ePixmap position="140,0" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" /> <ePixmap position="280,0" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" /> <ePixmap position="420,0" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" /> <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="all_sel" pixmap="skin_default/epg_now.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="5,47" zPosition="2" source="all_text" render="Label" halign="center" font="Regular;18" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="downloading_sel" pixmap="skin_default/epg_next.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="111,47" zPosition="2" source="downloading_text" render="Label" halign="center" font="Regular;18" /> <widget size="320,25" alphatest="on" position="5,45" zPosition="1" name="seeding_sel" pixmap="skin_default/epg_more.png" /> <widget valign="center" transparent="1" size="108,22" backgroundColor="#25062748" position="212,47" zPosition="2" source="seeding_text" render="Label" halign="center" font="Regular;18" /> <widget source="torrents" render="Label" size="240,22" position="320,47" halign="right" font="Regular;18" /> <!--ePixmap size="550,230" alphatest="on" position="5,65" pixmap="skin_default/border_epg.png" /--> <widget source="list" render="Listbox" position="5,70" size="550,225" scrollbarMode="showAlways"> <convert type="TemplatedMultiContent"> {"template": [ MultiContentEntryText(pos=(2,2), size=(555,22), text = 1, font = 0, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER), MultiContentEntryText(pos=(2,26), size=(555,18), text = 2, font = 1, flags = RT_HALIGN_LEFT|RT_VALIGN_CENTER), (eListboxPythonMultiContent.TYPE_PROGRESS, 0, 44, 537, 6, -3), ], "fonts": [gFont("Regular", 20),gFont("Regular", 16)], "itemHeight": 51 } </convert> </widget> <widget source="upspeed" render="Label" size="150,20" position="5,300" halign="left" font="Regular;18" /> <widget source="downspeed" render="Label" size="150,20" position="410,300" halign="right" font="Regular;18" /> </screen>""" def __init__(self, session): Screen.__init__(self, session) HelpableScreen.__init__(self) try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value) except TransmissionError as te: self.transmission = None self["SetupActions"] = HelpableActionMap( self, "SetupActions", { "ok": (self.ok, _("show details")), "cancel": (self.close, _("close")), }) self["ColorActions"] = HelpableActionMap( self, "ColorActions", { "green": (self.bandwidth, _("open bandwidth settings")), "yellow": (self.prevlist, _("show previous list")), "blue": (self.nextlist, _("show next list")), }) self["MenuActions"] = HelpableActionMap(self, "MenuActions", { "menu": (self.menu, _("open context menu")), }) self["key_red"] = StaticText(_("Close")) self["key_green"] = StaticText(_("Bandwidth")) self["key_yellow"] = StaticText("") self["key_blue"] = StaticText("") self["all_text"] = StaticText(_("All")) self["downloading_text"] = StaticText(_("DL")) self["seeding_text"] = StaticText(_("UL")) self["upspeed"] = StaticText("") self["downspeed"] = StaticText("") self["torrents"] = StaticText("") self["all_sel"] = Pixmap() self["downloading_sel"] = Pixmap() self["seeding_sel"] = Pixmap() self['list'] = List([]) self.list_type = config.plugins.emission.last_tab.value self.sort_type = config.plugins.emission.last_sort.value self.showHideSetTextMagic() self.timer = eTimer() self.timer_conn = self.timer.timeout.connect(self.updateList) self.timer.start(0, 1) def bandwidthCallback(self, ret=None): if self.transmission is not None and ret: try: self.transmission.set_session(**ret) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) self.updateList() def menuCallback(self, ret=None): ret and ret[1]() def newDlCallback(self, ret=None): if self.transmission is not None and ret: try: res = self.transmission.add_url(ret) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) else: if not res: self.session.open( MessageBox, _("Torrent could not be scheduled not download!"), type=MessageBox.TYPE_ERROR, timeout=5) self.updateList() def newDl(self): self.timer.stop() self.session.openWithCallback(self.newDlCallback, TorrentLocationBox) def sortCallback(self, ret=None): if ret is not None: self.sort_type = config.plugins.emission.last_sort.value = ret[1] config.plugins.emission.last_sort.save() self.updateList() def sort(self): self.timer.stop() self.session.openWithCallback(self.sortCallback, ChoiceBox, _("Which sorting method do you prefer?"), [(_("by eta"), SORT_TYPE_TIME), (_("by progress"), SORT_TYPE_PROGRESS), (_("by age"), SORT_TYPE_ADDED), (_("by speed"), SORT_TYPE_SPEED)]) def pauseShown(self): if self.transmission is not None: self.transmission.stop([x[0].id for x in self.list]) def unpauseShown(self): if self.transmission is not None: self.transmission.start([x[0].id for x in self.list]) def pauseAll(self): if self.transmission is None: return try: self.transmission.stop( [x.id for x in self.transmission.list().values()]) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) def unpauseAll(self): if self.transmission is None: return try: self.transmission.start( [x.id for x in self.transmission.list().values()]) except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) def configure(self): #reload(EmissionSetup) self.timer.stop() self.session.openWithCallback(self.configureCallback, EmissionSetup.EmissionSetup) def menu(self): self.session.openWithCallback( self.menuCallback, ChoiceBox, _("What do you want to do?"), [(_("Configure connection"), self.configure), (_("Change sorting"), self.sort), (_("Add new download"), self.newDl), (_("Pause shown"), self.pauseShown), (_("Unpause shown"), self.unpauseShown), (_("Pause all"), self.pauseAll), (_("Unpause all"), self.unpauseAll)], ) def showHideSetTextMagic(self): list_type = self.list_type if list_type == LIST_TYPE_ALL: self["all_sel"].show() self["downloading_sel"].hide() self["seeding_sel"].hide() self["key_yellow"].setText(_("Seeding")) self["key_blue"].setText(_("Download")) elif list_type == LIST_TYPE_DOWNLOADING: self["all_sel"].hide() self["downloading_sel"].show() self["seeding_sel"].hide() self["key_yellow"].setText(_("All")) self["key_blue"].setText(_("Seeding")) else: #if list_type == LIST_TYPE_SEEDING: self["all_sel"].hide() self["downloading_sel"].hide() self["seeding_sel"].show() self["key_yellow"].setText(_("Download")) self["key_blue"].setText(_("All")) def prevlist(self): self.timer.stop() list_type = self.list_type if list_type == LIST_TYPE_ALL: self.list_type = LIST_TYPE_SEEDING elif list_type == LIST_TYPE_DOWNLOADING: self.list_type = LIST_TYPE_ALL else: #if list_type == LIST_TYPE_SEEDING: self.list_type = LIST_TYPE_DOWNLOADING self.showHideSetTextMagic() self.updateList() def nextlist(self): self.timer.stop() list_type = self.list_type if list_type == LIST_TYPE_ALL: self.list_type = LIST_TYPE_DOWNLOADING elif list_type == LIST_TYPE_DOWNLOADING: self.list_type = LIST_TYPE_SEEDING else: #if list_type == LIST_TYPE_SEEDING: self.list_type = LIST_TYPE_ALL self.showHideSetTextMagic() self.updateList() def prevItem(self): self['list'].selectPrevious() cur = self['list'].getCurrent() return cur and cur[0] def nextItem(self): self['list'].selectNext() cur = self['list'].getCurrent() return cur and cur[0] def bandwidth(self): if self.transmission is None: return #reload(EmissionBandwidth) self.timer.stop() try: sess = self.transmission.get_session() rpc_version = self.transmission.rpc_version except TransmissionError as te: self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) # XXX: this seems silly but cleans the gui and restarts the timer :-) self.updateList() else: self.session.openWithCallback(self.bandwidthCallback, EmissionBandwidth.EmissionBandwidth, sess, False, rpc_version) def configureCallback(self): try: self.transmission = Client( address=config.plugins.emission.hostname.value, port=config.plugins.emission.port.value, user=config.plugins.emission.username.value, password=config.plugins.emission.password.value) except TransmissionError as te: self.transmission = None self.session.open( MessageBox, _("Error communicating with transmission-daemon: %s.") % (te), type=MessageBox.TYPE_ERROR, timeout=5) else: self.updateList() def updateList(self, *args, **kwargs): # XXX: if we are not connected do NOT restart timer, it's useless anyway if self.transmission is None: return try: lst = list(self.transmission.list().values()) session = self.transmission.session_stats() except TransmissionError: # XXX: some hint in gui would be nice self['list'].setList([]) self["torrents"].setText("") self["upspeed"].setText("") self["downspeed"].setText("") else: sort_type = self.sort_type if sort_type == SORT_TYPE_TIME: def cmp_func(x, y): x_eta = x.fields['eta'] y_eta = y.fields['eta'] if x_eta > -1 and y_eta < 0: return 1 if x_eta < 0 and y_eta > -1: return -1 # note: cmp call inversed because lower eta is "better" return cmp(y_eta, x_eta) or cmp(x.progress, y.progress) lst.sort(cmp=cmp_func, reverse=True) elif sort_type == SORT_TYPE_PROGRESS: lst.sort(key=lambda x: x.progress, reverse=True) elif sort_type == SORT_TYPE_SPEED: lst.sort(key=lambda x: (x.rateDownload, x.rateUpload), reverse=True) # SORT_TYPE_ADDED is what we already have list_type = self.list_type if list_type == LIST_TYPE_ALL: lst = [(x, x.name.encode('utf-8', 'ignore'), str(x.eta or '?:??:??').encode('utf-8'), int(x.progress)) for x in lst] elif list_type == LIST_TYPE_DOWNLOADING: lst = [(x, x.name.encode('utf-8', 'ignore'), str(x.eta or '?:??:??').encode('utf-8'), int(x.progress)) for x in lst if x.status == "downloading"] else: #if list_type == LIST_TYPE_SEEDING: lst = [(x, x.name.encode('utf-8', 'ignore'), str(x.eta or '?:??:??').encode('utf-8'), int(x.progress)) for x in lst if x.status == "seeding"] self["torrents"].setText( _("Active Torrents: %d/%d") % (session.activeTorrentCount, session.torrentCount)) self["upspeed"].setText( _("UL: %d kb/s") % (session.uploadSpeed / 1024)) self["downspeed"].setText( _("DL: %d kb/s") % (session.downloadSpeed / 1024)) # XXX: this is a little ugly but this way we have the least # visible distortion :-) index = min(self['list'].index, len(lst) - 1) self['list'].setList(lst) self['list'].index = index self.list = lst self.timer.startLongTimer(10) def ok(self): cur = self['list'].getCurrent() if self.transmission is not None and cur: #reload(EmissionDetailview) self.timer.stop() self.session.openWithCallback( self.updateList, EmissionDetailview.EmissionDetailview, self.transmission, cur[0], self.prevItem, self.nextItem, ) def close(self): self.timer.stop() config.plugins.emission.last_tab.value = self.list_type config.plugins.emission.last_tab.save() Screen.close(self)
trk_remote = [] config['local_lists'].append(cache_file) trk_local = readLocalLists() if trk_local: dbg('Local lists loaded: {} trackers'.format(len(trk_local))) trackers = set(trk_remote + trk_local) dbg('Total trackers: {}'.format(len(trackers))) if not trackers: lg("No trackers loaded, nothing to do") exit(1) try: tc = Client(**client) except: if not config['err_on_connect']: exit() print("Unable to connect to Transmission: ", sys.exc_info()[0]) raise torrents = tc.get_torrents() dbg('{} torrents total'.format(len(torrents))) for t in torrents: if config['status_filter'] and not t.status in config['status_filter']: dbg('{}: skipping due to status filter'.format(t.name)) continue