From d1772d410235592b482e3b08b1863f6624d9fe6b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 19 Feb 2023 15:52:21 +0100 Subject: Adding upstream version 2.0.3. Signed-off-by: Daniel Baumann --- deluge/ui/hostlist.py | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 deluge/ui/hostlist.py (limited to 'deluge/ui/hostlist.py') diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py new file mode 100644 index 0000000..ee4c7df --- /dev/null +++ b/deluge/ui/hostlist.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) Calum Lind 2017 +# +# 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. +# + +from __future__ import unicode_literals + +import logging +import os +import uuid +from socket import gaierror, gethostbyname + +from twisted.internet import defer + +from deluge.common import get_localhost_auth +from deluge.config import Config +from deluge.configmanager import get_config_dir +from deluge.ui.client import Client, client + +log = logging.getLogger(__name__) + +DEFAULT_HOST = '127.0.0.1' +DEFAULT_PORT = 58846 +LOCALHOST = ('127.0.0.1', 'localhost') + + +def default_hostlist(): + """Create a new hosts key for hostlist with a localhost entry""" + host_id = uuid.uuid4().hex + username, password = get_localhost_auth() + return {'hosts': [(host_id, DEFAULT_HOST, DEFAULT_PORT, username, password)]} + + +def validate_host_info(hostname, port): + """Checks that hostname and port are valid. + + Args: + hostname (str): The IP or hostname of the deluge daemon. + port (int): The port of the deluge daemon. + + Raises: + ValueError: Host details are not valid with reason. + """ + + try: + gethostbyname(hostname) + except gaierror as ex: + raise ValueError('Host %s: %s', hostname, ex.args[1]) + + if not isinstance(port, int): + raise ValueError('Invalid port. Must be an integer') + + +def migrate_hostlist(old_filename, new_filename): + """Check for old hostlist filename and save details to new filename""" + old_hostlist = get_config_dir(old_filename) + if os.path.isfile(old_hostlist): + config_v2 = Config(old_filename, config_dir=get_config_dir()) + config_v2.save(get_config_dir(new_filename)) + del config_v2 + + try: + os.rename(old_hostlist, old_hostlist + '.old') + except OSError as ex: + log.exception(ex) + + try: + os.remove(old_hostlist + '.bak') + except OSError: + pass + + +def migrate_config_2_to_3(config): + """Mirgrates old hostlist config files to new file version""" + localclient_username, localclient_password = get_localhost_auth() + if not localclient_username: + # Nothing to do here, there's no auth file + return + for idx, (__, host, __, username, __) in enumerate(config['hosts'][:]): + if host in LOCALHOST and not username: + config['hosts'][idx][3] = localclient_username + config['hosts'][idx][4] = localclient_password + return config + + +class HostList(object): + """This class contains methods for adding, removing and looking up hosts in hostlist.conf.""" + + def __init__(self): + migrate_hostlist('hostlist.conf.1.2', 'hostlist.conf') + self.config = Config( + 'hostlist.conf', + default_hostlist(), + config_dir=get_config_dir(), + file_version=3, + ) + self.config.run_converter((1, 2), 3, migrate_config_2_to_3) + self.config.save() + + def check_info_exists(self, hostname, port, username, skip_host_id=None): + """Check for exising host entries with the same details. + + Args: + hostname (str): The IP or hostname of the deluge daemon. + port (int): The port of the deluge daemon. + username (str): The username to login to the daemon with. + skip_host_id (str): A host_id to skip to check if other hosts match details. + + Raises: + ValueError: Host details already exist. + + """ + for host_entry in self.config['hosts']: + if (hostname, port, username) == ( + host_entry[1], + host_entry[2], + host_entry[3], + ): + if skip_host_id is not None and skip_host_id == host_entry[0]: + continue + raise ValueError('Host details already in hostlist') + + def add_host(self, hostname, port, username, password): + """Add a new host to hostlist. + + Args: + hostname (str): The IP or hostname of the deluge daemon. + port (int): The port of the deluge daemon. + username (str): The username to login to the daemon with. + password (str): The password to login to the daemon with. + + Returns: + str: The new host id. + """ + if ( + not password and not username or username == 'localclient' + ) and hostname in LOCALHOST: + username, password = get_localhost_auth() + + validate_host_info(hostname, port) + self.check_info_exists(hostname, port, username) + host_id = uuid.uuid4().hex + self.config['hosts'].append((host_id, hostname, port, username, password)) + self.config.save() + return host_id + + def get_host_info(self, host_id): + """Get the host details for host_id. + + Args: + host_id (str): The host id to get info on. + + Returns: + list: A list of (host_id, hostname, port, username). + + """ + for host_entry in self.config['hosts']: + if host_entry[0] == host_id: + return host_entry[0:4] + else: + return [] + + def get_hosts_info(self): + """Get information of all the hosts in the hostlist. + + Returns: + list of lists: Host information in the format [(host_id, hostname, port, username)]. + + """ + return [host_entry[0:4] for host_entry in self.config['hosts']] + + def get_host_status(self, host_id): + """Gets the current status (online/offline) of the host + + Args: + host_id (str): The host id to check status of. + + Returns: + tuple: A tuple of strings (host_id, status, version). + + """ + status_offline = (host_id, 'Offline', '') + + def on_connect(result, c, host_id): + """Successfully connected to a daemon""" + + def on_info(info, c): + c.disconnect() + return host_id, 'Online', info + + def on_info_fail(reason, c): + c.disconnect() + return status_offline + + return c.daemon.info().addCallback(on_info, c).addErrback(on_info_fail, c) + + def on_connect_failed(reason, host_id): + """Connection to daemon failed""" + log.debug('Host status failed for %s: %s', host_id, reason) + return status_offline + + try: + host_id, host, port, user = self.get_host_info(host_id) + except ValueError: + log.warning('Problem getting host_id info from hostlist') + return status_offline + + try: + ip = gethostbyname(host) + except gaierror as ex: + log.error('Error resolving host %s to ip: %s', host, ex.args[1]) + return status_offline + + host_conn_info = ( + ip, + port, + 'localclient' if not user and host in LOCALHOST else user, + ) + if client.connected() and host_conn_info == client.connection_info(): + # Currently connected to host_id daemon. + def on_info(info, host_id): + log.debug('Client connected, query info: %s', info) + return host_id, 'Connected', info + + return client.daemon.info().addCallback(on_info, host_id) + else: + # Attempt to connect to daemon with host_id details. + c = Client() + d = c.connect(host, port, skip_authentication=True) + d.addCallback(on_connect, c, host_id) + d.addErrback(on_connect_failed, host_id) + return d + + def update_host(self, host_id, hostname, port, username, password): + """Update the supplied host id with new connection details. + + Args: + host_id (str): The host id to update. + hostname (str): The new IP or hostname of the deluge daemon. + port (int): The new port of the deluge daemon. + username (str): The new username to login to the daemon with. + password (str): The new password to login to the daemon with. + + """ + validate_host_info(hostname, port) + self.check_info_exists(hostname, port, username, skip_host_id=host_id) + + if ( + not password and not username or username == 'localclient' + ) and hostname in LOCALHOST: + username, password = get_localhost_auth() + + for idx, host_entry in enumerate(self.config['hosts']): + if host_id == host_entry[0]: + self.config['hosts'][idx] = host_id, hostname, port, username, password + self.config.save() + return True + return False + + def remove_host(self, host_id): + """Removes the host entry from hostlist config. + + Args: + host_id (str): The host id to remove. + + Returns: + bool: True is successfully removed, False otherwise. + + """ + for host_entry in self.config['hosts']: + if host_id == host_entry[0]: + self.config['hosts'].remove(host_entry) + self.config.save() + return True + else: + return False + + def add_default_host(self): + self.add_host(DEFAULT_HOST, DEFAULT_PORT, *get_localhost_auth()) + + def connect_host(self, host_id): + """Connect to host daemon""" + for host_entry in self.config['hosts']: + if host_entry[0] == host_id: + __, host, port, username, password = host_entry + return client.connect(host, port, username, password) + + return defer.fail(Exception('Bad host id')) -- cgit v1.2.3