def __init__(self, listen_interface=None): log.debug("Core init..") component.Component.__init__(self, "Core") # Start the libtorrent session log.info("Starting libtorrent %s session..", lt.version) # Create the client fingerprint version = [ int(value.split("-")[0]) for value in deluge.common.get_version().split(".") ] while len(version) < 4: version.append(0) self.session = lt.session(lt.fingerprint("DE", *version), flags=0) # Load the session state if available self.__load_session_state() # Set the user agent self.settings = lt.session_settings() self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \ { 'deluge_version': deluge.common.get_version(), 'lt_version': self.get_libtorrent_version().rpartition(".")[0] } # Set session settings self.settings.send_redundant_have = True if deluge.common.windows_check(): self.settings.disk_io_write_mode = \ lt.io_buffer_mode_t.disable_os_cache self.settings.disk_io_read_mode = \ lt.io_buffer_mode_t.disable_os_cache self.session.set_settings(self.settings) # Load metadata extension self.session.add_extension(lt.create_metadata_plugin) self.session.add_extension(lt.create_ut_metadata_plugin) self.session.add_extension(lt.create_smart_ban_plugin) # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # Get the core config self.config = deluge.configmanager.ConfigManager("core.conf") self.config.run_converter((0, 1), 2, self.__migrate_config_1_to_2) self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self.__old_interface = None if listen_interface: self.__old_interface = self.config["listen_interface"] self.config["listen_interface"] = listen_interface
def __init__(self, listen_interface=None): log.debug("Core init..") component.Component.__init__(self, "Core") # Start the libtorrent session log.info("Starting libtorrent %s session..", lt.version) # Create the client fingerprint version = [ int(value.split("-")[0]) for value in deluge.common.get_version().split(".") ] while len(version) < 4: version.append(0) # Note: All libtorrent python bindings to set plugins/extensions need to be disabled # due to GIL issue. https://code.google.com/p/libtorrent/issues/detail?id=369 # Setting session flags to 1 enables all libtorrent default plugins self.session = lt.session(lt.fingerprint("DE", *version), flags=1) # Load the session state if available self.__load_session_state() # Set the user agent self.settings = lt.session_settings() self.settings.user_agent = "Deluge/%(deluge_version)s Libtorrent/%(lt_version)s" % \ { 'deluge_version': deluge.common.get_version(), 'lt_version': self.get_libtorrent_version().rpartition(".")[0] } # Increase the alert queue size so that alerts don't get lost self.settings.alert_queue_size = 10000 # Set session settings self.settings.send_redundant_have = True if deluge.common.windows_check(): self.settings.disk_io_write_mode = \ lt.io_buffer_mode_t.disable_os_cache self.settings.disk_io_read_mode = \ lt.io_buffer_mode_t.disable_os_cache self.session.set_settings(self.settings) # Load metadata extension # Note: All libtorrent python bindings to set plugins/extensions need to be disabled # due to GIL issue. https://code.google.com/p/libtorrent/issues/detail?id=369 # self.session.add_extension(lt.create_metadata_plugin) # self.session.add_extension(lt.create_ut_metadata_plugin) # self.session.add_extension(lt.create_smart_ban_plugin) # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # Get the core config self.config = deluge.configmanager.ConfigManager("core.conf") self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self.__old_interface = None if listen_interface: self.__old_interface = self.config["listen_interface"] self.config["listen_interface"] = listen_interface
def __init__(self, listen_interface=None): log.debug("Core init..") component.Component.__init__(self, "Core") # Start the libtorrent session log.info("Starting libtorrent %s session..", lt.version) # Create the client fingerprint version = deluge.common.VersionSplit( deluge.common.get_version()).version while len(version) < 4: version.append(0) # In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue. # https://code.google.com/p/libtorrent/issues/detail?id=369 if deluge.common.VersionSplit( lt.version) >= deluge.common.VersionSplit("0.16.7.0"): self.session = lt.session(lt.fingerprint("DE", *version), flags=0) else: # Setting session flags to 1 enables all libtorrent default plugins self.session = lt.session(lt.fingerprint("DE", *version), flags=1) # Load the session state if available self.__load_session_state() # Set the user agent self.settings = lt.session_settings() self.settings.user_agent = "Deluge %s" % deluge.common.get_version() # Increase the alert queue size so that alerts don't get lost self.settings.alert_queue_size = 10000 # Set session settings self.settings.send_redundant_have = True if deluge.common.windows_check( ) and lt.version_major == 0 and lt.version_minor <= 15: self.settings.disk_io_write_mode = \ lt.io_buffer_mode_t.disable_os_cache self.settings.disk_io_read_mode = \ lt.io_buffer_mode_t.disable_os_cache self.session.set_settings(self.settings) # Load metadata extension # In libtorrent versions below 0.16.7.0 disable extension bindings due to GIL issue. # https://code.google.com/p/libtorrent/issues/detail?id=369 if deluge.common.VersionSplit( lt.version) >= deluge.common.VersionSplit("0.16.7.0"): self.session.add_extension("metadata_transfer") self.session.add_extension("ut_metadata") self.session.add_extension("smart_ban") # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.autoadd = AutoAdd() self.authmanager = AuthManager() # New release check information self.new_release = None # Get the core config self.config = deluge.configmanager.ConfigManager("core.conf") # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self.__old_interface = None if listen_interface: if deluge.common.is_ip(listen_interface): self.__old_interface = self.config["listen_interface"] self.config["listen_interface"] = listen_interface else: log.error("Invalid listen interface (must be IP Address): %s", listen_interface)
def __init__(self, listen_interface=None, outgoing_interface=None, read_only_config_keys=None): component.Component.__init__(self, 'Core') # Start the libtorrent session. user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION) peer_id = self._create_peer_id(DELUGE_VER) log.debug('Starting session (peer_id: %s, user_agent: %s)', peer_id, user_agent) settings_pack = { 'peer_fingerprint': peer_id, 'user_agent': user_agent, 'ignore_resume_timestamps': True, } self.session = lt.session(settings_pack, flags=0) # Load the settings, if available. self._load_session_state() # Enable libtorrent extensions # Allows peers to download the metadata from the swarm directly self.session.add_extension('ut_metadata') # Ban peers that sends bad data self.session.add_extension('smart_ban') # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # External IP Address from libtorrent self.external_ip = None self.eventmanager.register_event_handler('ExternalIPEvent', self._on_external_ip_event) # GeoIP instance with db loaded self.geoip_instance = None # These keys will be dropped from the set_config() RPC and are # configurable from the command-line. self.read_only_config_keys = read_only_config_keys log.debug('read_only_config_keys: %s', read_only_config_keys) # Get the core config self.config = ConfigManager('core.conf') self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self._old_listen_interface = None if listen_interface: if deluge.common.is_ip(listen_interface): self._old_listen_interface = self.config['listen_interface'] self.config['listen_interface'] = listen_interface else: log.error( 'Invalid listen interface (must be IP Address): %s', listen_interface, ) self._old_outgoing_interface = None if outgoing_interface: self._old_outgoing_interface = self.config['outgoing_interface'] self.config['outgoing_interface'] = outgoing_interface # New release check information self.__new_release = None # Session status timer self.session_status = {k.name: 0 for k in lt.session_stats_metrics()} self._session_prev_bytes = {k: 0 for k in SESSION_RATES_MAPPING} # Initiate other session status keys. self.session_status.update(self._session_prev_bytes) hit_ratio_keys = ['write_hit_ratio', 'read_hit_ratio'] self.session_status.update({k: 0.0 for k in hit_ratio_keys}) self.session_status_timer_interval = 0.5 self.session_status_timer = task.LoopingCall( self.session.post_session_stats) self.alertmanager.register_handler('session_stats_alert', self._on_alert_session_stats) self.session_rates_timer_interval = 2 self.session_rates_timer = task.LoopingCall(self._update_session_rates)
class Core(component.Component): def __init__(self, listen_interface=None, outgoing_interface=None, read_only_config_keys=None): component.Component.__init__(self, 'Core') # Start the libtorrent session. user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION) peer_id = self._create_peer_id(DELUGE_VER) log.debug('Starting session (peer_id: %s, user_agent: %s)', peer_id, user_agent) settings_pack = { 'peer_fingerprint': peer_id, 'user_agent': user_agent, 'ignore_resume_timestamps': True, } self.session = lt.session(settings_pack, flags=0) # Load the settings, if available. self._load_session_state() # Enable libtorrent extensions # Allows peers to download the metadata from the swarm directly self.session.add_extension('ut_metadata') # Ban peers that sends bad data self.session.add_extension('smart_ban') # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # External IP Address from libtorrent self.external_ip = None self.eventmanager.register_event_handler('ExternalIPEvent', self._on_external_ip_event) # GeoIP instance with db loaded self.geoip_instance = None # These keys will be dropped from the set_config() RPC and are # configurable from the command-line. self.read_only_config_keys = read_only_config_keys log.debug('read_only_config_keys: %s', read_only_config_keys) # Get the core config self.config = ConfigManager('core.conf') self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self._old_listen_interface = None if listen_interface: if deluge.common.is_ip(listen_interface): self._old_listen_interface = self.config['listen_interface'] self.config['listen_interface'] = listen_interface else: log.error( 'Invalid listen interface (must be IP Address): %s', listen_interface, ) self._old_outgoing_interface = None if outgoing_interface: self._old_outgoing_interface = self.config['outgoing_interface'] self.config['outgoing_interface'] = outgoing_interface # New release check information self.__new_release = None # Session status timer self.session_status = {k.name: 0 for k in lt.session_stats_metrics()} self._session_prev_bytes = {k: 0 for k in SESSION_RATES_MAPPING} # Initiate other session status keys. self.session_status.update(self._session_prev_bytes) hit_ratio_keys = ['write_hit_ratio', 'read_hit_ratio'] self.session_status.update({k: 0.0 for k in hit_ratio_keys}) self.session_status_timer_interval = 0.5 self.session_status_timer = task.LoopingCall( self.session.post_session_stats) self.alertmanager.register_handler('session_stats_alert', self._on_alert_session_stats) self.session_rates_timer_interval = 2 self.session_rates_timer = task.LoopingCall(self._update_session_rates) def start(self): """Starts the core""" self.session_status_timer.start(self.session_status_timer_interval) self.session_rates_timer.start(self.session_rates_timer_interval, now=False) def stop(self): log.debug('Core stopping...') if self.session_status_timer.running: self.session_status_timer.stop() if self.session_rates_timer.running: self.session_rates_timer.stop() # Save the libtorrent session state self._save_session_state() # We stored a copy of the old interface value if self._old_listen_interface is not None: self.config['listen_interface'] = self._old_listen_interface if self._old_outgoing_interface is not None: self.config['outgoing_interface'] = self._old_outgoing_interface # Make sure the config file has been saved self.config.save() def shutdown(self): pass def apply_session_setting(self, key, value): self.apply_session_settings({key: value}) def apply_session_settings(self, settings): """Apply libtorrent session settings. Args: settings (dict): A dict of lt session settings to apply. """ self.session.apply_settings(settings) @staticmethod def _create_peer_id(version): """Create a peer_id fingerprint. This creates the peer_id and modifies the release char to identify pre-release and development version. Using ``D`` for dev, daily or nightly builds, ``a, b, r`` for pre-releases and ``s`` for stable releases. Examples: ``--<client><client><major><minor><micro><release>--`` ``--DE200D--`` (development version of 2.0.0) ``--DE200s--`` (stable release of v2.0.0) ``--DE201b--`` (beta pre-release of v2.0.1) Args: version (str): The version string in PEP440 dotted notation. Returns: str: The formattted peer_id with Deluge prefix e.g. '--DE200s--' """ split = deluge.common.VersionSplit(version) # Fill list with zeros to length of 4 and use lt to create fingerprint. version_list = split.version + [0] * (4 - len(split.version)) peer_id = lt.generate_fingerprint('DE', *version_list) def substitute_chr(string, idx, char): """Fast substitute single char in string.""" return string[:idx] + char + string[idx + 1:] if split.dev: release_chr = 'D' elif split.suffix: # a (alpha), b (beta) or r (release candidate). release_chr = split.suffix[0].lower() else: release_chr = 's' peer_id = substitute_chr(peer_id, 6, release_chr) return peer_id def _save_session_state(self): """Saves the libtorrent session state""" filename = 'session.state' filepath = get_config_dir(filename) filepath_bak = filepath + '.bak' filepath_tmp = filepath + '.tmp' try: if os.path.isfile(filepath): log.debug('Creating backup of %s at: %s', filename, filepath_bak) shutil.copy2(filepath, filepath_bak) except IOError as ex: log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex) else: log.info('Saving the %s at: %s', filename, filepath) try: with open(filepath_tmp, 'wb') as _file: _file.write(lt.bencode(self.session.save_state())) _file.flush() os.fsync(_file.fileno()) shutil.move(filepath_tmp, filepath) except (IOError, EOFError) as ex: log.error('Unable to save %s: %s', filename, ex) if os.path.isfile(filepath_bak): log.info('Restoring backup of %s from: %s', filename, filepath_bak) shutil.move(filepath_bak, filepath) def _load_session_state(self): """Loads the libtorrent session state Returns: dict: A libtorrent sesion state, empty dict if unable to load it. """ filename = 'session.state' filepath = get_config_dir(filename) filepath_bak = filepath + '.bak' for _filepath in (filepath, filepath_bak): log.debug('Opening %s for load: %s', filename, _filepath) try: with open(_filepath, 'rb') as _file: state = lt.bdecode(_file.read()) except (IOError, EOFError, RuntimeError) as ex: log.warning('Unable to load %s: %s', _filepath, ex) else: log.info('Successfully loaded %s: %s', filename, _filepath) self.session.load_state(state) def _on_alert_session_stats(self, alert): """The handler for libtorrent session stats alert""" self.session_status.update(alert.values) self._update_session_cache_hit_ratio() def _update_session_cache_hit_ratio(self): """Calculates the cache read/write hit ratios for session_status.""" blocks_written = self.session_status['disk.num_blocks_written'] blocks_read = self.session_status['disk.num_blocks_read'] if blocks_written: self.session_status['write_hit_ratio'] = ( blocks_written - self.session_status['disk.num_write_ops']) / blocks_written else: self.session_status['write_hit_ratio'] = 0.0 if blocks_read: self.session_status['read_hit_ratio'] = ( self.session_status['disk.num_blocks_cache_hits'] / blocks_read) else: self.session_status['read_hit_ratio'] = 0.0 def _update_session_rates(self): """Calculate session status rates. Uses polling interval and counter difference for session_status rates. """ for rate_key, prev_bytes in list(self._session_prev_bytes.items()): new_bytes = self.session_status[SESSION_RATES_MAPPING[rate_key]] self.session_status[rate_key] = ( new_bytes - prev_bytes) / self.session_rates_timer_interval # Store current value for next update. self._session_prev_bytes[rate_key] = new_bytes def get_new_release(self): log.debug('get_new_release') try: self.new_release = ( urlopen('http://download.deluge-torrent.org/version-2.0').read( ).decode().strip()) except URLError as ex: log.debug('Unable to get release info from website: %s', ex) else: self.check_new_release() def check_new_release(self): if self.new_release: log.debug('new_release: %s', self.new_release) if deluge.common.VersionSplit( self.new_release) > deluge.common.VersionSplit( deluge.common.get_version()): component.get('EventManager').emit( NewVersionAvailableEvent(self.new_release)) return self.new_release return False # Exported Methods @export def add_torrent_file_async(self, filename, filedump, options, save_state=True): """Adds a torrent file to the session asynchonously. Args: filename (str): The filename of the torrent. filedump (str): A base64 encoded string of torrent file contents. options (dict): The options to apply to the torrent upon adding. save_state (bool): If the state should be saved after adding the file. Returns: Deferred: The torrent ID or None. """ try: filedump = b64decode(filedump) except TypeError as ex: log.error('There was an error decoding the filedump string: %s', ex) try: d = self.torrentmanager.add_async( filedump=filedump, options=options, filename=filename, save_state=save_state, ) except RuntimeError as ex: log.error('There was an error adding the torrent file %s: %s', filename, ex) raise else: return d @export def prefetch_magnet_metadata(self, magnet, timeout=30): """Download magnet metadata without adding to Deluge session. Used by UIs to get magnet files for selection before adding to session. Args: magnet (str): The magnet uri. timeout (int): Number of seconds to wait before cancelling request. Returns: Deferred: A tuple of (torrent_id (str), metadata (dict)) for the magnet. """ def on_metadata(result, result_d): """Return result of torrent_id and metadata""" result_d.callback(result) return result d = self.torrentmanager.prefetch_metadata(magnet, timeout) # Use a seperate callback chain to handle existing prefetching magnet. result_d = defer.Deferred() d.addBoth(on_metadata, result_d) return result_d @export def add_torrent_file(self, filename, filedump, options): """Adds a torrent file to the session. Args: filename (str): The filename of the torrent. filedump (str): A base64 encoded string of the torrent file contents. options (dict): The options to apply to the torrent upon adding. Returns: str: The torrent_id or None. """ try: filedump = b64decode(filedump) except Exception as ex: log.error('There was an error decoding the filedump string: %s', ex) try: return self.torrentmanager.add(filedump=filedump, options=options, filename=filename) except RuntimeError as ex: log.error('There was an error adding the torrent file %s: %s', filename, ex) raise @export def add_torrent_files(self, torrent_files): """Adds multiple torrent files to the session asynchonously. Args: torrent_files (list of tuples): Torrent files as tuple of (filename, filedump, options). Returns: Deferred """ @defer.inlineCallbacks def add_torrents(): errors = [] last_index = len(torrent_files) - 1 for idx, torrent in enumerate(torrent_files): try: yield self.add_torrent_file_async( torrent[0], torrent[1], torrent[2], save_state=idx == last_index) except AddTorrentError as ex: log.warning('Error when adding torrent: %s', ex) errors.append(ex) defer.returnValue(errors) return task.deferLater(reactor, 0, add_torrents) @export def add_torrent_url(self, url, options, headers=None): """ Adds a torrent from a url. Deluge will attempt to fetch the torrent from url prior to adding it to the session. :param url: the url pointing to the torrent file :type url: string :param options: the options to apply to the torrent on add :type options: dict :param headers: any optional headers to send :type headers: dict :returns: a Deferred which returns the torrent_id as a str or None """ log.info('Attempting to add url %s', url) def on_download_success(filename): # We got the file, so add it to the session with open(filename, 'rb') as _file: data = _file.read() try: os.remove(filename) except OSError as ex: log.warning('Could not remove temp file: %s', ex) return self.add_torrent_file(filename, b64encode(data), options) def on_download_fail(failure): # Log the error and pass the failure onto the client log.error('Failed to add torrent from url %s', url) return failure tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent') os.close(tmp_fd) d = download_file(url, tmp_file, headers=headers, force_filename=True) d.addCallbacks(on_download_success, on_download_fail) return d @export def add_torrent_magnet(self, uri, options): """ Adds a torrent from a magnet link. :param uri: the magnet link :type uri: string :param options: the options to apply to the torrent on add :type options: dict :returns: the torrent_id :rtype: string """ log.debug('Attempting to add by magnet uri: %s', uri) return self.torrentmanager.add(magnet=uri, options=options) @export def remove_torrent(self, torrent_id, remove_data): """Removes a single torrent from the session. Args: torrent_id (str): The torrent ID to remove. remove_data (bool): If True, also remove the downloaded data. Returns: bool: True if removed successfully. Raises: InvalidTorrentError: If the torrent ID does not exist in the session. """ log.debug('Removing torrent %s from the core.', torrent_id) return self.torrentmanager.remove(torrent_id, remove_data) @export def remove_torrents(self, torrent_ids, remove_data): """Remove multiple torrents from the session. Args: torrent_ids (list): The torrent IDs to remove. remove_data (bool): If True, also remove the downloaded data. Returns: list: An empty list if no errors occurred otherwise the list contains tuples of strings, a torrent ID and an error message. For example: [('<torrent_id>', 'Error removing torrent')] """ log.info('Removing %d torrents from core.', len(torrent_ids)) def do_remove_torrents(): errors = [] for torrent_id in torrent_ids: try: self.torrentmanager.remove(torrent_id, remove_data=remove_data, save_state=False) except InvalidTorrentError as ex: errors.append((torrent_id, str(ex))) # Save the session state self.torrentmanager.save_state() if errors: log.warning('Failed to remove %d of %d torrents.', len(errors), len(torrent_ids)) return errors return task.deferLater(reactor, 0, do_remove_torrents) @export def get_session_status(self, keys): """Gets the session status values for 'keys', these keys are taking from libtorrent's session status. See: http://www.rasterbar.com/products/libtorrent/manual.html#status :param keys: the keys for which we want values :type keys: list :returns: a dictionary of {key: value, ...} :rtype: dict """ if not keys: return self.session_status status = {} for key in keys: try: status[key] = self.session_status[key] except KeyError: if key in DEPR_SESSION_STATUS_KEYS: new_key = DEPR_SESSION_STATUS_KEYS[key] log.debug( 'Deprecated session status key %s, please use %s', key, new_key) status[key] = self.session_status[new_key] else: log.warning('Session status key not valid: %s', key) return status @export def force_reannounce(self, torrent_ids): log.debug('Forcing reannouncment to: %s', torrent_ids) for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_reannounce() @export def pause_torrent(self, torrent_id): """Pauses a torrent""" log.debug('Pausing: %s', torrent_id) if not isinstance(torrent_id, string_types): self.pause_torrents(torrent_id) else: self.torrentmanager[torrent_id].pause() @export def pause_torrents(self, torrent_ids=None): """Pauses a list of torrents""" if not torrent_ids: torrent_ids = self.torrentmanager.get_torrent_list() for torrent_id in torrent_ids: self.pause_torrent(torrent_id) @export def connect_peer(self, torrent_id, ip, port): log.debug('adding peer %s to %s', ip, torrent_id) if not self.torrentmanager[torrent_id].connect_peer(ip, port): log.warning('Error adding peer %s:%s to %s', ip, port, torrent_id) @export def move_storage(self, torrent_ids, dest): log.debug('Moving storage %s to %s', torrent_ids, dest) for torrent_id in torrent_ids: if not self.torrentmanager[torrent_id].move_storage(dest): log.warning('Error moving torrent %s to %s', torrent_id, dest) @export def pause_session(self): """Pause the entire session""" if not self.session.is_paused(): self.session.pause() component.get('EventManager').emit(SessionPausedEvent()) @export def resume_session(self): """Resume the entire session""" if self.session.is_paused(): self.session.resume() for torrent_id in self.torrentmanager.torrents: self.torrentmanager[torrent_id].update_state() component.get('EventManager').emit(SessionResumedEvent()) @export def is_session_paused(self): """Returns the activity of the session""" return self.session.is_paused() @export def resume_torrent(self, torrent_id): """Resumes a torrent""" log.debug('Resuming: %s', torrent_id) if not isinstance(torrent_id, string_types): self.resume_torrents(torrent_id) else: self.torrentmanager[torrent_id].resume() @export def resume_torrents(self, torrent_ids=None): """Resumes a list of torrents""" if not torrent_ids: torrent_ids = self.torrentmanager.get_torrent_list() for torrent_id in torrent_ids: self.resume_torrent(torrent_id) def create_torrent_status( self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False, all_keys=False, ): try: status = self.torrentmanager[torrent_id].get_status( torrent_keys, diff, update=update, all_keys=all_keys) except KeyError: import traceback traceback.print_exc() # Torrent was probaly removed meanwhile return {} # Ask the plugin manager to fill in the plugin keys if len(plugin_keys) > 0 or all_keys: status.update( self.pluginmanager.get_status(torrent_id, plugin_keys)) return status @export def get_torrent_status(self, torrent_id, keys, diff=False): torrent_keys, plugin_keys = self.torrentmanager.separate_keys( keys, [torrent_id]) return self.create_torrent_status( torrent_id, torrent_keys, plugin_keys, diff=diff, update=True, all_keys=not keys, ) @export def get_torrents_status(self, filter_dict, keys, diff=False): """ returns all torrents , optionally filtered by filter_dict. """ torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict) d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=diff) def add_plugin_fields(args): status_dict, plugin_keys = args # Ask the plugin manager to fill in the plugin keys if len(plugin_keys) > 0: for key in status_dict: status_dict[key].update( self.pluginmanager.get_status(key, plugin_keys)) return status_dict d.addCallback(add_plugin_fields) return d @export def get_filter_tree(self, show_zero_hits=True, hide_cat=None): """ returns {field: [(value,count)] } for use in sidebar(s) """ return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat) @export def get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager return self.torrentmanager.get_torrent_list() @export def get_config(self): """Get all the preferences as a dictionary""" return self.config.config @export def get_config_value(self, key): """Get the config value for key""" return self.config.get(key) @export def get_config_values(self, keys): """Get the config values for the entered keys""" return {key: self.config.get(key) for key in keys} @export def set_config(self, config): """Set the config with values from dictionary""" # Load all the values into the configuration for key in config: if self.read_only_config_keys and key in self.read_only_config_keys: continue self.config[key] = config[key] @export def get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() @export def get_proxy(self): """Returns the proxy settings Returns: dict: Contains proxy settings. Notes: Proxy type names: 0: None, 1: Socks4, 2: Socks5, 3: Socks5 w Auth, 4: HTTP, 5: HTTP w Auth, 6: I2P """ settings = self.session.get_settings() proxy_type = settings['proxy_type'] proxy_hostname = (settings['i2p_hostname'] if proxy_type == 6 else settings['proxy_hostname']) proxy_port = settings['i2p_port'] if proxy_type == 6 else settings[ 'proxy_port'] proxy_dict = { 'type': proxy_type, 'hostname': proxy_hostname, 'username': settings['proxy_username'], 'password': settings['proxy_password'], 'port': proxy_port, 'proxy_hostnames': settings['proxy_hostnames'], 'proxy_peer_connections': settings['proxy_peer_connections'], 'proxy_tracker_connections': settings['proxy_tracker_connections'], } return proxy_dict @export def get_available_plugins(self): """Returns a list of plugins available in the core""" return self.pluginmanager.get_available_plugins() @export def get_enabled_plugins(self): """Returns a list of enabled plugins in the core""" return self.pluginmanager.get_enabled_plugins() @export def enable_plugin(self, plugin): return self.pluginmanager.enable_plugin(plugin) @export def disable_plugin(self, plugin): return self.pluginmanager.disable_plugin(plugin) @export def force_recheck(self, torrent_ids): """Forces a data recheck on torrent_ids""" for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_recheck() @export def set_torrent_options(self, torrent_ids, options): """Sets the torrent options for torrent_ids Args: torrent_ids (list): A list of torrent_ids to set the options for. options (dict): A dict of torrent options to set. See torrent.TorrentOptions class for valid keys. """ if 'owner' in options and not self.authmanager.has_account( options['owner']): raise DelugeError('Username "%s" is not known.' % options['owner']) if isinstance(torrent_ids, string_types): torrent_ids = [torrent_ids] for torrent_id in torrent_ids: self.torrentmanager[torrent_id].set_options(options) @export def set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" return self.torrentmanager[torrent_id].set_trackers(trackers) @deprecated @export def set_torrent_max_connections(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_connections'""" self.set_torrent_options([torrent_id], {'max_connections': value}) @deprecated @export def set_torrent_max_upload_slots(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_upload_slots'""" self.set_torrent_options([torrent_id], {'max_upload_slots': value}) @deprecated @export def set_torrent_max_upload_speed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_upload_speed'""" self.set_torrent_options([torrent_id], {'max_upload_speed': value}) @deprecated @export def set_torrent_max_download_speed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_download_speed'""" self.set_torrent_options([torrent_id], {'max_download_speed': value}) @deprecated @export def set_torrent_file_priorities(self, torrent_id, priorities): """Deprecated: Use set_torrent_options with 'file_priorities'""" self.set_torrent_options([torrent_id], {'file_priorities': priorities}) @deprecated @export def set_torrent_prioritize_first_last(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'prioritize_first_last'""" self.set_torrent_options([torrent_id], {'prioritize_first_last_pieces': value}) @deprecated @export def set_torrent_auto_managed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'auto_managed'""" self.set_torrent_options([torrent_id], {'auto_managed': value}) @deprecated @export def set_torrent_stop_at_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'stop_at_ratio'""" self.set_torrent_options([torrent_id], {'stop_at_ratio': value}) @deprecated @export def set_torrent_stop_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'stop_ratio'""" self.set_torrent_options([torrent_id], {'stop_ratio': value}) @deprecated @export def set_torrent_remove_at_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'remove_at_ratio'""" self.set_torrent_options([torrent_id], {'remove_at_ratio': value}) @deprecated @export def set_torrent_move_completed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'move_completed'""" self.set_torrent_options([torrent_id], {'move_completed': value}) @deprecated @export def set_torrent_move_completed_path(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'move_completed_path'""" self.set_torrent_options([torrent_id], {'move_completed_path': value}) @export def get_path_size(self, path): """Returns the size of the file or folder 'path' and -1 if the path is unaccessible (non-existent or insufficient privs)""" return deluge.common.get_path_size(path) @export def create_torrent( self, path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session, ): log.debug('creating torrent..') threading.Thread( target=self._create_torrent_thread, args=( path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session, ), ).start() def _create_torrent_thread( self, path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session, ): from deluge import metafile metafile.make_meta_file( path, tracker, piece_length, comment=comment, target=target, webseeds=webseeds, private=private, created_by=created_by, trackers=trackers, ) log.debug('torrent created!') if add_to_session: options = {} options['download_location'] = os.path.split(path)[0] with open(target, 'rb') as _file: filedump = b64encode(_file.read()) self.add_torrent_file( os.path.split(target)[1], filedump, options) @export def upload_plugin(self, filename, filedump): """This method is used to upload new plugins to the daemon. It is used when connecting to the daemon remotely and installing a new plugin on the client side. 'plugin_data' is a xmlrpc.Binary object of the file data, ie, plugin_file.read()""" try: filedump = b64decode(filedump) except TypeError as ex: log.error('There was an error decoding the filedump string!') log.exception(ex) return with open(os.path.join(get_config_dir(), 'plugins', filename), 'wb') as _file: _file.write(filedump) component.get('CorePluginManager').scan_for_plugins() @export def rescan_plugins(self): """ Rescans the plugin folders for new plugins """ component.get('CorePluginManager').scan_for_plugins() @export def rename_files(self, torrent_id, filenames): """ Rename files in torrent_id. Since this is an asynchronous operation by libtorrent, watch for the TorrentFileRenamedEvent to know when the files have been renamed. :param torrent_id: the torrent_id to rename files :type torrent_id: string :param filenames: a list of index, filename pairs :type filenames: ((index, filename), ...) :raises InvalidTorrentError: if torrent_id is invalid """ if torrent_id not in self.torrentmanager.torrents: raise InvalidTorrentError('torrent_id is not in session') def rename(): self.torrentmanager[torrent_id].rename_files(filenames) return task.deferLater(reactor, 0, rename) @export def rename_folder(self, torrent_id, folder, new_folder): """ Renames the 'folder' to 'new_folder' in 'torrent_id'. Watch for the TorrentFolderRenamedEvent which is emitted when the folder has been renamed successfully. :param torrent_id: the torrent to rename folder in :type torrent_id: string :param folder: the folder to rename :type folder: string :param new_folder: the new folder name :type new_folder: string :raises InvalidTorrentError: if the torrent_id is invalid """ if torrent_id not in self.torrentmanager.torrents: raise InvalidTorrentError('torrent_id is not in session') return self.torrentmanager[torrent_id].rename_folder( folder, new_folder) @export def queue_top(self, torrent_ids): log.debug('Attempting to queue %s to top', torrent_ids) # torrent_ids must be sorted in reverse before moving to preserve order for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True): try: # If the queue method returns True, then we should emit a signal if self.torrentmanager.queue_top(torrent_id): component.get('EventManager').emit( TorrentQueueChangedEvent()) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) @export def queue_up(self, torrent_ids): log.debug('Attempting to queue %s to up', torrent_ids) torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids) torrent_moved = True prev_queue_position = None # torrent_ids must be sorted before moving. for queue_position, torrent_id in sorted(torrents): # Move the torrent if and only if there is space (by not moving it we preserve the order) if torrent_moved or queue_position - prev_queue_position > 1: try: torrent_moved = self.torrentmanager.queue_up(torrent_id) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) # If the torrent moved, then we should emit a signal if torrent_moved: component.get('EventManager').emit(TorrentQueueChangedEvent()) else: prev_queue_position = queue_position @export def queue_down(self, torrent_ids): log.debug('Attempting to queue %s to down', torrent_ids) torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids) torrent_moved = True prev_queue_position = None # torrent_ids must be sorted before moving. for queue_position, torrent_id in sorted(torrents, reverse=True): # Move the torrent if and only if there is space (by not moving it we preserve the order) if torrent_moved or prev_queue_position - queue_position > 1: try: torrent_moved = self.torrentmanager.queue_down(torrent_id) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) # If the torrent moved, then we should emit a signal if torrent_moved: component.get('EventManager').emit(TorrentQueueChangedEvent()) else: prev_queue_position = queue_position @export def queue_bottom(self, torrent_ids): log.debug('Attempting to queue %s to bottom', torrent_ids) # torrent_ids must be sorted before moving to preserve order for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position): try: # If the queue method returns True, then we should emit a signal if self.torrentmanager.queue_bottom(torrent_id): component.get('EventManager').emit( TorrentQueueChangedEvent()) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) @export def glob(self, path): return glob.glob(path) @export def test_listen_port(self): """ Checks if the active port is open :returns: True if the port is open, False if not :rtype: bool """ port = self.get_listen_port() url = 'https://deluge-torrent.org/test_port.php?port=%s' % port agent = Agent(reactor, connectTimeout=30) d = agent.request(b'GET', url.encode()) def on_get_page(body): return bool(int(body)) def on_error(failure): log.warning('Error testing listen port: %s', failure) d.addCallback(readBody).addCallback(on_get_page) d.addErrback(on_error) return d @export def get_free_space(self, path=None): """ Returns the number of free bytes at path :param path: the path to check free space at, if None, use the default download location :type path: string :returns: the number of free bytes at path :rtype: int :raises InvalidPathError: if the path is invalid """ if not path: path = self.config['download_location'] try: return deluge.common.free_space(path) except InvalidPathError: return -1 def _on_external_ip_event(self, external_ip): self.external_ip = external_ip @export def get_external_ip(self): """ Returns the external ip address recieved from libtorrent. """ return self.external_ip @export def get_libtorrent_version(self): """ Returns the libtorrent version. :returns: the version :rtype: string """ return LT_VERSION @export def get_completion_paths(self, args): """ Returns the available path completions for the input value. """ return path_chooser_common.get_completion_paths(args) @export(AUTH_LEVEL_ADMIN) def get_known_accounts(self): return self.authmanager.get_known_accounts() @export(AUTH_LEVEL_NONE) def get_auth_levels_mappings(self): return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE) @export(AUTH_LEVEL_ADMIN) def create_account(self, username, password, authlevel): return self.authmanager.create_account(username, password, authlevel) @export(AUTH_LEVEL_ADMIN) def update_account(self, username, password, authlevel): return self.authmanager.update_account(username, password, authlevel) @export(AUTH_LEVEL_ADMIN) def remove_account(self, username): return self.authmanager.remove_account(username)
def __init__(self, listen_interface=None, read_only_config_keys=None): log.debug('Core init...') component.Component.__init__(self, 'Core') deluge_version = deluge.common.get_version() split_version = deluge.common.VersionSplit(deluge_version).version while len(split_version) < 4: split_version.append(0) deluge_fingerprint = lt.generate_fingerprint('DE', *split_version) user_agent = 'Deluge/{} libtorrent/{}'.format(deluge_version, self.get_libtorrent_version()) # Start the libtorrent session. log.debug('Starting session (fingerprint: %s, user_agent: %s)', deluge_fingerprint, user_agent) settings_pack = {'peer_fingerprint': deluge_fingerprint, 'user_agent': user_agent, 'ignore_resume_timestamps': True} self.session = lt.session(settings_pack, flags=0) # Load the settings, if available. self._load_session_state() # Enable libtorrent extensions # Allows peers to download the metadata from the swarm directly self.session.add_extension('ut_metadata') # Ban peers that sends bad data self.session.add_extension('smart_ban') # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # External IP Address from libtorrent self.external_ip = None self.eventmanager.register_event_handler('ExternalIPEvent', self._on_external_ip_event) # GeoIP instance with db loaded self.geoip_instance = None # These keys will be dropped from the set_config() RPC and are # configurable from the command-line. self.read_only_config_keys = read_only_config_keys log.debug('read_only_config_keys: %s', read_only_config_keys) # Get the core config self.config = ConfigManager('core.conf') self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self.__old_interface = None if listen_interface: if deluge.common.is_ip(listen_interface): self.__old_interface = self.config['listen_interface'] self.config['listen_interface'] = listen_interface else: log.error('Invalid listen interface (must be IP Address): %s', listen_interface) # New release check information self.__new_release = None # Session status timer self.session_status = {} self.session_status_timer_interval = 0.5 self.session_status_timer = task.LoopingCall(self.session.post_session_stats) self.alertmanager.register_handler('session_stats_alert', self._on_alert_session_stats) self._session_rates = {(k_rate, k_bytes): 0 for k_rate, k_bytes in SESSION_RATES_MAPPING.items()} self.session_rates_timer_interval = 2 self.session_rates_timer = task.LoopingCall(self._update_session_rates)
class Core(component.Component): def __init__(self, listen_interface=None, read_only_config_keys=None): log.debug('Core init...') component.Component.__init__(self, 'Core') deluge_version = deluge.common.get_version() split_version = deluge.common.VersionSplit(deluge_version).version while len(split_version) < 4: split_version.append(0) deluge_fingerprint = lt.generate_fingerprint('DE', *split_version) user_agent = 'Deluge/{} libtorrent/{}'.format(deluge_version, self.get_libtorrent_version()) # Start the libtorrent session. log.debug('Starting session (fingerprint: %s, user_agent: %s)', deluge_fingerprint, user_agent) settings_pack = {'peer_fingerprint': deluge_fingerprint, 'user_agent': user_agent, 'ignore_resume_timestamps': True} self.session = lt.session(settings_pack, flags=0) # Load the settings, if available. self._load_session_state() # Enable libtorrent extensions # Allows peers to download the metadata from the swarm directly self.session.add_extension('ut_metadata') # Ban peers that sends bad data self.session.add_extension('smart_ban') # Create the components self.eventmanager = EventManager() self.preferencesmanager = PreferencesManager() self.alertmanager = AlertManager() self.pluginmanager = PluginManager(self) self.torrentmanager = TorrentManager() self.filtermanager = FilterManager(self) self.authmanager = AuthManager() # New release check information self.new_release = None # External IP Address from libtorrent self.external_ip = None self.eventmanager.register_event_handler('ExternalIPEvent', self._on_external_ip_event) # GeoIP instance with db loaded self.geoip_instance = None # These keys will be dropped from the set_config() RPC and are # configurable from the command-line. self.read_only_config_keys = read_only_config_keys log.debug('read_only_config_keys: %s', read_only_config_keys) # Get the core config self.config = ConfigManager('core.conf') self.config.save() # If there was an interface value from the command line, use it, but # store the one in the config so we can restore it on shutdown self.__old_interface = None if listen_interface: if deluge.common.is_ip(listen_interface): self.__old_interface = self.config['listen_interface'] self.config['listen_interface'] = listen_interface else: log.error('Invalid listen interface (must be IP Address): %s', listen_interface) # New release check information self.__new_release = None # Session status timer self.session_status = {} self.session_status_timer_interval = 0.5 self.session_status_timer = task.LoopingCall(self.session.post_session_stats) self.alertmanager.register_handler('session_stats_alert', self._on_alert_session_stats) self._session_rates = {(k_rate, k_bytes): 0 for k_rate, k_bytes in SESSION_RATES_MAPPING.items()} self.session_rates_timer_interval = 2 self.session_rates_timer = task.LoopingCall(self._update_session_rates) def start(self): """Starts the core""" self.session_status_timer.start(self.session_status_timer_interval) self.session_rates_timer.start(self.session_rates_timer_interval, now=False) def stop(self): log.debug('Core stopping...') if self.session_status_timer.running: self.session_status_timer.stop() if self.session_rates_timer.running: self.session_rates_timer.stop() # Save the libtorrent session state self._save_session_state() # We stored a copy of the old interface value if self.__old_interface: self.config['listen_interface'] = self.__old_interface # Make sure the config file has been saved self.config.save() def shutdown(self): pass def apply_session_setting(self, key, value): self.apply_session_settings({key: value}) def apply_session_settings(self, settings): """Apply libtorrent session settings. Args: settings (dict): A dict of lt session settings to apply. """ self.session.apply_settings(settings) def _save_session_state(self): """Saves the libtorrent session state""" filename = 'session.state' filepath = get_config_dir(filename) filepath_bak = filepath + '.bak' filepath_tmp = filepath + '.tmp' try: if os.path.isfile(filepath): log.debug('Creating backup of %s at: %s', filename, filepath_bak) shutil.copy2(filepath, filepath_bak) except IOError as ex: log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex) else: log.info('Saving the %s at: %s', filename, filepath) try: with open(filepath_tmp, 'wb') as _file: _file.write(lt.bencode(self.session.save_state())) _file.flush() os.fsync(_file.fileno()) shutil.move(filepath_tmp, filepath) except (IOError, EOFError) as ex: log.error('Unable to save %s: %s', filename, ex) if os.path.isfile(filepath_bak): log.info('Restoring backup of %s from: %s', filename, filepath_bak) shutil.move(filepath_bak, filepath) def _load_session_state(self): """Loads the libtorrent session state Returns: dict: A libtorrent sesion state, empty dict if unable to load it. """ filename = 'session.state' filepath = get_config_dir(filename) filepath_bak = filepath + '.bak' for _filepath in (filepath, filepath_bak): log.debug('Opening %s for load: %s', filename, _filepath) try: with open(_filepath, 'rb') as _file: state = lt.bdecode(_file.read()) except (IOError, EOFError, RuntimeError) as ex: log.warning('Unable to load %s: %s', _filepath, ex) else: log.info('Successfully loaded %s: %s', filename, _filepath) self.session.load_state(state) def _on_alert_session_stats(self, alert): """The handler for libtorrent session stats alert""" if not self.session_status: # Empty dict on startup so needs populated with session rate keys and default value. self.session_status.update({key: 0 for key in list(SESSION_RATES_MAPPING)}) self.session_status.update(alert.values) self._update_session_cache_hit_ratio() def _update_session_cache_hit_ratio(self): """Calculates the cache read/write hit ratios and updates session_status""" try: self.session_status['write_hit_ratio'] = ((self.session_status['disk.num_blocks_written'] - self.session_status['disk.num_write_ops']) / self.session_status['disk.num_blocks_written']) except ZeroDivisionError: self.session_status['write_hit_ratio'] = 0.0 try: self.session_status['read_hit_ratio'] = (self.session_status['disk.num_blocks_cache_hits'] / self.session_status['disk.num_blocks_read']) except ZeroDivisionError: self.session_status['read_hit_ratio'] = 0.0 def _update_session_rates(self): """Calculates status rates based on interval and value difference for session_status""" if not self.session_status: return for (rate_key, status_key), prev_bytes in list(self._session_rates.items()): new_bytes = self.session_status[status_key] byte_rate = (new_bytes - prev_bytes) / self.session_rates_timer_interval self.session_status[rate_key] = byte_rate # Store current value for next update. self._session_rates[(rate_key, status_key)] = new_bytes def get_new_release(self): log.debug('get_new_release') try: self.new_release = urlopen('http://download.deluge-torrent.org/version-2.0').read().strip() except URLError as ex: log.debug('Unable to get release info from website: %s', ex) return self.check_new_release() def check_new_release(self): if self.new_release: log.debug('new_release: %s', self.new_release) if deluge.common.VersionSplit(self.new_release) > deluge.common.VersionSplit(deluge.common.get_version()): component.get('EventManager').emit(NewVersionAvailableEvent(self.new_release)) return self.new_release return False def _add_torrent_file(self, filename, filedump, options, save_state=True): """Adds a torrent file to the session. Args: filename (str): The filename of the torrent. filedump (str): A base64 encoded string of torrent file contents. options (dict): The options to apply to the torrent upon adding. save_state (bool): If the state should be saved after adding the file. Returns: str: The torrent ID or None. """ try: filedump = base64.decodestring(filedump) except Exception as ex: log.error('There was an error decoding the filedump string: %s', ex) try: d = self.torrentmanager.add( filedump=filedump, options=options, filename=filename, save_state=save_state ) except RuntimeError as ex: log.error('There was an error adding the torrent file %s: %s', filename, ex) raise else: return d # Exported Methods @export def add_torrent_file(self, filename, filedump, options): """Adds a torrent file to the session. Args: filename (str): The filename of the torrent. filedump (str): A base64 encoded string of the torrent file contents. options (dict): The options to apply to the torrent upon adding. Returns: str: The torrent_id or None. """ return self._add_torrent_file(filename, filedump, options) @export def add_torrent_files(self, torrent_files): """Adds multiple torrent files to the session. Args: torrent_files (list of tuples): Torrent files as tuple of (filename, filedump, options). Returns: Deferred """ @defer.inlineCallbacks def add_torrents(): errors = [] last_index = len(torrent_files) - 1 for idx, torrent in enumerate(torrent_files): try: yield self._add_torrent_file(torrent[0], torrent[1], torrent[2], save_state=idx == last_index) except AddTorrentError as ex: log.warn('Error when adding torrent: %s', ex) errors.append(ex) defer.returnValue(errors) return task.deferLater(reactor, 0, add_torrents) @export def add_torrent_url(self, url, options, headers=None): """ Adds a torrent from a url. Deluge will attempt to fetch the torrent from url prior to adding it to the session. :param url: the url pointing to the torrent file :type url: string :param options: the options to apply to the torrent on add :type options: dict :param headers: any optional headers to send :type headers: dict :returns: a Deferred which returns the torrent_id as a str or None """ log.info('Attempting to add url %s', url) def on_download_success(filename): # We got the file, so add it to the session with open(filename, 'rb') as _file: data = _file.read() try: os.remove(filename) except OSError as ex: log.warning('Could not remove temp file: %s', ex) return self.add_torrent_file(filename, base64.encodestring(data), options) def on_download_fail(failure): # Log the error and pass the failure onto the client log.error('Failed to add torrent from url %s', url) return failure tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent') os.close(tmp_fd) d = download_file(url, tmp_file, headers=headers, force_filename=True) d.addCallbacks(on_download_success, on_download_fail) return d @export def add_torrent_magnet(self, uri, options): """ Adds a torrent from a magnet link. :param uri: the magnet link :type uri: string :param options: the options to apply to the torrent on add :type options: dict :returns: the torrent_id :rtype: string """ log.debug('Attempting to add by magnet uri: %s', uri) return self.torrentmanager.add(magnet=uri, options=options) @export def remove_torrent(self, torrent_id, remove_data): """Removes a single torrent from the session. Args: torrent_id (str): The torrent ID to remove. remove_data (bool): If True, also remove the downloaded data. Returns: bool: True if removed successfully. Raises: InvalidTorrentError: If the torrent ID does not exist in the session. """ log.debug('Removing torrent %s from the core.', torrent_id) return self.torrentmanager.remove(torrent_id, remove_data) @export def remove_torrents(self, torrent_ids, remove_data): """Remove multiple torrents from the session. Args: torrent_ids (list): The torrent IDs to remove. remove_data (bool): If True, also remove the downloaded data. Returns: list: An empty list if no errors occurred otherwise the list contains tuples of strings, a torrent ID and an error message. For example: [('<torrent_id>', 'Error removing torrent')] """ log.info('Removing %d torrents from core.', len(torrent_ids)) def do_remove_torrents(): errors = [] for torrent_id in torrent_ids: try: self.torrentmanager.remove(torrent_id, remove_data=remove_data, save_state=False) except InvalidTorrentError as ex: errors.append((torrent_id, str(ex))) # Save the session state self.torrentmanager.save_state() if errors: log.warn('Failed to remove %d of %d torrents.', len(errors), len(torrent_ids)) return errors return task.deferLater(reactor, 0, do_remove_torrents) @export def get_session_status(self, keys): """Gets the session status values for 'keys', these keys are taking from libtorrent's session status. See: http://www.rasterbar.com/products/libtorrent/manual.html#status :param keys: the keys for which we want values :type keys: list :returns: a dictionary of {key: value, ...} :rtype: dict """ if not self.session_status: return {key: 0 for key in keys} if not keys: return self.session_status status = {} for key in keys: if key in OLD_SESSION_STATUS_KEYS: new_key = OLD_SESSION_STATUS_KEYS[key] log.warning('Using deprecated session status key %s, please use %s', key, new_key) status[key] = self.session_status[new_key] else: try: status[key] = self.session_status[key] except KeyError: log.warning('Session status key does not exist: %s', key) return status @export def force_reannounce(self, torrent_ids): log.debug('Forcing reannouncment to: %s', torrent_ids) for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_reannounce() @export def pause_torrent(self, torrent_ids): log.debug('Pausing: %s', torrent_ids) for torrent_id in torrent_ids: if not self.torrentmanager[torrent_id].pause(): log.warning('Error pausing torrent %s', torrent_id) @export def connect_peer(self, torrent_id, ip, port): log.debug('adding peer %s to %s', ip, torrent_id) if not self.torrentmanager[torrent_id].connect_peer(ip, port): log.warning('Error adding peer %s:%s to %s', ip, port, torrent_id) @export def move_storage(self, torrent_ids, dest): log.debug('Moving storage %s to %s', torrent_ids, dest) for torrent_id in torrent_ids: if not self.torrentmanager[torrent_id].move_storage(dest): log.warning('Error moving torrent %s to %s', torrent_id, dest) @export def pause_session(self): """Pause all torrents in the session""" if not self.session.is_paused(): self.session.pause() component.get('EventManager').emit(SessionPausedEvent()) @export def resume_session(self): """Resume all torrents in the session""" if self.session.is_paused(): self.session.resume() for torrent_id in self.torrentmanager.torrents: self.torrentmanager[torrent_id].update_state() component.get('EventManager').emit(SessionResumedEvent()) @export def resume_torrent(self, torrent_ids): log.debug('Resuming: %s', torrent_ids) for torrent_id in torrent_ids: self.torrentmanager[torrent_id].resume() def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False, all_keys=False): try: status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update, all_keys=all_keys) except KeyError: import traceback traceback.print_exc() # Torrent was probaly removed meanwhile return {} # Ask the plugin manager to fill in the plugin keys if len(plugin_keys) > 0 or all_keys: status.update(self.pluginmanager.get_status(torrent_id, plugin_keys)) return status @export def get_torrent_status(self, torrent_id, keys, diff=False): torrent_keys, plugin_keys = self.torrentmanager.separate_keys(keys, [torrent_id]) return self.create_torrent_status(torrent_id, torrent_keys, plugin_keys, diff=diff, update=True, all_keys=not keys) @export def get_torrents_status(self, filter_dict, keys, diff=False): """ returns all torrents , optionally filtered by filter_dict. """ torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict) d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=diff) def add_plugin_fields(args): status_dict, plugin_keys = args # Ask the plugin manager to fill in the plugin keys if len(plugin_keys) > 0: for key in status_dict: status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys)) return status_dict d.addCallback(add_plugin_fields) return d @export def get_filter_tree(self, show_zero_hits=True, hide_cat=None): """ returns {field: [(value,count)] } for use in sidebar(s) """ return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat) @export def get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager return self.torrentmanager.get_torrent_list() @export def get_config(self): """Get all the preferences as a dictionary""" return self.config.config @export def get_config_value(self, key): """Get the config value for key""" return self.config.get(key) @export def get_config_values(self, keys): """Get the config values for the entered keys""" return dict((key, self.config.get(key)) for key in keys) @export def set_config(self, config): """Set the config with values from dictionary""" # Load all the values into the configuration for key in config: if self.read_only_config_keys and key in self.read_only_config_keys: continue self.config[key] = config[key] @export def get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() @export def get_proxy(self): """Returns the proxy settings Returns: dict: Contains proxy settings. Notes: Proxy type names: 0: None, 1: Socks4, 2: Socks5, 3: Socks5 w Auth, 4: HTTP, 5: HTTP w Auth, 6: I2P """ settings = self.session.get_settings() proxy_type = settings['proxy_type'] proxy_hostname = settings['i2p_hostname'] if proxy_type == 6 else settings['proxy_hostname'] proxy_port = settings['i2p_port'] if proxy_type == 6 else settings['proxy_port'] proxy_dict = { 'type': proxy_type, 'hostname': proxy_hostname, 'username': settings['proxy_username'], 'password': settings['proxy_password'], 'port': proxy_port, 'proxy_hostnames': settings['proxy_hostnames'], 'proxy_peer_connections': settings['proxy_peer_connections'], 'proxy_tracker_connections': settings['proxy_tracker_connections'] } return proxy_dict @export def get_available_plugins(self): """Returns a list of plugins available in the core""" return self.pluginmanager.get_available_plugins() @export def get_enabled_plugins(self): """Returns a list of enabled plugins in the core""" return self.pluginmanager.get_enabled_plugins() @export def enable_plugin(self, plugin): return self.pluginmanager.enable_plugin(plugin) @export def disable_plugin(self, plugin): return self.pluginmanager.disable_plugin(plugin) @export def force_recheck(self, torrent_ids): """Forces a data recheck on torrent_ids""" for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_recheck() @export def set_torrent_options(self, torrent_ids, options): """Sets the torrent options for torrent_ids Args: torrent_ids (list): A list of torrent_ids to set the options for. options (dict): A dict of torrent options to set. See torrent.TorrentOptions class for valid keys. """ if 'owner' in options and not self.core.authmanager.has_account(options['owner']): raise DelugeError('Username "%s" is not known.' % options['owner']) if isinstance(torrent_ids, str if not PY2 else basestring): torrent_ids = [torrent_ids] for torrent_id in torrent_ids: self.torrentmanager[torrent_id].set_options(options) @export def set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" return self.torrentmanager[torrent_id].set_trackers(trackers) @deprecated @export def set_torrent_max_connections(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_connections'""" self.set_torrent_options([torrent_id], {'max_connections': value}) @deprecated @export def set_torrent_max_upload_slots(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_upload_slots'""" self.set_torrent_options([torrent_id], {'max_upload_slots': value}) @deprecated @export def set_torrent_max_upload_speed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_upload_speed'""" self.set_torrent_options([torrent_id], {'max_upload_speed': value}) @deprecated @export def set_torrent_max_download_speed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'max_download_speed'""" self.set_torrent_options([torrent_id], {'max_download_speed': value}) @deprecated @export def set_torrent_file_priorities(self, torrent_id, priorities): """Deprecated: Use set_torrent_options with 'file_priorities'""" self.set_torrent_options([torrent_id], {'file_priorities': priorities}) @deprecated @export def set_torrent_prioritize_first_last(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'prioritize_first_last'""" self.set_torrent_options([torrent_id], {'prioritize_first_last_pieces': value}) @deprecated @export def set_torrent_auto_managed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'auto_managed'""" self.set_torrent_options([torrent_id], {'auto_managed': value}) @deprecated @export def set_torrent_stop_at_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'stop_at_ratio'""" self.set_torrent_options([torrent_id], {'stop_at_ratio': value}) @deprecated @export def set_torrent_stop_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'stop_ratio'""" self.set_torrent_options([torrent_id], {'stop_ratio': value}) @deprecated @export def set_torrent_remove_at_ratio(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'remove_at_ratio'""" self.set_torrent_options([torrent_id], {'remove_at_ratio': value}) @deprecated @export def set_torrent_move_completed(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'move_completed'""" self.set_torrent_options([torrent_id], {'move_completed': value}) @deprecated @export def set_torrent_move_completed_path(self, torrent_id, value): """Deprecated: Use set_torrent_options with 'move_completed_path'""" self.set_torrent_options([torrent_id], {'move_completed_path': value}) @export def get_path_size(self, path): """Returns the size of the file or folder 'path' and -1 if the path is unaccessible (non-existent or insufficient privs)""" return deluge.common.get_path_size(path) @export def create_torrent(self, path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session): log.debug('creating torrent..') threading.Thread(target=self._create_torrent_thread, args=( path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session)).start() def _create_torrent_thread(self, path, tracker, piece_length, comment, target, webseeds, private, created_by, trackers, add_to_session): from deluge import metafile metafile.make_meta_file( path, tracker, piece_length, comment=comment, target=target, webseeds=webseeds, private=private, created_by=created_by, trackers=trackers) log.debug('torrent created!') if add_to_session: options = {} options['download_location'] = os.path.split(path)[0] with open(target, 'rb') as _file: filedump = base64.encodestring(_file.read()) self.add_torrent_file(os.path.split(target)[1], filedump, options) @export def upload_plugin(self, filename, filedump): """This method is used to upload new plugins to the daemon. It is used when connecting to the daemon remotely and installing a new plugin on the client side. 'plugin_data' is a xmlrpc.Binary object of the file data, ie, plugin_file.read()""" try: filedump = base64.decodestring(filedump) except Exception as ex: log.error('There was an error decoding the filedump string!') log.exception(ex) return with open(os.path.join(get_config_dir(), 'plugins', filename), 'wb') as _file: _file.write(filedump) component.get('CorePluginManager').scan_for_plugins() @export def rescan_plugins(self): """ Rescans the plugin folders for new plugins """ component.get('CorePluginManager').scan_for_plugins() @export def rename_files(self, torrent_id, filenames): """ Rename files in torrent_id. Since this is an asynchronous operation by libtorrent, watch for the TorrentFileRenamedEvent to know when the files have been renamed. :param torrent_id: the torrent_id to rename files :type torrent_id: string :param filenames: a list of index, filename pairs :type filenames: ((index, filename), ...) :raises InvalidTorrentError: if torrent_id is invalid """ if torrent_id not in self.torrentmanager.torrents: raise InvalidTorrentError('torrent_id is not in session') def rename(): self.torrentmanager[torrent_id].rename_files(filenames) return task.deferLater(reactor, 0, rename) @export def rename_folder(self, torrent_id, folder, new_folder): """ Renames the 'folder' to 'new_folder' in 'torrent_id'. Watch for the TorrentFolderRenamedEvent which is emitted when the folder has been renamed successfully. :param torrent_id: the torrent to rename folder in :type torrent_id: string :param folder: the folder to rename :type folder: string :param new_folder: the new folder name :type new_folder: string :raises InvalidTorrentError: if the torrent_id is invalid """ if torrent_id not in self.torrentmanager.torrents: raise InvalidTorrentError('torrent_id is not in session') return self.torrentmanager[torrent_id].rename_folder(folder, new_folder) @export def queue_top(self, torrent_ids): log.debug('Attempting to queue %s to top', torrent_ids) # torrent_ids must be sorted in reverse before moving to preserve order for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position, reverse=True): try: # If the queue method returns True, then we should emit a signal if self.torrentmanager.queue_top(torrent_id): component.get('EventManager').emit(TorrentQueueChangedEvent()) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) @export def queue_up(self, torrent_ids): log.debug('Attempting to queue %s to up', torrent_ids) torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids) torrent_moved = True prev_queue_position = None # torrent_ids must be sorted before moving. for queue_position, torrent_id in sorted(torrents): # Move the torrent if and only if there is space (by not moving it we preserve the order) if torrent_moved or queue_position - prev_queue_position > 1: try: torrent_moved = self.torrentmanager.queue_up(torrent_id) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) # If the torrent moved, then we should emit a signal if torrent_moved: component.get('EventManager').emit(TorrentQueueChangedEvent()) else: prev_queue_position = queue_position @export def queue_down(self, torrent_ids): log.debug('Attempting to queue %s to down', torrent_ids) torrents = ((self.torrentmanager.get_queue_position(torrent_id), torrent_id) for torrent_id in torrent_ids) torrent_moved = True prev_queue_position = None # torrent_ids must be sorted before moving. for queue_position, torrent_id in sorted(torrents, reverse=True): # Move the torrent if and only if there is space (by not moving it we preserve the order) if torrent_moved or prev_queue_position - queue_position > 1: try: torrent_moved = self.torrentmanager.queue_down(torrent_id) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) # If the torrent moved, then we should emit a signal if torrent_moved: component.get('EventManager').emit(TorrentQueueChangedEvent()) else: prev_queue_position = queue_position @export def queue_bottom(self, torrent_ids): log.debug('Attempting to queue %s to bottom', torrent_ids) # torrent_ids must be sorted before moving to preserve order for torrent_id in sorted(torrent_ids, key=self.torrentmanager.get_queue_position): try: # If the queue method returns True, then we should emit a signal if self.torrentmanager.queue_bottom(torrent_id): component.get('EventManager').emit(TorrentQueueChangedEvent()) except KeyError: log.warning('torrent_id: %s does not exist in the queue', torrent_id) @export def glob(self, path): return glob.glob(path) @export def test_listen_port(self): """ Checks if the active port is open :returns: True if the port is open, False if not :rtype: bool """ d = getPage(b'http://deluge-torrent.org/test_port.php?port=%s' % self.get_listen_port(), timeout=30) def on_get_page(result): return bool(int(result)) def on_error(failure): log.warning('Error testing listen port: %s', failure) d.addCallback(on_get_page) d.addErrback(on_error) return d @export def get_free_space(self, path=None): """ Returns the number of free bytes at path :param path: the path to check free space at, if None, use the default download location :type path: string :returns: the number of free bytes at path :rtype: int :raises InvalidPathError: if the path is invalid """ if not path: path = self.config['download_location'] try: return deluge.common.free_space(path) except InvalidPathError: return -1 def _on_external_ip_event(self, external_ip): self.external_ip = external_ip @export def get_external_ip(self): """ Returns the external ip address recieved from libtorrent. """ return self.external_ip @export def get_libtorrent_version(self): """ Returns the libtorrent version. :returns: the version :rtype: string """ return lt.__version__ @export def get_completion_paths(self, args): """ Returns the available path completions for the input value. """ return path_chooser_common.get_completion_paths(args) @export(AUTH_LEVEL_ADMIN) def get_known_accounts(self): return self.authmanager.get_known_accounts() @export(AUTH_LEVEL_NONE) def get_auth_levels_mappings(self): return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE) @export(AUTH_LEVEL_ADMIN) def create_account(self, username, password, authlevel): return self.authmanager.create_account(username, password, authlevel) @export(AUTH_LEVEL_ADMIN) def update_account(self, username, password, authlevel): return self.authmanager.update_account(username, password, authlevel) @export(AUTH_LEVEL_ADMIN) def remove_account(self, username): return self.authmanager.remove_account(username)