Example #1
0
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)
Example #2
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_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
Example #3
0
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
Example #4
0
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
Example #5
0
class RTorrentApplet(gnaf.Gnaf):
    settings = {
        'icon':{
            'idle':'idle.png',
            'new':'new.png',
            'downloading':'downloading.png',
            'seeding':'seeding.png',
            'both':'both.png',
            'stopped':'stopped.png'
        },
        'interval':5,
        'server':'http://localhost'
    }
    
    def initialize(self):
        self.rtorrent = RTorrent(self.settings['server'])
        self.uncompleted = []
        self.new = []
        return True
    
    def update(self):
        try:
            self.torrents = self.rtorrent.get_torrents()
            downloaded, uploaded, downspeed, upspeed = self.rtorrent.get_global_vars()
        except:
            self.tooltip = 'RTorrent or RPC server isn\'t running.'
            self.data = self.tooltip
            self.icon = 'idle'
            return False
        if len(self.torrents) == 0:
            self.uncompleted = []
            self.tooltip = 'No torrents opened.'
            self.data = self.tooltip
            self.icon = 'idle'
            return False
        
        self.filter_new()
        
        data = []
        downloading_count = 0
        seeding_count = 0
        completed_count = 0
        stopped_count = 0
        ETA = 0
        
        for t in self.torrents:
            if len(t['name']) > self.settings['menu-wrap']-16:
                t_name = t['name'][:self.settings['menu-wrap']-19] + '...'
            else:
                t_name = t['name']
            if t['state'] == 0:
                if t['percentage'] == 100.0:
                    state = 'completed'
                    state_symbol = '\xe2\x9c\x93'
                    completed_count += 1
                else:
                    state = 'stopped'
                    state_symbol = '\xc3\x97'
                    stopped_count += 1
            else:
                if t['percentage'] == 100.0:
                    state = 'seeding'
                    state_symbol = '\xe2\x86\x91'
                    seeding_count += 1
                else:
                    state = 'downloading'
                    state_symbol = '\xe2\x86\x93'
                    downloading_count += 1
            if t['ETA'] > ETA: ETA = t['ETA']
            info = [
                ('Full name',t['name']),
                ('ETA', seconds_to_str(t['ETA'])) if t['ETA'] > 0 else None,
                ('Downloaded', bytes_to_str(t['downloaded']) + ((' / ' + bytes_to_str(t['size'])) if t['percentage'] != 100.0 else '')),
                ('Uploaded', bytes_to_str(t['uploaded'])),
                ('Ratio', '%.2f' % t['ratio']),
                ('Down', bytes_to_str(t['downspeed'], True) + '/s') if t['percentage'] != 100.0 or t['state'] != 0 else None,
                ('Up', bytes_to_str(t['upspeed'], True) + '/s') if t['state'] != 0 else None,
                ('Peers', '%i peers / %i seeds (%i)' % (t['peers'], t['seeds'], t['total-peers'])),
                ('Files', str(t['files']) + ' / ' + str(t['total-files'])),
                ('Total size', bytes_to_str(t['total-size'])) if t['total-size'] != t['downloaded'] else None
            ]
            info = [i for i in info if i != None]
            data.append((
                '[%s] %s (%s%%)' % (state_symbol, t_name, ('%.2f' % t['percentage']) if t['percentage'] != 100.0 else '100'),
                formatTooltip(info)
            ))
        self.data = data
        
        tooltip = [
            '%i\xe2\x86\x93 / %i\xe2\x86\x91 / %i\xe2\x9c\x93 / %i\xc3\x97' % (downloading_count, seeding_count, completed_count, stopped_count),
            ('Downloaded', bytes_to_str(downloaded)),
            ('Uploaded', bytes_to_str(uploaded)),
            ('Down', bytes_to_str(downspeed, True) + '/s'),
            ('Up', bytes_to_str(upspeed, True) + '/s'),
            ('ETA', seconds_to_str(ETA)) if ETA > 0 else None
        ]
        tooltip = [t for t in tooltip if t != None]
        self.tooltip = formatTooltip(tooltip)
        
        if len(self.new) > 0:
            self.icon = 'new'
        elif downloading_count > 0:
            if seeding_count > 0:
                self.icon = 'both'
            else:
                self.icon = 'downloading'
        elif seeding_count > 0:
            self.icon = 'seeding'
        else:
            self.icon = 'stopped'
        
        return (len(self.new) > 0)
    
    def notify(self):
        if len(self.new) == 0:
            return False
        notifications = []
        for n in self.new:
            notifications.append((
                n['name'],
                formatTooltip([
                    ('Uploaded', bytes_to_str(n['uploaded'])),
                    ('Ratio', '%.2f' % n['ratio'])
                ])
            ))
        self.notifications = notifications
        return True
    
    def filter_new(self):
        completed = [t for t in self.torrents if t['percentage'] == 100]
        self.new = []
        for u in self.uncompleted:
            for c in completed:
                if u['id'] == c['id']:
                    self.new.append(c)
                    completed.remove(c)
                    break
        self.uncompleted = [t for t in self.torrents if t['percentage'] < 100]
Example #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 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')

        self.rt = RTorrent(
            url,
            self.conf('username'),
            self.conf('password')
        )

        self.error_msg = ''
        try:
            self.rt._verify_conn()
        except AssertionError as e:
            self.error_msg = e.message
            self.rt = None

        return self.rt

    def test(self):
        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):
        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):
        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
Example #7
0
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)
Example #8
0
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
	)
Example #9
0
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())
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
Example #11
0
def format_speed(bits):
    units = ("KB", "MB", "GB", "TB")
    # convert bits to kilobits before calculating (we dont want 0.0 b/s)
    bits /= float(1024)
    speed_reduced, unit_index = calc_size(bits)
    speed_formatted = "{0:.1f}{1}/s".format(speed_reduced,
                                        units[unit_index])
    return(speed_formatted)


table = texttable.Texttable(max_width=350)
table.set_precision(2)
table.set_deco(texttable.Texttable.HEADER)
r = RTorrent(url="http://*****:*****@rtorrent.opensrc.mx")
for torrent in r.get_torrents():
    complete_per = format_percentage( torrent.completed_bytes, torrent.size_bytes)
    connected = torrent.get_peers_connected()
    total = connected + torrent.get_peers_not_connected()
    table.add_row([
        torrent.get_name(),
        "%s of %s" %(connected,total),
        "%s%%" % complete_per,
        format_size(torrent.size_bytes),
        format_ratio(torrent.ratio),
    ])
output = table.draw()
print (output,)

"""
if output: print output