Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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['authtype'] = sickbeard.TORRENT_AUTH_TYPE

        if not sickbeard.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

    def _add_torrent_uri(self, result):
        filedata = None

        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.lower())

            if sickbeard.TORRENT_PATH:
                torrent.set_directory(sickbeard.TORRENT_PATH)

            # Start torrent
            torrent.start()

            return True

        except Exception as e:
            logger.log(traceback.format_exc(), logger.DEBUG)
            return False

    def _add_torrent_file(self, result):
        filedata = None

        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.lower())

            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 as e:
            logger.log(traceback.format_exc(), logger.DEBUG)
            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
Ejemplo n.º 5
0
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'
Ejemplo n.º 6
0
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_hash = 0
        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':
            # Send magnet to rTorrent
            torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
            # Send request to rTorrent
            try:
                torrent = self.rt.load_magnet(data.get('url'), torrent_hash)

                if not torrent:
                    log.error('Unable to find the torrent, did it fail to load?')
                    return False

            except Exception as err:
                log.error('Failed to send magnet to rTorrent: %s', err)
                return False

        if data.get('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

            except Exception as err:
                log.error('Failed to send torrent to rTorrent: %s', err)
                return False

        try:
            # 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
Ejemplo n.º 7
0
class rTorrent(DownloaderBase):

    protocol = ['torrent', 'torrent_magnet']
    rt = None
    error_msg = ''

    # Migration url to host options
    def __init__(self):
        super(rTorrent, self).__init__()

        add_event('app.load', self.migrate)
        add_event('setting.save.rtorrent.*.after', self.settings_changed)

    def migrate(self):

        url = self.conf('url')
        if url:
            host_split = split_string(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 settings_changed(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 get_auth(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 get_verify_ssl(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 = clean_host(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.get_auth(),
            verify_ssl=self.get_verify_ssl()
        )

        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_hash = 0
        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':
            # Send magnet to rTorrent
            torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper()
            # Send request to rTorrent
            try:
                torrent = self.rt.load_magnet(data.get('url'), torrent_hash)

                if not torrent:
                    log.error('Unable to find the torrent, did it fail to load?')
                    return False

            except Exception as err:
                log.error('Failed to send magnet to rTorrent: %s', err)
                return False

        if data.get('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

            except Exception as err:
                log.error('Failed to send torrent to rTorrent: %s', err)
                return False

        try:
            # 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.download_return_id(torrent_hash)

        except Exception as err:
            log.error('Failed to send torrent to rTorrent: %s', err)
            return False

    def get_torrent_status(self, torrent):
        if not torrent.complete:
            return 'busy'

        if torrent.open:
            return 'seeding'

        return 'completed'

    def get_all_download_status(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.get_torrent_status(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 remove_failed(self, release_download):
        log.info('%s failed downloading, deleting...', release_download['name'])
        return self.process_complete(release_download, delete_files=True)

    def process_complete(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
Ejemplo n.º 8
0
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'
Ejemplo n.º 9
0
class RTorrentAPI(GenericClient):  # pylint: disable=invalid-name
    def __init__(self, host=None, username=None, password=None):
        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 sickbeard.TORRENT_AUTH_TYPE != 'none':
            tp_kwargs['authtype'] = sickbeard.TORRENT_AUTH_TYPE

        if not sickbeard.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

    def _add_torrent_uri(self, result):

        if not (self.auth or 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()
        except Exception as error:  # pylint: disable=broad-except
            logger.log('Error while sending torrent: {error}'.format  # pylint: disable=no-member
                       (error=ex(error)), logger.WARNING)
            return False
        else:
            return True

    def _add_torrent_file(self, result):

        if not (self.auth or result):
            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)

            # Start torrent
            torrent.start()
        except Exception as msg:  # pylint: disable=broad-except
            logger.log('Error while sending torrent: {error}'.format  # pylint: disable=no-member
                       (error=ex(msg)), logger.WARNING)
            return False
        else:
            return True

    def _set_torrent_ratio(self, name):
        _ = name

        return True

    def test_authentication(self):
        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'