# # Copyright (C) 2008-2010 Andrew Resch # # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. # import logging import os import platform import random import threading from urllib.parse import quote_plus from urllib.request import urlopen from twisted.internet.task import LoopingCall import deluge.common import deluge.component as component import deluge.configmanager from deluge._libtorrent import lt from deluge.event import ConfigValueChangedEvent GeoIP = None try: from GeoIP import GeoIP except ImportError: try: from pygeoip import GeoIP except ImportError: pass log = logging.getLogger(__name__) DEFAULT_PREFS = { 'send_info': False, 'info_sent': 0.0, 'daemon_port': 58846, 'allow_remote': False, 'pre_allocate_storage': False, 'download_location': deluge.common.get_default_download_dir(), 'listen_ports': [6881, 6891], 'listen_interface': '', 'outgoing_interface': '', 'random_port': True, 'listen_random_port': None, 'listen_use_sys_port': False, 'listen_reuse_port': True, 'outgoing_ports': [0, 0], 'random_outgoing_ports': True, 'copy_torrent_file': False, 'del_copy_torrent_file': False, 'torrentfiles_location': deluge.common.get_default_download_dir(), 'plugins_location': os.path.join(deluge.configmanager.get_config_dir(), 'plugins'), 'prioritize_first_last_pieces': False, 'sequential_download': False, 'dht': True, 'upnp': True, 'natpmp': True, 'utpex': True, 'lsd': True, 'enc_in_policy': 1, 'enc_out_policy': 1, 'enc_level': 2, 'max_connections_global': 200, 'max_upload_speed': -1.0, 'max_download_speed': -1.0, 'max_upload_slots_global': 4, 'max_half_open_connections': ( lambda: deluge.common.windows_check() and (lambda: deluge.common.vista_check() and 4 or 8)() or 50 )(), 'max_connections_per_second': 20, 'ignore_limits_on_local_network': True, 'max_connections_per_torrent': -1, 'max_upload_slots_per_torrent': -1, 'max_upload_speed_per_torrent': -1, 'max_download_speed_per_torrent': -1, 'enabled_plugins': [], 'add_paused': False, 'max_active_seeding': 5, 'max_active_downloading': 3, 'max_active_limit': 8, 'dont_count_slow_torrents': False, 'queue_new_to_top': False, 'stop_seed_at_ratio': False, 'remove_seed_at_ratio': False, 'stop_seed_ratio': 2.00, 'share_ratio_limit': 2.00, 'seed_time_ratio_limit': 7.00, 'seed_time_limit': 180, 'auto_managed': True, 'move_completed': False, 'move_completed_path': deluge.common.get_default_download_dir(), 'move_completed_paths_list': [], 'download_location_paths_list': [], 'path_chooser_show_chooser_button_on_localhost': True, 'path_chooser_auto_complete_enabled': True, 'path_chooser_accelerator_string': 'Tab', 'path_chooser_max_popup_rows': 20, 'path_chooser_show_hidden_files': False, 'new_release_check': True, 'proxy': { 'type': 0, 'hostname': '', 'username': '', 'password': '', 'port': 8080, 'proxy_hostnames': True, 'proxy_peer_connections': True, 'proxy_tracker_connections': True, 'force_proxy': False, 'anonymous_mode': False, }, 'peer_tos': '0x00', 'rate_limit_ip_overhead': True, 'geoip_db_location': '/usr/share/GeoIP/GeoIP.dat', 'cache_size': 512, 'cache_expiry': 60, 'auto_manage_prefer_seeds': False, 'shared': False, 'super_seeding': False, } class PreferencesManager(component.Component): def __init__(self): component.Component.__init__(self, 'PreferencesManager') self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS) if 'proxies' in self.config: log.warning( 'Updating config file for proxy, using "peer" values to fill new "proxy" setting' ) self.config['proxy'].update(self.config['proxies']['peer']) log.warning('New proxy config is: %s', self.config['proxy']) del self.config['proxies'] if 'i2p_proxy' in self.config and self.config['i2p_proxy']['hostname']: self.config['proxy'].update(self.config['i2p_proxy']) self.config['proxy']['type'] = 6 del self.config['i2p_proxy'] if 'anonymous_mode' in self.config: self.config['proxy']['anonymous_mode'] = self.config['anonymous_mode'] del self.config['anonymous_mode'] if 'proxy' in self.config: for key in DEFAULT_PREFS['proxy']: if key not in self.config['proxy']: self.config['proxy'][key] = DEFAULT_PREFS['proxy'][key] self.core = component.get('Core') self.new_release_timer = None def start(self): # Set the initial preferences on start-up for key in DEFAULT_PREFS: self.do_config_set_func(key, self.config[key]) self.config.register_change_callback(self._on_config_value_change) def stop(self): if self.new_release_timer and self.new_release_timer.running: self.new_release_timer.stop() # Config set functions def do_config_set_func(self, key, value): on_set_func = getattr(self, '_on_set_' + key, None) if on_set_func: if log.isEnabledFor(logging.DEBUG): log.debug('Config key: %s set to %s..', key, value) on_set_func(key, value) def _on_config_value_change(self, key, value): if self.get_state() == 'Started': self.do_config_set_func(key, value) component.get('EventManager').emit(ConfigValueChangedEvent(key, value)) def _on_set_torrentfiles_location(self, key, value): if self.config['copy_torrent_file']: try: os.makedirs(value) except OSError as ex: log.debug('Unable to make directory: %s', ex) def _on_set_listen_ports(self, key, value): self.__set_listen_on() def _on_set_listen_interface(self, key, value): self.__set_listen_on() def _on_set_outgoing_interface(self, key, value): """Set interface name or IP address for outgoing BitTorrent connections.""" value = value.strip() if value else '' self.core.apply_session_settings({'outgoing_interfaces': value}) def _on_set_random_port(self, key, value): self.__set_listen_on() def __set_listen_on(self): """Set the ports and interface address to listen for incoming connections on.""" if self.config['random_port']: if not self.config['listen_random_port']: self.config['listen_random_port'] = random.randrange(49152, 65525) listen_ports = [ self.config['listen_random_port'] ] * 2 # use single port range else: self.config['listen_random_port'] = None listen_ports = self.config['listen_ports'] if self.config['listen_interface']: interface = self.config['listen_interface'].strip() else: interface = '0.0.0.0' log.debug( 'Listen Interface: %s, Ports: %s with use_sys_port: %s', interface, listen_ports, self.config['listen_use_sys_port'], ) interfaces = [ f'{interface}:{port}' for port in range(listen_ports[0], listen_ports[1] + 1) ] self.core.apply_session_settings( { 'listen_system_port_fallback': self.config['listen_use_sys_port'], 'listen_interfaces': ','.join(interfaces), } ) def _on_set_outgoing_ports(self, key, value): self.__set_outgoing_ports() def _on_set_random_outgoing_ports(self, key, value): self.__set_outgoing_ports() def __set_outgoing_ports(self): port = ( 0 if self.config['random_outgoing_ports'] else self.config['outgoing_ports'][0] ) if port: num_ports = ( self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0] ) num_ports = num_ports if num_ports > 1 else 5 else: num_ports = 0 log.debug('Outgoing port set to %s with range: %s', port, num_ports) self.core.apply_session_settings( {'outgoing_port': port, 'num_outgoing_ports': num_ports} ) def _on_set_peer_tos(self, key, value): try: self.core.apply_session_setting('peer_tos', int(value, 16)) except ValueError as ex: log.error('Invalid tos byte: %s', ex) def _on_set_dht(self, key, value): lt_bootstraps = self.core.session.get_settings()['dht_bootstrap_nodes'] # Update list of lt bootstraps, using set to remove duplicates. dht_bootstraps = set( lt_bootstraps.split(',') + [ 'router.bittorrent.com:6881', 'router.utorrent.com:6881', 'router.bitcomet.com:6881', 'dht.transmissionbt.com:6881', 'dht.aelitis.com:6881', ] ) self.core.apply_session_settings( {'dht_bootstrap_nodes': ','.join(dht_bootstraps), 'enable_dht': value} ) def _on_set_upnp(self, key, value): self.core.apply_session_setting('enable_upnp', value) def _on_set_natpmp(self, key, value): self.core.apply_session_setting('enable_natpmp', value) def _on_set_lsd(self, key, value): self.core.apply_session_setting('enable_lsd', value) def _on_set_utpex(self, key, value): if value: self.core.session.add_extension('ut_pex') def _on_set_enc_in_policy(self, key, value): self._on_set_encryption(key, value) def _on_set_enc_out_policy(self, key, value): self._on_set_encryption(key, value) def _on_set_enc_level(self, key, value): self._on_set_encryption(key, value) def _on_set_encryption(self, key, value): # Convert Deluge enc_level values to libtorrent enc_level values. pe_enc_level = { 0: lt.enc_level.plaintext, 1: lt.enc_level.rc4, 2: lt.enc_level.both, } self.core.apply_session_settings( { 'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']), 'in_enc_policy': lt.enc_policy(self.config['enc_in_policy']), 'allowed_enc_level': lt.enc_level( pe_enc_level[self.config['enc_level']] ), 'prefer_rc4': True, } ) def _on_set_max_connections_global(self, key, value): self.core.apply_session_setting('connections_limit', value) def _on_set_max_upload_speed(self, key, value): # We need to convert Kb/s to B/s value = -1 if value < 0 else int(value * 1024) self.core.apply_session_setting('upload_rate_limit', value) def _on_set_max_download_speed(self, key, value): # We need to convert Kb/s to B/s value = -1 if value < 0 else int(value * 1024) self.core.apply_session_setting('download_rate_limit', value) def _on_set_max_upload_slots_global(self, key, value): self.core.apply_session_setting('unchoke_slots_limit', value) def _on_set_max_half_open_connections(self, key, value): self.core.apply_session_setting('half_open_limit', value) def _on_set_max_connections_per_second(self, key, value): self.core.apply_session_setting('connection_speed', value) def _on_set_ignore_limits_on_local_network(self, key, value): self.core.apply_session_setting('ignore_limits_on_local_network', value) def _on_set_share_ratio_limit(self, key, value): # This value is a float percentage in deluge, but libtorrent needs int percentage. self.core.apply_session_setting('share_ratio_limit', int(value * 100)) def _on_set_seed_time_ratio_limit(self, key, value): # This value is a float percentage in deluge, but libtorrent needs int percentage. self.core.apply_session_setting('seed_time_ratio_limit', int(value * 100)) def _on_set_seed_time_limit(self, key, value): # This value is stored in minutes in deluge, but libtorrent wants seconds self.core.apply_session_setting('seed_time_limit', int(value * 60)) def _on_set_max_active_downloading(self, key, value): self.core.apply_session_setting('active_downloads', value) def _on_set_max_active_seeding(self, key, value): self.core.apply_session_setting('active_seeds', value) def _on_set_max_active_limit(self, key, value): self.core.apply_session_setting('active_limit', value) def _on_set_dont_count_slow_torrents(self, key, value): self.core.apply_session_setting('dont_count_slow_torrents', value) def _on_set_send_info(self, key, value): """sends anonymous stats home""" log.debug('Sending anonymous stats..') class SendInfoThread(threading.Thread): def __init__(self, config): self.config = config threading.Thread.__init__(self) def run(self): import time now = time.time() # check if we've done this within the last week or never if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7): try: url = ( 'http://deluge-torrent.org/stats_get.php?processor=' + platform.machine() + '&python=' + platform.python_version() + '&deluge=' + deluge.common.get_version() + '&os=' + platform.system() + '&plugins=' + quote_plus(':'.join(self.config['enabled_plugins'])) ) urlopen(url) except OSError as ex: log.debug('Network error while trying to send info: %s', ex) else: self.config['info_sent'] = now if value: SendInfoThread(self.config).start() def _on_set_new_release_check(self, key, value): if value: log.debug('Checking for new release..') threading.Thread(target=self.core.get_new_release).start() if self.new_release_timer and self.new_release_timer.running: self.new_release_timer.stop() # Set a timer to check for a new release every 3 days self.new_release_timer = LoopingCall( self._on_set_new_release_check, 'new_release_check', True ) self.new_release_timer.start(72 * 60 * 60, False) else: if self.new_release_timer and self.new_release_timer.running: self.new_release_timer.stop() def _on_set_proxy(self, key, value): # Initialise with type none and blank hostnames. proxy_settings = { 'proxy_type': lt.proxy_type_t.none, 'i2p_hostname': '', 'proxy_hostname': '', 'proxy_hostnames': value['proxy_hostnames'], 'proxy_peer_connections': value['proxy_peer_connections'], 'proxy_tracker_connections': value['proxy_tracker_connections'], 'force_proxy': value['force_proxy'], 'anonymous_mode': value['anonymous_mode'], } if value['type'] == lt.proxy_type_t.i2p_proxy: proxy_settings.update( { 'proxy_type': lt.proxy_type_t.i2p_proxy, 'i2p_hostname': value['hostname'], 'i2p_port': value['port'], } ) elif value['type'] != lt.proxy_type_t.none: proxy_settings.update( { 'proxy_type': value['type'], 'proxy_hostname': value['hostname'], 'proxy_port': value['port'], 'proxy_username': value['username'], 'proxy_password': value['password'], } ) self.core.apply_session_settings(proxy_settings) def _on_set_rate_limit_ip_overhead(self, key, value): self.core.apply_session_setting('rate_limit_ip_overhead', value) def _on_set_geoip_db_location(self, key, geoipdb_path): # Load the GeoIP DB for country look-ups if available if os.path.exists(geoipdb_path): try: self.core.geoip_instance = GeoIP(geoipdb_path, 0) except Exception as ex: log.warning('GeoIP Unavailable: %s', ex) else: log.warning('Unable to find GeoIP database file: %s', geoipdb_path) def _on_set_cache_size(self, key, value): self.core.apply_session_setting('cache_size', value) def _on_set_cache_expiry(self, key, value): self.core.apply_session_setting('cache_expiry', value) def _on_auto_manage_prefer_seeds(self, key, value): self.core.apply_session_setting('auto_manage_prefer_seeds', value)