def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if app.TORRENT_AUTH_TYPE != 'none': tp_kwargs['authtype'] = app.TORRENT_AUTH_TYPE if not app.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False else: if app.SSL_CA_BUNDLE: tp_kwargs['check_ssl_cert'] = app.SSL_CA_BUNDLE if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) return self.auth
def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return self.host = self.host.rstrip('/') tp_kwargs = {} if settings.TORRENT_AUTH_TYPE and settings.TORRENT_AUTH_TYPE.lower( ) != 'none': tp_kwargs['authtype'] = settings.TORRENT_AUTH_TYPE if not settings.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs or None) else: self.auth = RTorrent(self.host, None, None, True, tp_kwargs=tp_kwargs or None) return self.auth
def connect(self, reconnect=False): # Already connected? if not reconnect and self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf('ssl')) # Automatically add '+https' to 'httprpc' protocol if SSL is enabled if self.conf('ssl') and url.startswith('httprpc://'): url = url.replace('httprpc://', 'httprpc+https://') parsed = urlparse(url) # rpc_url is only used on http/https scgi pass-through if parsed.scheme in ['http', 'https']: url += self.conf('rpc_url') # Construct client self.rt = RTorrent(url, self.getAuth(), verify_ssl=self.getVerifySsl()) self.error_msg = '' try: self.rt.connection.verify() except AssertionError as e: self.error_msg = e.message self.rt = None return self.rt
def _get_auth(self): self.auth = None if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if sickbeard.TORRENT_AUTH_TYPE is not 'none': tp_kwargs[b'authtype'] = sickbeard.TORRENT_AUTH_TYPE if not sickbeard.TORRENT_VERIFY_CERT: tp_kwargs[b'check_ssl_cert'] = False if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) return self.auth
def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if app.TORRENT_AUTH_TYPE != 'none': tp_kwargs['authtype'] = app.TORRENT_AUTH_TYPE if not app.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False else: if app.SSL_CA_BUNDLE: tp_kwargs['check_ssl_cert'] = app.SSL_CA_BUNDLE try: if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) except Exception as error: # No request/connection specific exception thrown. raise DownloadClientConnectionException( f'Unable to authenticate with rtorrent client: {error}') return self.auth
def _get_auth(self): self.auth = None if self.auth is not None: return self.auth if not self.host: return sp_kwargs = {} if sickrage.TORRENT_AUTH_TYPE != 'None': sp_kwargs[b'authtype'] = sickrage.TORRENT_AUTH_TYPE if not sickrage.TORRENT_VERIFY_CERT: sp_kwargs[b'check_ssl_cert'] = False if self.username and self.password: url_parts = self.host.split('//') self.auth = RTorrent( url_parts[0] + "{0}:{1}@".format(self.username, self.password) + url_parts[1]) else: self.auth = RTorrent(self.host) return self.auth
def connect(self, reconnect=False): # Already connected? if not reconnect and self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf('ssl')) parsed = urlparse(url) # rpc_url is only used on http/https scgi pass-through if parsed.scheme in ['http', 'https']: url += self.conf('rpc_url') if self.conf('username') and self.conf('password'): self.rt = RTorrent(url, self.conf('username'), self.conf('password')) else: self.rt = RTorrent(url) self.error_msg = '' try: self.rt._verify_conn() except AssertionError as e: self.error_msg = e.message self.rt = None return self.rt
def setUp(self): self.rt = RTorrent() self.test_file = "tests/tester.torrent" self.test_magnet = "magnet:?xt=urn:btih:f2ed240912dc324d6a30de6811a8747f80b9722d&dn=The+Wolverine+2013+DVDRip+x264+AC3-EVO&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.istole.it%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Fopen.demonii.com%3A1337" files = self.rt.get_active_infohashes() for f in files: self.rt.erase(f)
def _authenticate(self): """ Setup connection to rTorrent XMLRPC server :return: """ try: self.rtorrent = RTorrent(self.url) except ConnectionRefusedError as e: self.send_log('Failed to connect to rTorrent. Aborting', 'critical') sys.exit(1) self.send_log('Successfully connected to rTorrent', 'info')
def connect(self): # Already connected? if self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf('ssl')) + self.conf('rpc_url') if self.conf('username') and self.conf('password'): self.rt = RTorrent(url, self.conf('username'), self.conf('password')) else: self.rt = RTorrent(url) return self.rt
def connect(self): # Already connected? if self.rt is not None: return self.rt # Ensure url is set if not self.conf('url'): log.error( 'Config properties are not filled in correctly, url is missing.' ) return False if self.conf('username') and self.conf('password'): self.rt = RTorrent(self.conf('url'), self.conf('username'), self.conf('password')) else: self.rt = RTorrent(self.conf('url')) return self.rt
def test_RTorrent(socketpath): rtorrent = RTorrent(socketpath) assert rtorrent.test_connection() is True listViews = rtorrent.get_views() print(listViews) t0 = time() listTorrents = rtorrent.get_torrents() t1 = time() print(listTorrents, t1 - t0) listTorrents = rtorrent.get_torrents() t2 = time() print(listTorrents, t2 - t1)
def test_Torrent(socketpath): rtorrent = RTorrent(socketpath) t = Torrent(rtorrent, info_hash="42B7EFAB0D48757C6D7906272733BD22B57BB182")
class rTorrentAPI(GenericClient): def __init__(self, host=None, username=None, password=None): super(rTorrentAPI, self).__init__('rTorrent', host, username, password) def _get_auth(self): self.auth = None if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if sickbeard.TORRENT_AUTH_TYPE is not 'none': tp_kwargs[b'authtype'] = sickbeard.TORRENT_AUTH_TYPE if not sickbeard.TORRENT_VERIFY_CERT: tp_kwargs[b'check_ssl_cert'] = False if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) return self.auth def _add_torrent_uri(self, result): if not self.auth: return False if not result: return False try: # Send magnet to rTorrent torrent = self.auth.load_magnet(result.url, result.hash) if not torrent: return False # Set label label = sickbeard.TORRENT_LABEL if result.show.is_anime: label = sickbeard.TORRENT_LABEL_ANIME if label: torrent.set_custom(1, label) if sickbeard.TORRENT_PATH: torrent.set_directory(sickbeard.TORRENT_PATH) # Start torrent torrent.start() return True except Exception: logging.debug(traceback.format_exc()) return False def _add_torrent_file(self, result): if not self.auth: return False if not result: return False # group_name = 'sb_test'.lower() ##### Use provider instead of _test # if not self._set_torrent_ratio(group_name): # return False # Send request to rTorrent try: # Send torrent to rTorrent torrent = self.auth.load_torrent(result.content) if not torrent: return False # Set label label = sickbeard.TORRENT_LABEL if result.show.is_anime: label = sickbeard.TORRENT_LABEL_ANIME if label: torrent.set_custom(1, label) if sickbeard.TORRENT_PATH: torrent.set_directory(sickbeard.TORRENT_PATH) # Set Ratio Group # torrent.set_visible(group_name) # Start torrent torrent.start() return True except Exception: logging.debug(traceback.format_exc()) return False def _set_torrent_ratio(self, name): # if not name: # return False # # if not self.auth: # return False # # views = self.auth.get_views() # # if name not in views: # self.auth.create_group(name) # group = self.auth.get_group(name) # ratio = int(float(sickbeard.TORRENT_RATIO) * 100) # # try: # if ratio > 0: # # # Explicitly set all group options to ensure it is setup correctly # group.set_upload('1M') # group.set_min(ratio) # group.set_max(ratio) # group.set_command('d.stop') # group.enable() # else: # # Reset group action and disable it # group.set_command() # group.disable() # # except: # return False return True def testAuthentication(self): try: self._get_auth() if self.auth is not None: return True, 'Success: Connected and Authenticated' else: return False, 'Error: Unable to get ' + self.name + ' Authentication, check your config!' except Exception: return False, 'Error: Unable to connect to ' + self.name
class rTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] rt = None # Migration url to host options def __init__(self): super(rTorrent, self).__init__() addEvent('app.load', self.migrate) def migrate(self): url = self.conf('url') if url: host_split = splitString(url.split('://')[-1], split_on='/') self.conf('ssl', value=url.startswith('https')) self.conf('host', value=host_split[0].strip()) self.conf('rpc_url', value='/'.join(host_split[1:])) self.deleteConf('url') def connect(self): # Already connected? if self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf( 'ssl')) + '/' + self.conf('rpc_url').strip('/ ') + '/' if self.conf('username') and self.conf('password'): self.rt = RTorrent(url, self.conf('username'), self.conf('password')) else: self.rt = RTorrent(url) return self.rt def _update_provider_group(self, name, data): if data.get('seed_time'): log.info('seeding time ignored, not supported') if not name: return False if not self.connect(): return False views = self.rt.get_views() if name not in views: self.rt.create_group(name) group = self.rt.get_group(name) try: if data.get('seed_ratio'): ratio = int(float(data.get('seed_ratio')) * 100) log.debug('Updating provider ratio to %s, group name: %s', (ratio, name)) # Explicitly set all group options to ensure it is setup correctly group.set_upload('1M') group.set_min(ratio) group.set_max(ratio) group.set_command('d.stop') group.enable() else: # Reset group action and disable it group.set_command() group.disable() except MethodError, err: log.error('Unable to set group options: %s', err.msg) return False return True
class rTorrent(DownloaderBase): protocol = ['torrent', 'torrent_magnet'] rt = None error_msg = '' # Migration url to host options def __init__(self): super(rTorrent, self).__init__() addEvent('app.load', self.migrate) addEvent('setting.save.rtorrent.*.after', self.settingsChanged) def migrate(self): url = self.conf('url') if url: host_split = splitString(url.split('://')[-1], split_on='/') self.conf('ssl', value=url.startswith('https')) self.conf('host', value=host_split[0].strip()) self.conf('rpc_url', value='/'.join(host_split[1:])) self.deleteConf('url') def settingsChanged(self): # Reset active connection if settings have changed if self.rt: log.debug('Settings have changed, closing active connection') self.rt = None return True def getAuth(self): if not self.conf('username') or not self.conf('password'): # Missing username or password parameter return None # Build authentication tuple return (self.conf('authentication'), self.conf('username'), self.conf('password')) def getVerifySsl(self): # Ensure verification has been enabled if not self.conf('ssl_verify'): return False # Use ca bundle if defined ca_bundle = self.conf('ssl_ca_bundle') if ca_bundle and os.path.exists(ca_bundle): return ca_bundle # Use default ssl verification return True def connect(self, reconnect=False): # Already connected? if not reconnect and self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf('ssl')) # Automatically add '+https' to 'httprpc' protocol if SSL is enabled if self.conf('ssl') and url.startswith('httprpc://'): url = url.replace('httprpc://', 'httprpc+https://') parsed = urlparse(url) # rpc_url is only used on http/https scgi pass-through if parsed.scheme in ['http', 'https']: url += self.conf('rpc_url') # Construct client self.rt = RTorrent(url, self.getAuth(), verify_ssl=self.getVerifySsl()) self.error_msg = '' try: self.rt.connection.verify() except AssertionError as e: self.error_msg = e.message self.rt = None return self.rt def test(self): """ Check if connection works :return: bool """ if self.connect(True): return True if self.error_msg: return False, 'Connection failed: ' + self.error_msg return False def download(self, data=None, media=None, filedata=None): """ Send a torrent/nzb file to the downloader :param data: dict returned from provider Contains the release information :param media: media dict with information Used for creating the filename when possible :param filedata: downloaded torrent/nzb filedata The file gets downloaded in the searcher and send to this function This is done to have failed checking before using the downloader, so the downloader doesn't need to worry about that :return: boolean One faile returns false, but the downloaded should log his own errors """ if not media: media = {} if not data: data = {} log.debug('Sending "%s" to rTorrent.', (data.get('name'))) if not self.connect(): return False torrent_params = {} if self.conf('label'): torrent_params['label'] = self.conf('label') if not filedata and data.get('protocol') == 'torrent': log.error('Failed sending torrent, no data') return False # Try download magnet torrents if data.get('protocol') == 'torrent_magnet': filedata = self.magnetToTorrent(data.get('url')) if filedata is False: return False data['protocol'] = 'torrent' info = bdecode(filedata)["info"] torrent_hash = sha1(bencode(info)).hexdigest().upper() # Convert base 32 to hex if len(torrent_hash) == 32: torrent_hash = b16encode(b32decode(torrent_hash)) # Send request to rTorrent try: # Send torrent to rTorrent torrent = self.rt.load_torrent(filedata, verify_retries=10) if not torrent: log.error('Unable to find the torrent, did it fail to load?') return False # Set label if self.conf('label'): torrent.set_custom(1, self.conf('label')) if self.conf('directory'): torrent.set_directory(self.conf('directory')) # Start torrent if not self.conf('paused', default=0): torrent.start() return self.downloadReturnId(torrent_hash) except Exception as err: log.error('Failed to send torrent to rTorrent: %s', err) return False def getTorrentStatus(self, torrent): if not torrent.complete: return 'busy' if torrent.open: return 'seeding' return 'completed' def getAllDownloadStatus(self, ids): """ Get status of all active downloads :param ids: list of (mixed) downloader ids Used to match the releases for this downloader as there could be other downloaders active that it should ignore :return: list of releases """ log.debug('Checking rTorrent download status.') if not self.connect(): return [] try: torrents = self.rt.get_torrents() release_downloads = ReleaseDownloadList(self) for torrent in torrents: if torrent.info_hash in ids: torrent_directory = os.path.normpath(torrent.directory) torrent_files = [] for file in torrent.get_files(): if not os.path.normpath( file.path).startswith(torrent_directory): file_path = os.path.join(torrent_directory, file.path.lstrip('/')) else: file_path = file.path torrent_files.append(sp(file_path)) release_downloads.append({ 'id': torrent.info_hash, 'name': torrent.name, 'status': self.getTorrentStatus(torrent), 'seed_ratio': torrent.ratio, 'original_status': torrent.state, 'timeleft': str( timedelta(seconds=float(torrent.left_bytes) / torrent.down_rate)) if torrent.down_rate > 0 else -1, 'folder': sp(torrent.directory), 'files': torrent_files }) return release_downloads except Exception as err: log.error('Failed to get status from rTorrent: %s', err) return [] def pause(self, release_download, pause=True): if not self.connect(): return False torrent = self.rt.find_torrent(release_download['id']) if torrent is None: return False if pause: return torrent.pause() return torrent.resume() def removeFailed(self, release_download): log.info('%s failed downloading, deleting...', release_download['name']) return self.processComplete(release_download, delete_files=True) def processComplete(self, release_download, delete_files): log.debug( 'Requesting rTorrent to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else '')) if not self.connect(): return False torrent = self.rt.find_torrent(release_download['id']) if torrent is None: return False if delete_files: for file_item in torrent.get_files( ): # will only delete files, not dir/sub-dir os.unlink(os.path.join(torrent.directory, file_item.path)) if torrent.is_multi_file() and torrent.directory.endswith( torrent.name): # Remove empty directories bottom up try: for path, _, _ in os.walk(sp(torrent.directory), topdown=False): os.rmdir(path) except OSError: log.info( 'Directory "%s" contains extra files, unable to remove', torrent.directory) torrent.erase() # just removes the torrent, doesn't delete data return True
class rTorrentClient(TorrentClient): def __init__(self, logger, username=None, password=None, url=None, hostname=None): TorrentClient.__init__(self, logger, username=username, password=password, url=url, hostname=hostname) self.torrent_client = 'rTorrent' self._authenticate() self.rtorrent = None self._authenticate() def _authenticate(self): """ Setup connection to rTorrent XMLRPC server :return: """ try: self.rtorrent = RTorrent(self.url) except ConnectionRefusedError as e: self.send_log('Failed to connect to rTorrent. Aborting', 'critical') sys.exit(1) self.send_log('Successfully connected to rTorrent', 'info') def _build_torrent_list(self, torrents): """ Take the resulting torrent list and create a consistent structure shared through all clients :return: """ self.send_log( 'Structuring list of ' + str(len(torrents)) + ' torrent(s)', 'debug') for torrent in torrents: self.torrent_list[torrent.info_hash] = {} self.torrent_list[torrent.info_hash]['name'] = torrent.name self.torrent_list[ torrent.info_hash]['total_size'] = torrent.size_bytes self.torrent_list[torrent.info_hash]['progress'] = round( (torrent.bytes_done / torrent.size_bytes * 100), 2) self.torrent_list[ torrent.info_hash]['total_downloaded'] = torrent.bytes_done self.torrent_list[ torrent.info_hash]['total_uploaded'] = torrent.get_up_total() self.torrent_list[torrent.info_hash]['ratio'] = torrent.ratio self.torrent_list[torrent.info_hash]['total_seeds'] = 'N/A' self.torrent_list[torrent.info_hash]['state'] = torrent.get_state() self.torrent_list[torrent.info_hash]['tracker'] = urlsplit( torrent.get_trackers()[0].url).netloc self.torrent_list[ torrent.info_hash]['total_files'] = torrent.size_files def get_all_torrents(self): """ Return list of all torrents :return: """ self._authenticate( ) # We need to get another Rtorrent object so we get a fresh list of torrents self._build_torrent_list(self.rtorrent.get_torrents())
def xhr_rtorrentdl(): # url qualification def url_qualify(url_proto, url_host, url_port): url_host_part = str(url_host).partition('/') # validate host (kinda... just make sure it's not empty) if url_host_part[0]: # validate port if not url_port: # for http and https we can assume default service ports if url_proto == 'http': url_port = 80 elif url_proto == 'https': url_port = 443 else: raise Exception('port must be defined for protocol %s' % (url_proto)) else: try: url_port = int(url_port) except ValueError: raise Exception('port must be a numeric value') url_qualified = '%s://%s:%i%s%s' % (url_proto, url_host_part[0], url_port, url_host_part[1], url_host_part[2]) return url_qualified else: raise Exception('invalid host: %s' % (url_host[0])) # initialize empty list, which will be later populated with listing # of active torrents torrentlist = list() # connection flag connected = False # global rates down_rate = 0.0 up_rate = 0.0 rtorrent_url = None rtorrent_user = None rtorrent_password = None try: if get_setting_value('rtorrent_host') is not None: rtorrent_url = url_qualify(get_setting_value('rtorrent_proto'), get_setting_value('rtorrent_host'), get_setting_value('rtorrent_port')) except Exception as ex: log_error(ex) try: if rtorrent_url: # user/password login is not implemented for scgi if get_setting_value('rtorrent_proto') != 'scgi': rtorrent_user = get_setting_value('rtorrent_user') rtorrent_password = get_setting_value('rtorrent_password') client = RTorrent(rtorrent_url, rtorrent_user, rtorrent_password, True) if client is not None: connected = True down_rate = client.get_down_rate() up_rate = client.get_up_rate() # loop through each job and add all torrents to torrentlist() for torrent in client.get_torrents(): # friendly status and time left time_left = -1 if torrent.complete: if torrent.active: status = 'seeding' else: status = 'done' else: if torrent.active: if torrent.down_rate > 0: time_left = str( timedelta(seconds=round( float(torrent.left_bytes) / torrent.down_rate))) status = 'leeching' else: status = 'waiting' else: status = 'inactive' # get torrent file list # FIXME takes too much time and is not used anyway for now #torrent_filelist = [] #for file_current in torrent.get_files(): # torrent_filelist.append(os.path.join(torrent.directory,file_current.path)) # what's left? progress = float(100.0 / torrent.size_bytes * (torrent.size_bytes - torrent.left_bytes)) # append to list torrentlist.append({ 'name': torrent.name, 'info_hash': torrent.info_hash, 'status': status, 'state': torrent.state, 'progress': progress, 'time_left': time_left, 'down_rate': torrent.down_rate, 'up_rate': torrent.up_rate, 'ratio': torrent.ratio # 'folder': torrent.directory, # 'files': '|'.join(torrent_filelist) }) # no torrents -> empty list if not torrentlist.__len__(): torrentlist = None except Exception as ex: log_error(ex) torrentlist = None return render_template( 'rtorrentdl.html', connected=connected, torrentlist_scroll=get_setting_value('rtorrent_list_scroll'), torrentlist=torrentlist, down_rate=down_rate, up_rate=up_rate)
class rTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] rt = None # Migration url to host options def __init__(self): super(rTorrent, self).__init__() addEvent('app.load', self.migrate) def migrate(self): url = self.conf('url') if url: host_split = splitString(url.split('://')[-1], split_on='/') self.conf('ssl', value=url.startswith('https')) self.conf('host', value=host_split[0].strip()) self.conf('rpc_url', value='/'.join(host_split[1:])) self.deleteConf('url') def connect(self): # Already connected? if self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol=True, ssl=self.conf('ssl')) + self.conf('rpc_url') if self.conf('username') and self.conf('password'): self.rt = RTorrent(url, self.conf('username'), self.conf('password')) else: self.rt = RTorrent(url) return self.rt def _update_provider_group(self, name, data): if data.get('seed_time'): log.info('seeding time ignored, not supported') if not name: return False if not self.connect(): return False views = self.rt.get_views() if name not in views: self.rt.create_group(name) group = self.rt.get_group(name) try: if data.get('seed_ratio'): ratio = int(float(data.get('seed_ratio')) * 100) log.debug('Updating provider ratio to %s, group name: %s', (ratio, name)) # Explicitly set all group options to ensure it is setup correctly group.set_upload('1M') group.set_min(ratio) group.set_max(ratio) group.set_command('d.stop') group.enable() else: # Reset group action and disable it group.set_command() group.disable() except MethodError as err: log.error('Unable to set group options: %s', err.msg) return False return True def download(self, data=None, media=None, filedata=None): if not media: media = {} if not data: data = {} log.debug('Sending "%s" to rTorrent.', (data.get('name'))) if not self.connect(): return False group_name = 'cp_' + data.get('provider').lower() if not self._update_provider_group(group_name, data): return False torrent_params = {} if self.conf('label'): torrent_params['label'] = self.conf('label') if not filedata and data.get('protocol') == 'torrent': log.error('Failed sending torrent, no data') return False # Try download magnet torrents if data.get('protocol') == 'torrent_magnet': filedata = self.magnetToTorrent(data.get('url')) if filedata is False: return False data['protocol'] = 'torrent' info = bdecode(filedata)["info"] torrent_hash = sha1(bencode(info)).hexdigest().upper() # Convert base 32 to hex if len(torrent_hash) == 32: torrent_hash = b16encode(b32decode(torrent_hash)) # Send request to rTorrent try: # Send torrent to rTorrent torrent = self.rt.load_torrent(filedata, verify_retries=10) if not torrent: log.error('Unable to find the torrent, did it fail to load?') return False # Set label if self.conf('label'): torrent.set_custom(1, self.conf('label')) if self.conf('directory'): torrent.set_directory(self.conf('directory')) # Set Ratio Group torrent.set_visible(group_name) # Start torrent if not self.conf('paused', default=0): torrent.start() return self.downloadReturnId(torrent_hash) except Exception as err: log.error('Failed to send torrent to rTorrent: %s', err) return False def getAllDownloadStatus(self, ids): log.debug('Checking rTorrent download status.') if not self.connect(): return [] try: torrents = self.rt.get_torrents() release_downloads = ReleaseDownloadList(self) for torrent in torrents: if torrent.info_hash in ids: torrent_directory = os.path.normpath(torrent.directory) torrent_files = [] for file in torrent.get_files(): if not os.path.normpath( file.path).startswith(torrent_directory): file_path = os.path.join(torrent_directory, file.path.lstrip('/')) else: file_path = file.path torrent_files.append(sp(file_path)) status = 'busy' if torrent.complete: if torrent.active: status = 'seeding' else: status = 'completed' release_downloads.append({ 'id': torrent.info_hash, 'name': torrent.name, 'status': status, 'seed_ratio': torrent.ratio, 'original_status': torrent.state, 'timeleft': str( timedelta(seconds=float(torrent.left_bytes) / torrent.down_rate)) if torrent.down_rate > 0 else -1, 'folder': sp(torrent.directory), 'files': '|'.join(torrent_files) }) return release_downloads except Exception as err: log.error('Failed to get status from rTorrent: %s', err) return [] def pause(self, release_download, pause=True): if not self.connect(): return False torrent = self.rt.find_torrent(release_download['id']) if torrent is None: return False if pause: return torrent.pause() return torrent.resume() def removeFailed(self, release_download): log.info('%s failed downloading, deleting...', release_download['name']) return self.processComplete(release_download, delete_files=True) def processComplete(self, release_download, delete_files): log.debug( 'Requesting rTorrent to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else '')) if not self.connect(): return False torrent = self.rt.find_torrent(release_download['id']) if torrent is None: return False if delete_files: for file_item in torrent.get_files( ): # will only delete files, not dir/sub-dir os.unlink(os.path.join(torrent.directory, file_item.path)) if torrent.is_multi_file() and torrent.directory.endswith( torrent.name): # Remove empty directories bottom up try: for path, _, _ in os.walk(torrent.directory, topdown=False): os.rmdir(path) except OSError: log.info( 'Directory "%s" contains extra files, unable to remove', torrent.directory) torrent.erase() # just removes the torrent, doesn't delete data return True
class RTorrentAPI(GenericClient): """rTorrent API class.""" def __init__(self, host=None, username=None, password=None): """Constructor. :param host: :type host: string :param username: :type username: string :param password: :type password: string """ super(RTorrentAPI, self).__init__('rTorrent', host, username, password) def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if app.TORRENT_AUTH_TYPE != 'none': tp_kwargs['authtype'] = app.TORRENT_AUTH_TYPE if not app.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False else: if app.SSL_CA_BUNDLE: tp_kwargs['check_ssl_cert'] = app.SSL_CA_BUNDLE if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) return self.auth @staticmethod def _get_params(result): params = [] # Set label label = app.TORRENT_LABEL if result.series.is_anime: label = app.TORRENT_LABEL_ANIME if label: params.append('d.custom1.set={0}'.format(label)) if app.TORRENT_PATH: params.append('d.directory.set={0}'.format(app.TORRENT_PATH)) return params def _add_torrent_uri(self, result): if not (self.auth or result): return False try: params = self._get_params(result) # Send magnet to rTorrent and start it torrent = self.auth.load_magnet(result.url, result.hash, start=True, params=params) if not torrent: return False except Exception as msg: log.warning('Error while sending torrent: {error!r}', {'error': msg}) return False else: return True def _add_torrent_file(self, result): if not (self.auth or result): return False try: params = self._get_params(result) # Send torrent to rTorrent and start it torrent = self.auth.load_torrent(result.content, start=True, params=params) if not torrent: return False except Exception as msg: log.warning('Error while sending torrent: {error!r}', {'error': msg}) return False else: return True def test_authentication(self): """Test connection using authentication. :return: :rtype: tuple(bool, str) """ try: self.auth = None self._get_auth() except Exception: # pylint: disable=broad-except return False, 'Error: Unable to connect to {name}'.format(name=self.name) else: if self.auth is None: return False, 'Error: Unable to get {name} Authentication, check your config!'.format(name=self.name) else: return True, 'Success: Connected and Authenticated' def pause_torrent(self, info_hash): """Get torrent and pause.""" log.info('Pausing {client} torrent {hash} status.', {'client': self.name, 'hash': info_hash}) if not self._get_auth(): return False torrent = self.auth.find_torrent(info_hash.upper()) if not torrent: log.debug('Could not locate torrent with {hash} status.', {'hash': info_hash}) return return torrent.pause() def remove_torrent(self, info_hash): """Get torrent and remove.""" log.info('Removing {client} torrent {hash} status.', {'client': self.name, 'hash': info_hash}) if not self._get_auth(): return False torrent = self.auth.find_torrent(info_hash.upper()) if not torrent: log.debug('Could not locate torrent with {hash} status.', {'hash': info_hash}) return return torrent.erase() def _torrent_properties(self, info_hash): """Get torrent properties.""" log.debug('Get {client} torrent hash {hash} properties.', {'client': self.name, 'hash': info_hash}) if not self._get_auth(): return False torrent = self.auth.find_torrent(info_hash.upper()) if not torrent: log.debug('Could not locate torrent with {hash} status.', {'hash': info_hash}) return return torrent def torrent_completed(self, info_hash): """Check if torrent has finished downloading.""" get_status = self.get_status(info_hash) if not get_status: return False return str(get_status) == 'Completed' def torrent_ratio(self, info_hash): """Get torrent ratio.""" get_status = self.get_status(info_hash) if not get_status: return False return get_status.ratio def torrent_progress(self, info_hash): """Get torrent download progress.""" get_status = self.get_status(info_hash) if not get_status: return False return get_status.progress def get_status(self, info_hash): """ Return torrent status. Status codes: ``` complete: 'Completed download' is_finished: 'Finished seeding (ratio reeched)' ``` """ torrent = self._torrent_properties(info_hash) if not torrent: return client_status = ClientStatus() if torrent.started: client_status.set_status_string('Downloading') if torrent.paused: client_status.set_status_string('Paused') # # if torrent['status'] == ?: # # client_status.set_status_string('Failed') if torrent.complete: client_status.set_status_string('Completed') # Store ratio client_status.ratio = torrent.ratio # Store progress if torrent.bytes_done: client_status.progress = int(torrent.completed_bytes / torrent.bytes_done * 100) # Store destination client_status.destination = torrent.directory # Store resource client_status.resource = torrent.base_filename return client_status
class RTorrentAPI(GenericClient): """rTorrent API class.""" def __init__(self, host=None, username=None, password=None): """Constructor. :param host: :type host: string :param username: :type username: string :param password: :type password: string """ super(RTorrentAPI, self).__init__('rTorrent', host, username, password) def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return tp_kwargs = {} if app.TORRENT_AUTH_TYPE != 'none': tp_kwargs['authtype'] = app.TORRENT_AUTH_TYPE if not app.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs) else: self.auth = RTorrent(self.host, None, None, True) return self.auth @staticmethod def _get_params(result): params = [] # Set label label = app.TORRENT_LABEL if result.show.is_anime: label = app.TORRENT_LABEL_ANIME if label: params.append('d.custom1.set={0}'.format(label)) if app.TORRENT_PATH: params.append('d.directory.set={0}'.format(app.TORRENT_PATH)) return params def _add_torrent_uri(self, result): if not (self.auth or result): return False try: params = self._get_params(result) # Send magnet to rTorrent and start it torrent = self.auth.load_magnet(result.url, result.hash, start=True, params=params) if not torrent: return False except Exception as msg: log.warning('Error while sending torrent: {error!r}', {'error': msg}) return False else: return True def _add_torrent_file(self, result): if not (self.auth or result): return False try: params = self._get_params(result) # Send torrent to rTorrent and start it torrent = self.auth.load_torrent(result.content, start=True, params=params) if not torrent: return False except Exception as msg: log.warning('Error while sending torrent: {error!r}', {'error': msg}) return False else: return True def test_authentication(self): """Test connection using authentication. :return: :rtype: tuple(bool, str) """ try: self.auth = None self._get_auth() except Exception: # pylint: disable=broad-except return False, 'Error: Unable to connect to {name}'.format(name=self.name) else: if self.auth is None: return False, 'Error: Unable to get {name} Authentication, check your config!'.format(name=self.name) else: return True, 'Success: Connected and Authenticated'
class TestRTorrentAPI(unittest.TestCase): def setUp(self): self.rt = RTorrent() self.test_file = "tests/tester.torrent" self.test_magnet = "magnet:?xt=urn:btih:f2ed240912dc324d6a30de6811a8747f80b9722d&dn=The+Wolverine+2013+DVDRip+x264+AC3-EVO&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.istole.it%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Fopen.demonii.com%3A1337" files = self.rt.get_active_infohashes() for f in files: self.rt.erase(f) def test_add_torrent(self): self.rt.add_torrent_magnet(self.test_magnet) res = self.rt.get_active_infohashes() assert (len(res) == 1) self.rt.erase(res[0]) res = self.rt.get_active_infohashes() assert (len(res) == 0) def test_torrent_size(self): self.rt.add_torrent_magnet(self.test_magnet) res = self.rt.get_active_infohashes() assert (len(res) == 1) time.sleep(10) assert (1500000000 < self.rt.get_size_bytes(res[0]) < 1600000000) def test_torrent_file_add(self): f = open(self.test_file) out = f.read() self.rt.add_torrent_file(out) res = self.rt.get_active_infohashes() assert (len(res) == 1)
class Client(GenericClient): def __init__(self, host=None, username=None, password=None): super().__init__('rTorrent', host, username, password) def _get_auth(self): if self.auth is not None: return self.auth if not self.host: return self.host = self.host.rstrip('/') tp_kwargs = {} if settings.TORRENT_AUTH_TYPE and settings.TORRENT_AUTH_TYPE.lower( ) != 'none': tp_kwargs['authtype'] = settings.TORRENT_AUTH_TYPE if not settings.TORRENT_VERIFY_CERT: tp_kwargs['check_ssl_cert'] = False if self.username and self.password: self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs or None) else: self.auth = RTorrent(self.host, None, None, True, tp_kwargs=tp_kwargs or None) return self.auth def _add_torrent_uri(self, result): if not (self.auth and result): return False try: # Send torrent magnet with params to rTorrent and optionally start download torrent = self.auth.load_magnet(result.url, result.hash, start=not settings.TORRENT_PAUSED, params=self._get_params(result)) if not torrent: return False return True except Exception as error: logger.warning( _('Error while sending torrent: {error}'.format(error=error))) return False def _add_torrent_file(self, result): if not (self.auth and result): return False try: # Send torrent file with params to rTorrent and optionally start download torrent = self.auth.load_torrent(result.content, start=not settings.TORRENT_PAUSED, params=self._get_params(result)) if not torrent: return False return True except Exception as error: logger.info(traceback.format_exc()) logger.warning( _('Error while sending torrent: {error}'.format(error=error))) return False def testAuthentication(self): try: self._get_auth() if self.auth is not None: return True, _('Success: Connected and Authenticated') else: return False, _( 'Error: Unable to get {self.name} Authentication, check your config!' ) except Exception as error: return False, _( 'Error: Unable to connect to {name}: {error}'.format( name=self.name, error=error)) @staticmethod def _get_params(result): params = [] # Set label label = settings.TORRENT_LABEL if result.show.is_anime: label = settings.TORRENT_LABEL_ANIME if label: params.append(f'd.custom1.set={label}') # Set download folder if settings.TORRENT_PATH: params.append(f'd.directory.set={settings.TORRENT_PATH}') return params
class rTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] rt = None def connect(self): # Already connected? if self.rt is not None: return self.rt # Ensure url is set if not self.conf('url'): log.error('Config properties are not filled in correctly, url is missing.') return False if self.conf('username') and self.conf('password'): self.rt = RTorrent( self.conf('url'), self.conf('username'), self.conf('password') ) else: self.rt = RTorrent(self.conf('url')) return self.rt def _update_provider_group(self, name, data): if data.get('seed_time'): log.info('seeding time ignored, not supported') if not name: return False if not self.connect(): return False views = self.rt.get_views() if name not in views: self.rt.create_group(name) group = self.rt.get_group(name) try: if data.get('seed_ratio'): ratio = int(float(data.get('seed_ratio')) * 100) log.debug('Updating provider ratio to %s, group name: %s', (ratio, name)) # Explicitly set all group options to ensure it is setup correctly group.set_upload('1M') group.set_min(ratio) group.set_max(ratio) group.set_command('d.stop') group.enable() else: # Reset group action and disable it group.set_command() group.disable() except MethodError, err: log.error('Unable to set group options: %s', err.msg) return False return True