diff --git a/deluge/tests/common_web.py b/deluge/tests/common_web.py index 7ba5f9f42..1ca5fb68d 100644 --- a/deluge/tests/common_web.py +++ b/deluge/tests/common_web.py @@ -66,9 +66,9 @@ class WebServerTestBase(BaseTestCase, DaemonBase): self.deluge_web = DelugeWeb(daemon=False) - host = list(self.deluge_web.web_api.host_list['hosts'][0]) + host = list(self.deluge_web.web_api.hostlist.get_hosts_info2()[0]) host[2] = self.listen_port - self.deluge_web.web_api.host_list['hosts'][0] = tuple(host) + self.deluge_web.web_api.hostlist.config['hosts'][0] = tuple(host) self.host_id = host[0] self.deluge_web.start() diff --git a/deluge/tests/test_authmanager.py b/deluge/tests/test_authmanager.py index d8b502a89..2d25d0f33 100644 --- a/deluge/tests/test_authmanager.py +++ b/deluge/tests/test_authmanager.py @@ -9,6 +9,7 @@ from __future__ import unicode_literals import deluge.component as component from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AuthManager +from deluge.ui import hostlist from .basetest import BaseTestCase @@ -23,8 +24,7 @@ class AuthManagerTestCase(BaseTestCase): return component.shutdown() def test_authorize(self): - from deluge.ui import common self.assertEqual( - self.auth.authorize(*common.get_localhost_auth()), + self.auth.authorize(*hostlist.get_localhost_auth()), AUTH_LEVEL_ADMIN ) diff --git a/deluge/tests/test_client.py b/deluge/tests/test_client.py index 839f46fd1..84d7fd9f6 100644 --- a/deluge/tests/test_client.py +++ b/deluge/tests/test_client.py @@ -10,10 +10,11 @@ from __future__ import unicode_literals from twisted.internet import defer import deluge.component as component -import deluge.ui.common from deluge import error +from deluge.common import AUTH_LEVEL_NORMAL from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.ui.client import Client, DaemonSSLProxy, client +from deluge.ui.hostlist import get_localhost_auth from .basetest import BaseTestCase from .daemon_base import DaemonBase @@ -98,7 +99,7 @@ class ClientTestCase(BaseTestCase, DaemonBase): return d def test_connect_localclient(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() d = client.connect('localhost', self.listen_port, username=username, password=password) def on_connect(result): @@ -110,7 +111,7 @@ class ClientTestCase(BaseTestCase, DaemonBase): return d def test_connect_bad_password(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() d = client.connect('localhost', self.listen_port, username=username, password=password + '1') def on_failure(failure): @@ -125,7 +126,7 @@ class ClientTestCase(BaseTestCase, DaemonBase): return d def test_connect_invalid_user(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() d = client.connect('localhost', self.listen_port, username='invalid-user') def on_failure(failure): @@ -140,7 +141,7 @@ class ClientTestCase(BaseTestCase, DaemonBase): return d def test_connect_without_password(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() d = client.connect('localhost', self.listen_port, username=username) def on_failure(failure): @@ -156,12 +157,12 @@ class ClientTestCase(BaseTestCase, DaemonBase): @defer.inlineCallbacks def test_connect_with_password(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() yield client.connect('localhost', self.listen_port, username=username, password=password) yield client.core.create_account('testuser', 'testpw', 'DEFAULT') yield client.disconnect() ret = yield client.connect('localhost', self.listen_port, username='testuser', password='testpw') - self.assertEqual(ret, deluge.common.AUTH_LEVEL_NORMAL) + self.assertEqual(ret, AUTH_LEVEL_NORMAL) yield @defer.inlineCallbacks @@ -176,7 +177,7 @@ class ClientTestCase(BaseTestCase, DaemonBase): yield d def test_connect_without_sending_client_version_fails(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() no_version_sending_client = NoVersionSendingClient() d = no_version_sending_client.connect( 'localhost', self.listen_port, username=username, password=password diff --git a/deluge/tests/test_rpcserver.py b/deluge/tests/test_rpcserver.py index ab8239bc1..dc74f850e 100644 --- a/deluge/tests/test_rpcserver.py +++ b/deluge/tests/test_rpcserver.py @@ -15,7 +15,7 @@ from deluge.core import rpcserver from deluge.core.authmanager import AuthManager from deluge.core.rpcserver import DelugeRPCProtocol, RPCServer from deluge.log import setup_logger -from deluge.ui.common import get_localhost_auth +from deluge.ui.hostlist import get_localhost_auth from .basetest import BaseTestCase diff --git a/deluge/tests/test_ui_entry.py b/deluge/tests/test_ui_entry.py index 977b84dcc..272a523e9 100644 --- a/deluge/tests/test_ui_entry.py +++ b/deluge/tests/test_ui_entry.py @@ -25,6 +25,7 @@ import deluge.ui.console.main import deluge.ui.web.server from deluge.common import utf8_encode_structure from deluge.ui import ui_entry +from deluge.ui.hostlist import get_localhost_auth from deluge.ui.web.server import DelugeWeb from . import common @@ -338,7 +339,7 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase): @defer.inlineCallbacks def test_console_command_status(self): - username, password = deluge.ui.common.get_localhost_auth() + username, password = get_localhost_auth() self.patch(sys, 'argv', self.var['sys_arg_cmd'] + ['--port'] + ['58900'] + ['--username'] + [username] + ['--password'] + [password] + ['status']) fd = StringFileDescriptor(sys.stdout) diff --git a/deluge/tests/test_web_api.py b/deluge/tests/test_web_api.py index bc1330f90..6fb7d1008 100644 --- a/deluge/tests/test_web_api.py +++ b/deluge/tests/test_web_api.py @@ -91,11 +91,11 @@ class WebAPITestCase(WebServerTestBase): def test_get_host(self): self.assertFalse(self.deluge_web.web_api._get_host('invalid_id')) - conn = self.deluge_web.web_api.host_list['hosts'][0] + conn = list(self.deluge_web.web_api.hostlist.get_hosts_info2()[0]) self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn) def test_add_host(self): - conn = [None, '', 0, '', ''] + conn = ['abcdef', '10.0.0.1', 0, 'user123', 'pass123'] self.assertFalse(self.deluge_web.web_api._get_host(conn[0])) # Add valid host ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) @@ -105,16 +105,16 @@ class WebAPITestCase(WebServerTestBase): # Add already existing host ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) - self.assertEqual(ret, (False, 'Host already in the list')) + self.assertEqual(ret, (False, 'Host details already in hostlist')) # Add invalid port conn[2] = 'bad port' ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) - self.assertEqual(ret, (False, 'Port is invalid')) + self.assertEqual(ret, (False, 'Invalid port. Must be an integer')) def test_remove_host(self): conn = ['connection_id', '', 0, '', ''] - self.deluge_web.web_api.host_list['hosts'].append(conn) + self.deluge_web.web_api.hostlist.config['hosts'].append(conn) self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn) # Remove valid host self.assertTrue(self.deluge_web.web_api.remove_host(conn[0])) diff --git a/deluge/ui/client.py b/deluge/ui/client.py index a494de576..564bf625d 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -21,7 +21,7 @@ import deluge.common from deluge import error from deluge.decorators import deprecated from deluge.transfer import DelugeTransferProtocol -from deluge.ui.common import get_localhost_auth +from deluge.ui.hostlist import get_localhost_auth RPC_RESPONSE = 1 RPC_ERROR = 2 diff --git a/deluge/ui/common.py b/deluge/ui/common.py index ef7dda9af..c66e8eb27 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -15,10 +15,8 @@ from __future__ import unicode_literals import logging import os -import time from hashlib import sha1 as sha -import deluge.configmanager from deluge import bencode from deluge.common import decode_bytes @@ -163,12 +161,6 @@ FILE_PRIORITY = { del _ -DEFAULT_HOST = '127.0.0.1' -DEFAULT_PORT = 58846 -DEFAULT_HOSTS = { - 'hosts': [(sha(str(time.time()).encode('utf8')).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, '', '')] -} - # The keys from session statistics for cache status. DISK_CACHE_KEYS = [ 'disk.num_blocks_read', 'disk.num_blocks_written', 'disk.num_read_ops', 'disk.num_write_ops', @@ -533,36 +525,3 @@ class FileTree(object): lines.append(' ' * depth + path) self.walk(write) return '\n'.join(lines) - - -def get_localhost_auth(): - """ - Grabs the localclient auth line from the 'auth' file and creates a localhost uri - - :returns: with the username and password to login as - :rtype: tuple - """ - auth_file = deluge.configmanager.get_config_dir('auth') - if not os.path.exists(auth_file): - from deluge.common import create_localclient_account - create_localclient_account() - - with open(auth_file) as auth: - for line in auth: - line = line.strip() - if line.startswith('#') or not line: - # This is a comment or blank line - continue - - lsplit = line.split(':') - - if len(lsplit) == 2: - username, password = lsplit - elif len(lsplit) == 3: - username, password, level = lsplit - else: - log.error('Your auth file is malformed: Incorrect number of fields!') - continue - - if username == 'localclient': - return (username, password) diff --git a/deluge/ui/console/modes/connectionmanager.py b/deluge/ui/console/modes/connectionmanager.py index 359e8407b..705279f8b 100644 --- a/deluge/ui/console/modes/connectionmanager.py +++ b/deluge/ui/console/modes/connectionmanager.py @@ -9,17 +9,14 @@ from __future__ import unicode_literals -import hashlib import logging -import time import deluge.component as component -from deluge.configmanager import ConfigManager from deluge.decorators import overrides -from deluge.ui import common as uicommon from deluge.ui.client import Client, client from deluge.ui.console.modes.basemode import BaseMode from deluge.ui.console.widgets.popup import InputPopup, PopupsHandler, SelectablePopup +from deluge.ui.hostlist import HostList try: import curses @@ -35,7 +32,7 @@ class ConnectionManager(BaseMode, PopupsHandler): PopupsHandler.__init__(self) self.statuses = {} self.all_torrents = None - self.config = ConfigManager('hostlist.conf.1.2', uicommon.DEFAULT_HOSTS) + self.hostlist = HostList() self.update_hosts_status() BaseMode.__init__(self, stdscr, encoding=encoding) self.update_select_host_popup() @@ -47,18 +44,19 @@ class ConnectionManager(BaseMode, PopupsHandler): popup = SelectablePopup(self, _('Select Host'), self._host_selected, border_off_west=1, active_wrap=True) popup.add_header("{!white,black,bold!}'Q'=%s, 'a'=%s, 'D'=%s" % - (_('quit'), _('add new host'), _('delete host')), + (_('Quit'), _('Add New Host'), _('Delete Host')), space_below=True) self.push_popup(popup, clear=True) - for host in self.config['hosts']: - args = {'data': host[0], 'foreground': 'red'} + for host_entry in self.hostlist.get_host_info(): + host_id, hostname, port, user = host_entry + args = {'data': host_id, 'foreground': 'red'} state = 'Offline' - if host[0] in self.statuses: + if host_id in self.statuses: state = 'Online' - args.update({'data': self.statuses[host[0]], 'foreground': 'green'}) - host_str = '%s:%d [%s]' % (host[1], host[2], state) - self.popup.add_line(host[0], host_str, selectable=True, use_underline=True, **args) + args.update({'data': self.statuses[host_id], 'foreground': 'green'}) + host_str = '%s:%d [%s]' % (hostname, port, state) + self.popup.add_line(host_id, host_str, selectable=True, use_underline=True, **args) if selected_index is not None: self.popup.set_selection(selected_index) @@ -87,16 +85,13 @@ class ConnectionManager(BaseMode, PopupsHandler): del self.statuses[host_id] self.update_select_host_popup() - for host in self.config['hosts']: + for host_entry in self.hostlist.get_hosts_info2(): c = Client() - hadr = host[1] - port = host[2] - user = host[3] - password = host[4] - log.debug('connect: hadr=%s, port=%s, user=%s, password=%s', hadr, port, user, password) - d = c.connect(hadr, port, user, password) - d.addCallback(on_connect, c, host[0]) - d.addErrback(on_connect_failed, host[0]) + host_id, host, port, user, password = host_entry + log.debug('Connect: host=%s, port=%s, user=%s, pass=%s', host, port, user, password) + d = c.connect(host, port, user, password) + d.addCallback(on_connect, c, host_id) + d.addErrback(on_connect_failed, host_id) def _on_connected(self, result): d = component.get('ConsoleUI').start_console() @@ -112,57 +107,46 @@ class ConnectionManager(BaseMode, PopupsHandler): log.exception(result) def _host_selected(self, selected_host, *args, **kwargs): - if selected_host not in self.statuses: - return - for host in self.config['hosts']: - if host[0] == selected_host: - d = client.connect(host[1], host[2], host[3], host[4]) - d.addCallback(self._on_connected) - d.addErrback(self._on_connect_fail) - return - return False + if selected_host in self.statuses: + for host_entry in self.hostlist.get_hosts_info(): + if host_entry[0] == selected_host: + __, host, port, user, password = host_entry + d = client.connect(host, port, user, password) + d.addCallback(self._on_connected) + d.addErrback(self._on_connect_fail) def _do_add(self, result, **kwargs): if not result or kwargs.get('close', False): self.pop_popup() - return - hostname = result['hostname']['value'] - try: - port = int(result['port']['value']) - except ValueError: - self.report_message('Cannot add host', 'Invalid port. Must be an integer') - return - username = result['username']['value'] - password = result['password']['value'] - for host in self.config['hosts']: - if (host[1], host[2], host[3]) == (hostname, port, username): - self.report_message('Cannot add host', 'Host already in list') - return - newid = hashlib.sha1(str(time.time())).hexdigest() - self.config['hosts'].append((newid, hostname, port, username, password)) - self.config.save() - self.update_select_host_popup() + else: + self.add_host(result['hostname']['value'], result['port']['value'], + result['username']['value'], result['password']['value']) def add_popup(self): self.inlist = False - popup = InputPopup(self, 'Add Host (up & down arrows to navigate, esc to cancel)', + popup = InputPopup(self, _('Add Host (Up & Down arrows to navigate, Esc to cancel)'), border_off_north=1, border_off_east=1, close_cb=self._do_add) - popup.add_text_input('hostname', '%s:' % _('Hostname')) - popup.add_text_input('port', '%s:' % _('Port')) - popup.add_text_input('username', '%s:' % _('Username')) - popup.add_text_input('password', '%s:' % _('Password')) + popup.add_text_input('hostname', _('Hostname:')) + popup.add_text_input('port', _('Port:')) + popup.add_text_input('username', _('Username:')) + popup.add_text_input('password', _('Password:')) self.push_popup(popup, clear=True) self.refresh() - def delete_current_host(self): - idx, data = self.popup.current_selection() - log.debug('deleting host: %s', data) - for host in self.config['hosts']: - if host[0] == data: - self.config['hosts'].remove(host) - break - self.config.save() + def add_host(self, hostname, port, username, password): + try: + self.hostlist.add_host(hostname, port, username, password) + except ValueError as ex: + self.report_message(_('Error adding host'), '%s' % ex) + return + else: + self.update_select_host_popup() + + def delete_host(self, host_id): + log.debug('deleting host: %s', host_id) + self.hostlist.remove_host(host_id) + self.update_select_host_popup() @overrides(component.Component) def start(self): @@ -224,8 +208,8 @@ class ConnectionManager(BaseMode, PopupsHandler): reactor.stop() return if chr(c) == 'D' and self.inlist: - self.delete_current_host() - self.update_select_host_popup() + host_id = self.popup.current_selection()[1] + self.delete_host(host_id) return if chr(c) == 'a' and self.inlist: self.add_popup() diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 095816f30..fd420b510 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -9,10 +9,8 @@ from __future__ import unicode_literals -import hashlib import logging import os -import time from socket import gaierror, gethostbyname import gtk @@ -23,9 +21,9 @@ from deluge.common import resource_filename from deluge.configmanager import ConfigManager, get_config_dir from deluge.error import AuthenticationRequired, BadLoginError, IncompatibleClient from deluge.ui.client import Client, client -from deluge.ui.common import get_localhost_auth from deluge.ui.gtkui.common import get_clipboard_text, get_deluge_icon, get_logo from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog +from deluge.ui.hostlist import DEFAULT_PORT, HostList try: from urllib.parse import urlparse @@ -35,17 +33,15 @@ except ImportError: log = logging.getLogger(__name__) -DEFAULT_HOST = '127.0.0.1' -DEFAULT_PORT = 58846 - HOSTLIST_COL_ID = 0 HOSTLIST_COL_HOST = 1 HOSTLIST_COL_PORT = 2 -HOSTLIST_COL_STATUS = 3 -HOSTLIST_COL_USER = 4 -HOSTLIST_COL_PASS = 5 +HOSTLIST_COL_USER = 3 +HOSTLIST_COL_PASS = 4 +HOSTLIST_COL_STATUS = 5 HOSTLIST_COL_VERSION = 6 +LOCALHOST = ('127.0.0.1', 'localhost') HOSTLIST_PIXBUFS = [ # This is populated in ConnectionManager.show @@ -68,6 +64,7 @@ def cell_render_host(column, cell, model, row, data): def cell_render_status(column, cell, model, row, data): status = model[row][data] + status = status if status else 'Offline' pixbuf = None if status in HOSTLIST_STATUS: pixbuf = HOSTLIST_PIXBUFS[HOSTLIST_STATUS.index(status)] @@ -79,7 +76,7 @@ class ConnectionManager(component.Component): def __init__(self): component.Component.__init__(self, 'ConnectionManager') self.gtkui_config = ConfigManager('gtkui.conf') - self.config = self.__load_config() + self.running = False # Component overrides @@ -94,53 +91,27 @@ class ConnectionManager(component.Component): def shutdown(self): pass - def __load_config(self): - auth_file = get_config_dir('auth') - if not os.path.exists(auth_file): - from deluge.common import create_localclient_account - create_localclient_account() - - localclient_username, localclient_password = get_localhost_auth() - default_config = { - 'hosts': [( - hashlib.sha1(str(time.time())).hexdigest(), - DEFAULT_HOST, - DEFAULT_PORT, - localclient_username, - localclient_password - )] - } - config = ConfigManager('hostlist.conf.1.2', defaults=default_config, file_version=2) - config.run_converter((0, 1), 2, self.__migrate_config_1_to_2) - return config - # Public methods def show(self): """ Show the ConnectionManager dialog. """ - self.config = self.__load_config() # Get the gtk builder file for the connection manager self.builder = gtk.Builder() # The main dialog self.builder.add_from_file(resource_filename( - 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.ui') - )) + 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.ui'))) # The add host dialog self.builder.add_from_file(resource_filename( - 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.addhost.ui') - )) + 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.addhost.ui'))) # The ask password dialog self.builder.add_from_file(resource_filename( - 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.askpassword.ui') - )) + 'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.askpassword.ui'))) # Setup the ConnectionManager dialog self.connection_manager = self.builder.get_object('connection_manager') self.connection_manager.set_transient_for(component.get('MainWindow').window) - self.connection_manager.set_icon(get_deluge_icon()) - self.builder.get_object('image1').set_from_pixbuf(get_logo(32)) self.askpassword_dialog = self.builder.get_object('askpassword_dialog') @@ -148,6 +119,7 @@ class ConnectionManager(component.Component): self.askpassword_dialog.set_icon(get_deluge_icon()) self.askpassword_dialog_entry = self.builder.get_object('askpassword_dialog_entry') + self.hostlist_config = HostList() self.hostlist = self.builder.get_object('hostlist') # Create status pixbufs @@ -160,18 +132,19 @@ class ConnectionManager(component.Component): ) # Create the host list gtkliststore - # id-hash, hostname, port, status, username, password, version + # id-hash, hostname, port, username, password, status, version self.liststore = gtk.ListStore(str, str, int, str, str, str, str) # Setup host list treeview self.hostlist.set_model(self.liststore) render = gtk.CellRendererPixbuf() column = gtk.TreeViewColumn(_('Status'), render) - column.set_cell_data_func(render, cell_render_status, 3) + column.set_cell_data_func(render, cell_render_status, HOSTLIST_COL_STATUS) self.hostlist.append_column(column) render = gtk.CellRendererText() column = gtk.TreeViewColumn(_('Host'), render, text=HOSTLIST_COL_HOST) - column.set_cell_data_func(render, cell_render_host, (1, 2, 4)) + column.set_cell_data_func( + render, cell_render_host, (HOSTLIST_COL_HOST, HOSTLIST_COL_PORT, HOSTLIST_COL_USER)) column.set_expand(True) self.hostlist.append_column(column) render = gtk.CellRendererText() @@ -202,7 +175,6 @@ class ConnectionManager(component.Component): # Save the toggle options self.__save_options() - self.__save_hostlist() self.connection_manager.destroy() del self.builder @@ -210,42 +182,6 @@ class ConnectionManager(component.Component): del self.liststore del self.hostlist - def add_host(self, host, port, username='', password=''): - """ - Adds a host to the list. - - :param host: str, the hostname - :param port: int, the port - :param username: str, the username to login as - :param password: str, the password to login with - - """ - # Check to see if there is already an entry for this host and return - # if thats the case - for entry in self.liststore: - if [entry[HOSTLIST_COL_HOST], entry[HOSTLIST_COL_PORT], entry[HOSTLIST_COL_USER]] == [host, port, username]: - raise ValueError('Host already in list!') - - try: - gethostbyname(host) - except gaierror as ex: - raise ValueError("Host '%s': %s" % (host, ex.args[1])) - - # Host isn't in the list, so lets add it - row = self.liststore.append() - self.liststore[row][HOSTLIST_COL_ID] = hashlib.sha1(str(time.time())).hexdigest() - self.liststore[row][HOSTLIST_COL_HOST] = host - self.liststore[row][HOSTLIST_COL_PORT] = port - self.liststore[row][HOSTLIST_COL_USER] = username - self.liststore[row][HOSTLIST_COL_PASS] = password - self.liststore[row][HOSTLIST_COL_STATUS] = 'Offline' - - # Save the host list to file - self.__save_hostlist() - - # Update the status of the hosts - self.__update_list() - def on_entry_host_paste_clipboard(self, widget): text = get_clipboard_text() log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text) @@ -261,39 +197,21 @@ class ConnectionManager(component.Component): if parsed.password: self.builder.get_object('entry_password').set_text(parsed.password) - # Private methods - def __save_hostlist(self): - """ - Save the current hostlist to the config file. - """ - # Grab the hosts from the liststore - self.config['hosts'] = [] - for row in self.liststore: - self.config['hosts'].append((row[HOSTLIST_COL_ID], - row[HOSTLIST_COL_HOST], - row[HOSTLIST_COL_PORT], - row[HOSTLIST_COL_USER], - row[HOSTLIST_COL_PASS])) - - self.config.save() - def __load_hostlist(self): - """ - Load saved host entries - """ - for host in self.config['hosts']: - new_row = self.liststore.append() - self.liststore[new_row][HOSTLIST_COL_ID] = host[0] - self.liststore[new_row][HOSTLIST_COL_HOST] = host[1] - self.liststore[new_row][HOSTLIST_COL_PORT] = host[2] - self.liststore[new_row][HOSTLIST_COL_USER] = host[3] - self.liststore[new_row][HOSTLIST_COL_PASS] = host[4] - self.liststore[new_row][HOSTLIST_COL_STATUS] = 'Offline' - self.liststore[new_row][HOSTLIST_COL_VERSION] = '' + """Load saved host entries""" + status = version = '' + for host_entry in self.hostlist_config.get_hosts_info2(): + host_id, host, port, username, password = host_entry + self.liststore.append([host_id, host, port, username, password, status, version]) def __get_host_row(self, host_id): - """ - Returns the row in the liststore for `:param:host_id` or None + """Get the row in the liststore for the host_id. + + Args: + host_id (str): The host id. + + Returns: + list: The listsrore row with host details. """ for row in self.liststore: @@ -352,14 +270,11 @@ class ConnectionManager(component.Component): try: ip = gethostbyname(host) except gaierror as ex: - log.error('Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1]) + log.error('Error resolving host %s to ip: %s', host, ex.args[1]) continue - if client.connected() and ( - ip, - port, - 'localclient' if not user and host in ('127.0.0.1', 'localhost') else user - ) == client.connection_info(): + host_info = (ip, port, 'localclient' if not user and host in LOCALHOST else user) + if client.connected() and host_info == client.connection_info(): def on_info(info, row): if not self.running: return @@ -383,14 +298,11 @@ class ConnectionManager(component.Component): Set the widgets to show the correct options from the config. """ self.builder.get_object('chk_autoconnect').set_active( - self.gtkui_config['autoconnect'] - ) + self.gtkui_config['autoconnect']) self.builder.get_object('chk_autostart').set_active( - self.gtkui_config['autostart_localhost'] - ) + self.gtkui_config['autostart_localhost']) self.builder.get_object('chk_donotshow').set_active( - not self.gtkui_config['show_connection_manager_on_start'] - ) + not self.gtkui_config['show_connection_manager_on_start']) def __save_options(self): """ @@ -402,17 +314,15 @@ class ConnectionManager(component.Component): 'chk_donotshow').get_active() def __update_buttons(self): - """ - Updates the buttons states. - """ + """Updates the buttons states.""" if len(self.liststore) == 0: # There is nothing in the list - self.builder.get_object('button_startdaemon').set_sensitive(True) + self.builder.get_object('button_startdaemon').set_sensitive(False) self.builder.get_object('button_connect').set_sensitive(False) self.builder.get_object('button_removehost').set_sensitive(False) self.builder.get_object('image_startdaemon').set_from_stock( gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - self.builder.get_object('label_startdaemon').set_text('_Start Daemon') + self.builder.get_object('label_startdaemon').set_text_with_mnemonic('_Start Daemon') model, row = self.hostlist.get_selection().get_selected() if not row: @@ -420,25 +330,23 @@ class ConnectionManager(component.Component): return self.builder.get_object('button_edithost').set_sensitive(True) - - # Get some values about the selected host - status = model[row][HOSTLIST_COL_STATUS] - host = model[row][HOSTLIST_COL_HOST] - port = model[row][HOSTLIST_COL_PORT] - user = model[row][HOSTLIST_COL_USER] - passwd = model[row][HOSTLIST_COL_PASS] - - log.debug('Status: %s', status) - # Check to see if we have a localhost entry selected - localhost = False - if host in ('127.0.0.1', 'localhost'): - localhost = True - - # Make sure buttons are sensitive at start self.builder.get_object('button_startdaemon').set_sensitive(True) self.builder.get_object('button_connect').set_sensitive(True) self.builder.get_object('button_removehost').set_sensitive(True) + # Get some values about the selected host + __, host, port, user, password, status, __ = model[row] + + try: + ip = gethostbyname(host) + except gaierror as ex: + log.error('Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1]) + return + + log.debug('Status: %s', status) + # Check to see if we have a localhost entry selected + localhost = host in LOCALHOST + # See if this is the currently connected host if status == 'Connected': # Display a disconnect button if we're connected to this host @@ -453,30 +361,25 @@ class ConnectionManager(component.Component): if status == 'Connected' or status == 'Online': self.builder.get_object('image_startdaemon').set_from_stock( gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - self.builder.get_object('label_startdaemon').set_text( - _('_Stop Daemon')) + self.builder.get_object('label_startdaemon').set_text_with_mnemonic(_('_Stop Daemon')) # Update the start daemon button if the selected host is localhost if localhost and status == 'Offline': # The localhost is not online self.builder.get_object('image_startdaemon').set_from_stock( gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - self.builder.get_object('label_startdaemon').set_text( - _('_Start Daemon')) + self.builder.get_object('label_startdaemon').set_text_with_mnemonic(_('_Start Daemon')) - if client.connected() and (host, port, user) == client.connection_info(): + if client.connected() and (ip, port, user) == client.connection_info(): # If we're connected, we can stop the dameon self.builder.get_object('button_startdaemon').set_sensitive(True) - elif user and passwd: + elif user and password: # In this case we also have all the info to shutdown the daemon self.builder.get_object('button_startdaemon').set_sensitive(True) else: # Can't stop non localhost daemons, specially without the necessary info self.builder.get_object('button_startdaemon').set_sensitive(False) - # Make sure label is displayed correctly using mnemonics - self.builder.get_object('label_startdaemon').set_use_underline(True) - def start_daemon(self, port, config): """ Attempts to start a daemon process and will show an ErrorDialog if unable @@ -530,7 +433,7 @@ class ConnectionManager(component.Component): self.connection_manager.response(gtk.RESPONSE_OK) component.start() - def __on_connected_failed(self, reason, host_id, host, port, user, passwd, + def __on_connected_failed(self, reason, host_id, host, port, user, password, try_counter): log.debug('Failed to connect: %s', reason.value) @@ -552,9 +455,8 @@ class ConnectionManager(component.Component): if try_counter: log.info('Retrying connection.. Retries left: %s', try_counter) return reactor.callLater( - 0.5, self.__connect, host_id, host, port, user, passwd, - try_counter=try_counter - 1 - ) + 0.5, self.__connect, host_id, host, port, user, password, + try_counter=try_counter - 1) msg = str(reason.value) if not self.builder.get_object('chk_autostart').get_active(): @@ -566,28 +468,26 @@ class ConnectionManager(component.Component): model, row = self.hostlist.get_selection().get_selected() if not row: return + status = model[row][HOSTLIST_COL_STATUS] + + # If status is connected then connect button disconnects instead. if status == 'Connected': def on_disconnect(reason): self.__update_list() client.disconnect().addCallback(on_disconnect) return - host_id = model[row][HOSTLIST_COL_ID] - host = model[row][HOSTLIST_COL_HOST] - port = model[row][HOSTLIST_COL_PORT] - user = model[row][HOSTLIST_COL_USER] - password = model[row][HOSTLIST_COL_PASS] - - if (status == 'Offline' and self.builder.get_object('chk_autostart').get_active() and - host in ('127.0.0.1', 'localhost')): + host_id, host, port, user, password, __, __ = model[row] + try_counter = 0 + auto_start = self.builder.get_object('chk_autostart').get_active() + if status == 'Offline' and auto_start and host in LOCALHOST: if not self.start_daemon(port, get_config_dir()): log.debug('Failed to auto-start daemon') return - return self.__connect( - host_id, host, port, user, password, try_counter=6 - ) - return self.__connect(host_id, host, port, user, password) + try_counter = 6 + + return self.__connect(host_id, host, port, user, password, try_counter=try_counter) def on_button_close_clicked(self, widget): self.connection_manager.response(gtk.RESPONSE_CLOSE) @@ -610,25 +510,30 @@ class ConnectionManager(component.Component): username = username_entry.get_text() password = password_entry.get_text() hostname = hostname_entry.get_text() - - if (not password and not username or username == 'localclient') and hostname in ['127.0.0.1', 'localhost']: - username, password = get_localhost_auth() + port = port_spinbutton.get_value_as_int() try: - self.add_host(hostname, port_spinbutton.get_value_as_int(), username, password) + host_id = self.hostlist_config.add_host(hostname, port, username, password) except ValueError as ex: - ErrorDialog(_('Error Adding Host'), ex).run() + ErrorDialog(_('Error Adding Host'), ex, parent=dialog).run() + else: + self.liststore.append([host_id, hostname, port, username, password, 'Offline', '']) + + # Update the status of the hosts + self.__update_list() username_entry.set_text('') password_entry.set_text('') hostname_entry.set_text('') - port_spinbutton.set_value(58846) + port_spinbutton.set_value(DEFAULT_PORT) dialog.hide() def on_button_edithost_clicked(self, widget=None): log.debug('on_button_edithost_clicked') model, row = self.hostlist.get_selection().get_selected() status = model[row][HOSTLIST_COL_STATUS] + host_id = model[row][HOSTLIST_COL_ID] + if status == 'Connected': def on_disconnect(reason): self.__update_list() @@ -658,18 +563,14 @@ class ConnectionManager(component.Component): username = username_entry.get_text() password = password_entry.get_text() hostname = hostname_entry.get_text() + port = port_spinbutton.get_value_as_int() - if (not password and not username or username == 'localclient') and hostname in ['127.0.0.1', 'localhost']: - username, password = get_localhost_auth() - - self.liststore[row][HOSTLIST_COL_HOST] = hostname - self.liststore[row][HOSTLIST_COL_PORT] = port_spinbutton.get_value_as_int() - self.liststore[row][HOSTLIST_COL_USER] = username - self.liststore[row][HOSTLIST_COL_PASS] = password - self.liststore[row][HOSTLIST_COL_STATUS] = 'Offline' - - # Save the host list to file - self.__save_hostlist() + try: + self.hostlist_config.update_host(host_id, hostname, port, username, password) + except ValueError as ex: + ErrorDialog(_('Error Updating Host'), ex, parent=dialog).run() + else: + self.liststore[row] = host_id, hostname, port, username, password, '', '' # Update the status of the hosts self.__update_list() @@ -677,44 +578,38 @@ class ConnectionManager(component.Component): username_entry.set_text('') password_entry.set_text('') hostname_entry.set_text('') - port_spinbutton.set_value(58846) + port_spinbutton.set_value(DEFAULT_PORT) dialog.hide() def on_button_removehost_clicked(self, widget): log.debug('on_button_removehost_clicked') # Get the selected rows - paths = self.hostlist.get_selection().get_selected_rows()[1] - for path in paths: - self.liststore.remove(self.liststore.get_iter(path)) - + model, row = self.hostlist.get_selection().get_selected() + self.hostlist_config.remove_host(model[row][HOSTLIST_COL_ID]) + self.liststore.remove(row) # Update the hostlist self.__update_list() - # Save the host list - self.__save_hostlist() - def on_button_startdaemon_clicked(self, widget): log.debug('on_button_startdaemon_clicked') if self.liststore.iter_n_children(None) < 1: # There is nothing in the list, so lets create a localhost entry - self.add_host(DEFAULT_HOST, DEFAULT_PORT, *get_localhost_auth()) + try: + self.hostlist_config.add_default_host() + except ValueError as ex: + log.error('Error adding default host: %s', ex) + # ..and start the daemon. - self.start_daemon( - DEFAULT_PORT, get_config_dir() - ) + self.start_daemon(DEFAULT_PORT, get_config_dir()) return paths = self.hostlist.get_selection().get_selected_rows()[1] if len(paths) < 1: return - status = self.liststore[paths[0]][HOSTLIST_COL_STATUS] - host = self.liststore[paths[0]][HOSTLIST_COL_HOST] - port = self.liststore[paths[0]][HOSTLIST_COL_PORT] - user = self.liststore[paths[0]][HOSTLIST_COL_USER] - password = self.liststore[paths[0]][HOSTLIST_COL_PASS] + __, host, port, user, password, status, __ = self.liststore[paths[0]] - if host not in ('127.0.0.1', 'localhost'): + if host not in LOCALHOST: return if status in ('Online', 'Connected'): @@ -754,15 +649,3 @@ class ConnectionManager(component.Component): def on_askpassword_dialog_entry_activate(self, entry): self.askpassword_dialog.response(gtk.RESPONSE_OK) - - def __migrate_config_1_to_2(self, config): - 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 ('127.0.0.1', 'localhost'): - if not username: - config['hosts'][idx][3] = localclient_username - config['hosts'][idx][4] = localclient_password - return config diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py new file mode 100644 index 000000000..5eb43889e --- /dev/null +++ b/deluge/ui/hostlist.py @@ -0,0 +1,211 @@ +# -*- 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. +# + +""" +The UI hostlist module contains methods useful for adding, removing and lookingup host in hostlist.conf. +""" +from __future__ import unicode_literals + +import logging +import os +import time +from hashlib import sha1 +from socket import gaierror, gethostbyname + +from deluge.config import Config +from deluge.configmanager import get_config_dir + +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 for hostlist with a localhost entry""" + host_id = sha1(str(time.time()).encode('utf8')).hexdigest() + username, password = get_localhost_auth() + return {'hosts': [(host_id, DEFAULT_HOST, DEFAULT_PORT, username, password)]} + + +def get_localhost_auth(): + """Grabs the localclient auth line from the 'auth' file and creates a localhost uri. + + Returns: + tuple: With the username and password to login as. + + """ + auth_file = get_config_dir('auth') + if not os.path.exists(auth_file): + from deluge.common import create_localclient_account + create_localclient_account() + + with open(auth_file) as auth: + for line in auth: + line = line.strip() + if line.startswith('#') or not line: + # This is a comment or blank line + continue + + lsplit = line.split(':') + + if len(lsplit) == 2: + username, password = lsplit + elif len(lsplit) == 3: + username, password, level = lsplit + else: + log.error('Your auth file is malformed: Incorrect number of fields!') + continue + + if username == 'localclient': + return (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]) + + try: + int(port) + except ValueError: + raise ValueError('Invalid port. Must be an integer') + + +def _migrate_config_1_to_2(config): + 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): + def __init__(self): + self.config = Config('hostlist.conf', default_hostlist(), config_dir=get_config_dir(), file_version=2) + self.config.run_converter((0, 1), 2, _migrate_config_1_to_2) + 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 = sha1(str(time.time())).hexdigest() + 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. + + Includes password details! + + """ + for host_entry in self.config['hosts']: + if host_entry[0] == host_id: + return host_entry + else: + return [] + + def get_hosts_info(self): + """Get all the hosts in the hostlist + + Excluding password details. + """ + return [host[0:4 + 1] for host in self.config['hosts']] + + def get_hosts_info2(self): + """Get all the hosts in the hostlist + + Excluding password details. + """ + return [host for host in self.config['hosts']] + + def update_host(self, host_id, hostname, port, username, password): + """Update the host with new 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 host_entry in self.config['hosts']: + if host_id == host_entry[0]: + host_entry = host_id, hostname, port, username, password + return True + return False + + def remove_host(self, host_id): + 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()) diff --git a/deluge/ui/web/auth.py b/deluge/ui/web/auth.py index d80327f57..0a2ad0ac3 100644 --- a/deluge/ui/web/auth.py +++ b/deluge/ui/web/auth.py @@ -18,17 +18,11 @@ from email.utils import formatdate from twisted.internet.task import LoopingCall +from deluge.common import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE + log = logging.getLogger(__name__) -AUTH_LEVEL_NONE = 0 -AUTH_LEVEL_READONLY = 1 -AUTH_LEVEL_NORMAL = 5 -AUTH_LEVEL_ADMIN = 10 - -AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL - - class AuthError(Exception): """ An exception that might be raised when checking a request for @@ -36,7 +30,7 @@ class AuthError(Exception): """ pass -# Import after as json_api imports the above AuthError and AUTH_LEVEL_DEFAULT +# Import after as json_api imports the above AuthError from deluge.ui.web.json_api import export, JSONComponent # NOQA, isort:skip pylint: disable=wrong-import-position diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index 72052f7da..62ccd7709 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -10,13 +10,11 @@ from __future__ import division, unicode_literals import base64 -import hashlib import json import logging import os import shutil import tempfile -import time from types import FunctionType from twisted.internet import defer, reactor @@ -24,10 +22,11 @@ from twisted.internet.defer import Deferred, DeferredList from twisted.web import http, resource, server from deluge import common, component, httpdownloader -from deluge.configmanager import ConfigManager, get_config_dir -from deluge.ui import common as uicommon +from deluge.configmanager import get_config_dir from deluge.ui.client import Client, client +from deluge.ui.common import FileTree2, TorrentInfo from deluge.ui.coreconfig import CoreConfig +from deluge.ui.hostlist import HostList from deluge.ui.sessionproxy import SessionProxy from deluge.ui.translations_util import get_languages from deluge.ui.web.common import _, compress @@ -58,7 +57,8 @@ def export(auth_level=AUTH_LEVEL_DEFAULT): """ global AUTH_LEVEL_DEFAULT, AuthError if AUTH_LEVEL_DEFAULT is None: - from deluge.ui.web.auth import AUTH_LEVEL_DEFAULT, AuthError # NOQA pylint: disable=redefined-outer-name + from deluge.common import AUTH_LEVEL_DEFAULT + from deluge.ui.web.auth import AuthError # NOQA pylint: disable=redefined-outer-name def wrap(func, *args, **kwargs): func._json_export = True @@ -262,19 +262,6 @@ class JSON(resource.Resource, component.Component): self._local_methods[name + '.' + d] = getattr(obj, d) -HOSTLIST_ID = 0 -HOSTLIST_NAME = 1 -HOSTLIST_PORT = 2 -HOSTLIST_USER = 3 -HOSTLIST_PASS = 4 - -HOSTS_ID = HOSTLIST_ID -HOSTS_NAME = HOSTLIST_NAME -HOSTS_PORT = HOSTLIST_PORT -HOSTS_USER = HOSTLIST_USER -HOSTS_STATUS = 3 -HOSTS_INFO = 4 - FILES_KEYS = ['files', 'file_progress', 'file_priorities'] @@ -370,9 +357,7 @@ class WebApi(JSONComponent): def __init__(self): super(WebApi, self).__init__('Web', depend=['SessionProxy']) - self.host_list = ConfigManager('hostlist.conf.1.2', uicommon.DEFAULT_HOSTS) - if not os.path.isfile(self.host_list.config_file): - self.host_list.save() + self.hostlist = HostList() self.core_config = CoreConfig() self.event_queue = EventQueue() try: @@ -410,23 +395,6 @@ class WebApi(JSONComponent): component.get('Web.PluginManager').stop() return self.stop() - def _get_host(self, host_id): - """Information about a host from supplied host id. - - Args: - host_id (str): The id of the host. - - Returns: - list: The host information, empty list if not found. - - """ - host_info = [] - for host_entry in self.host_list['hosts']: - if host_entry[0] == host_id: - host_info = host_entry - break - return host_info - def start(self): self.core_config.start() return self.sessionproxy.start() @@ -611,7 +579,7 @@ class WebApi(JSONComponent): item.update(info[path]) return item - file_tree = uicommon.FileTree2(paths) + file_tree = FileTree2(paths) file_tree.walk(walk) d.callback(file_tree.get_tree()) @@ -685,7 +653,7 @@ class WebApi(JSONComponent): :rtype: dictionary """ try: - torrent_info = uicommon.TorrentInfo(filename.strip(), 2) + torrent_info = TorrentInfo(filename.strip(), 2) return torrent_info.as_dict('name', 'info_hash', 'files_tree') except Exception as ex: log.error(ex) @@ -730,13 +698,25 @@ class WebApi(JSONComponent): deferreds.append(d) return DeferredList(deferreds, consumeErrors=False) + def _get_host(self, host_id): + """Information about a host from supplied host id. + + Args: + host_id (str): The id of the host. + + Returns: + list: The host information, empty list if not found. + + """ + return list(self.hostlist.get_host_info(host_id)) + @export def get_hosts(self): """ Return the hosts in the hostlist. """ log.debug('get_hosts called') - return [(tuple(host[HOSTS_ID:HOSTS_USER + 1]) + ('Offline',)) for host in self.host_list['hosts']] + return self.hostlist.get_hosts() + [''] @export def get_host_status(self, host_id): @@ -786,6 +766,39 @@ class WebApi(JSONComponent): d.addCallback(on_connect, c, host_id).addErrback(on_connect_failed, host_id) return d + @export + def add_host(self, host, port, username='', password=''): + """Adds a host to the list. + + Args: + host (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: + tuple: A tuple of (bool, str). If True will contain the host_id, otherwise + if False will contain the error message. + """ + try: + host_id = self.hostlist.add_host(host, port, username, password) + except ValueError as ex: + return False, str(ex) + else: + return True, host_id + + @export + def remove_host(self, host_id): + """Removes a host from the list. + + Args: + host_id (str): The host identifying hash. + + Returns: + bool: True if succesful, False otherwise. + """ + return self.hostlist.remove_host(host_id) + @export def start_daemon(self, port): """ @@ -827,55 +840,6 @@ class WebApi(JSONComponent): main_deferred.callback((False, 'An error occurred')) return main_deferred - @export - def add_host(self, host, port, username='', password=''): - """ - Adds a host to the list. - - :param host: the hostname - :type host: string - :param port: the port - :type port: int - :param username: the username to login as - :type username: string - :param password: the password to login with - :type password: string - - """ - # Check to see if there is already an entry for this host and return - # if thats the case - for entry in self.host_list['hosts']: - if (entry[1], entry[2], entry[3]) == (host, port, username): - return (False, 'Host already in the list') - - try: - port = int(port) - except ValueError: - return (False, 'Port is invalid') - - # Host isn't in the list, so lets add it - connection_id = hashlib.sha1(str(time.time())).hexdigest() - self.host_list['hosts'].append([connection_id, host, port, username, - password]) - self.host_list.save() - return True, connection_id - - @export - def remove_host(self, connection_id): - """ - Removes a host for the list - - :param host_id: the hash id of the host - :type host_id: string - """ - host = self._get_host(connection_id) - if not host: - return False - - self.host_list['hosts'].remove(host) - self.host_list.save() - return True - @export def get_config(self): """