Example #1
0
    def __init__(self):
        self.alert = Alert()
        self.alert.register_handler(
            'session_stats_alert',
            self.on_session_stats_alert
        )

        self.session_stats_future = None
Example #2
0
    def __init__(self, config, state_dir=None):
        self.config = config
        self.session = None
        self.hooks = Hooks(Path(self.config.path, 'hooks'))
        if state_dir is None:
            self.state_dir = Path(
                Path.home(), '.local', 'share', 'spritzle', 'state')
        else:
            self.state_dir = state_dir
        # TODO check dir for rw, etc
        self.state_dir.mkdir(parents=True, exist_ok=True)
        self.session_stats_future = None
        # A place to keep additional data on torrents, that isn't stored in
        # libtorrent.  This is key'd on info_hash.
        self.torrent_data = {}

        # Store state of outstanding save resume data alerts
        self.resume_data_futures = {}

        self.alert = Alert()
        self.alert.register_handler(
            'session_stats_alert',
            self.on_session_stats_alert
        )
        self.alert.register_handler(
            'status_notification',
            self.on_status_notification_alert
        )
        self.alert.register_handler(
            'save_resume_data_alert',
            self.on_save_resume_data_alert
        )
        self.alert.register_handler(
            'save_resume_data_failed_alert',
            self.on_save_resume_data_failed_alert
        )
        self.alert.register_handler(
            'add_torrent_alert',
            self.on_add_torrent_alert
        )
Example #3
0
class Core(object):
    def __init__(self):
        self.alert = Alert()
        self.alert.register_handler(
            'session_stats_alert',
            self.on_session_stats_alert
        )

        self.session_stats_future = None

    def init(self, config_dir):
        self.config = Config('spritzle.conf', config_dir)
        self.session = lt.session({
            'alert_mask': (int(lt.alert.category_t.error_notification) |
                           int(lt.alert.category_t.peer_notification) |
                           int(lt.alert.category_t.port_mapping_notification) |
                           int(lt.alert.category_t.storage_notification) |
                           int(lt.alert.category_t.tracker_notification) |
                           int(lt.alert.category_t.status_notification) |
                           int(lt.alert.category_t.ip_block_notification) |
                           int(lt.alert.category_t.performance_warning) |
                           int(lt.alert.category_t.stats_notification) |
                           int(lt.alert.category_t.session_log_notification) |
                           int(lt.alert.category_t.torrent_log_notification) |
                           int(lt.alert.category_t.peer_log_notification)),
            'user_agent': 'Spritzle/%s libtorrent/%s' % (
                pkg_resources.require("spritzle")[0].version,
                lt.__version__),
            })

        self.alert.start(self.session)

    def stop(self):
        self.alert.stop()

    def on_session_stats_alert(self, alert):
        self.session_stats_future.set_result(alert.values)

    async def get_session_status(self):
        if self.session_stats_future is None or \
                self.session_stats_future.done():
            self.session_stats_future = asyncio.Future()
            self.session.post_session_stats()

        await self.session_stats_future
        return self.session_stats_future.result()
Example #4
0
class Core(object):

    def __init__(self, config, state_dir=None):
        self.config = config
        self.session = None
        self.hooks = Hooks(Path(self.config.path, 'hooks'))
        if state_dir is None:
            self.state_dir = Path(
                Path.home(), '.local', 'share', 'spritzle', 'state')
        else:
            self.state_dir = state_dir
        # TODO check dir for rw, etc
        self.state_dir.mkdir(parents=True, exist_ok=True)
        self.session_stats_future = None
        # A place to keep additional data on torrents, that isn't stored in
        # libtorrent.  This is key'd on info_hash.
        self.torrent_data = {}

        # Store state of outstanding save resume data alerts
        self.resume_data_futures = {}

        self.alert = Alert()
        self.alert.register_handler(
            'session_stats_alert',
            self.on_session_stats_alert
        )
        self.alert.register_handler(
            'status_notification',
            self.on_status_notification_alert
        )
        self.alert.register_handler(
            'save_resume_data_alert',
            self.on_save_resume_data_alert
        )
        self.alert.register_handler(
            'save_resume_data_failed_alert',
            self.on_save_resume_data_failed_alert
        )
        self.alert.register_handler(
            'add_torrent_alert',
            self.on_add_torrent_alert
        )

    async def start(self, settings=None):
        log.debug('Core starting..')
        if settings is None:
            settings = {
                'alert_mask':
                    (
                        int(lt.alert.category_t.error_notification) |
                        int(lt.alert.category_t.peer_notification) |
                        int(lt.alert.category_t.port_mapping_notification) |
                        int(lt.alert.category_t.storage_notification) |
                        int(lt.alert.category_t.tracker_notification) |
                        int(lt.alert.category_t.status_notification) |
                        int(lt.alert.category_t.ip_block_notification) |
                        int(lt.alert.category_t.performance_warning) |
                        int(lt.alert.category_t.stats_notification) |
                        int(lt.alert.category_t.session_log_notification) |
                        int(lt.alert.category_t.torrent_log_notification) |
                        int(lt.alert.category_t.peer_log_notification)
                    ),
                'user_agent': 'Spritzle/%s libtorrent/%s' % (
                    pkg_resources.require("spritzle")[0].version,
                    lt.__version__),
            }
        self.session = lt.session(settings)
        await self.load_session_state()
        await self.alert.start(self.session)
        await self.load_resume_data()
        log.debug('Core started.')

    async def stop(self):
        log.debug('Core stopping..')
        await self.save_session_state()
        await self.save_resume_data()
        await self.alert.stop()
        del self.session
        self.session = None
        log.debug('Core stopped..')

    async def save_session_state(self):
        state = await asyncio.get_event_loop().run_in_executor(
            None, functools.partial(self.session.save_state))
        f = Path(self.state_dir, 'session.state')
        f.write_bytes(lt.bencode(state))

    async def load_session_state(self):
        f = Path(self.state_dir, 'session.state')
        log.info(f'Loading session state from: {f}')
        if f.exists():
            await asyncio.get_event_loop().run_in_executor(
                None, functools.partial(self.session.load_state), f.read_bytes())

    async def load_resume_data(self):
        log.info(f'Loading resume data from {self.state_dir}')
        for f in self.state_dir.iterdir():
            if f.suffix == '.resume':
                log.info(f'Found {f.name}, attempting add..')
                b = f.read_bytes()
                atp = lt.read_resume_data(b)
                try:
                    await asyncio.get_event_loop().run_in_executor(
                        None, functools.partial(self.session.add_torrent), atp)
                except RuntimeError as e:
                    log.error(f'Error loading resume data {f}: {e}')

                d = lt.bdecode(b)

                info_hash = binascii.hexlify(d[b'info-hash']).decode()
                self.torrent_data[info_hash] = {}
                for key, value in d.items():
                    if key.startswith(b'spritzle.'):
                        self.torrent_data[info_hash][key.decode()] = value

    async def on_session_stats_alert(self, alert):
        self.session_stats_future.set_result(alert.values)

    async def get_session_status(self):
        if self.session_stats_future is None or \
                self.session_stats_future.done():
            self.session_stats_future = asyncio.Future()
            self.session.post_session_stats()

        await self.session_stats_future
        return self.session_stats_future.result()

    def get_torrent_tags(self, info_hash):
        return self.torrent_data.get(info_hash, {}).get('spritzle.tags', [])

    async def on_status_notification_alert(self, alert):
        if hasattr(alert, 'handle'):
            if alert.what() == 'torrent_removed_alert':
                info_hash = str(alert.info_hash)
            info_hash = str(alert.handle.info_hash())
            self.hooks.run_hooks(
                alert.what(),
                info_hash,
                ','.join(self.get_torrent_tags(info_hash)))

    async def save_resume_data(self):
        for torrent in self.session.get_torrents():
            if torrent.need_save_resume_data():
                self.resume_data_futures[str(torrent.info_hash())] = asyncio.Future()
                torrent.save_resume_data(
                    flags=(
                        int(lt.save_resume_flags_t.flush_disk_cache) |
                        int(lt.save_resume_flags_t.save_info_dict)
                    ),
                )
        await asyncio.gather(*self.resume_data_futures.values())

    async def on_save_resume_data_alert(self, alert):
        info_hash = str(alert.handle.info_hash())
        p = Path(self.state_dir, alert.torrent_name + '.resume')
        r = lt.write_resume_data(alert.params)
        r.update(self.torrent_data[info_hash])
        p.write_bytes(lt.bencode(r))
        if info_hash in self.resume_data_futures:
            self.resume_data_futures.pop(info_hash).set_result(True)

    async def on_save_resume_data_failed_alert(self, alert):
        log.error(
            f'Error saving resume_data for torrent {alert.torrent_name} '
            f'error: {alert.error.message()}')
        info_hash = str(alert.handle.info_hash())
        if info_hash in self.resume_data_futures:
            # We don't really care if this fails right now, maybe in the future
            # we should raise an exception.
            self.resume_data_futures.pop(info_hash).set_result(True)

    async def on_add_torrent_alert(self, alert):
        try:
            alert.handle.save_resume_data()
        except RuntimeError as e:
            # An invalid handle can occur here if a torrent is added and
            # removed in quick succession.
            log.error(e)

    async def on_state_changed_alert(self, alert):
        if alert.handle.need_save_resume_data():
            alert.handle.save_resume_data()