[UI] Refactor duplicated code out of connection managers

This commit is contained in:
Calum Lind 2017-03-30 00:36:44 +01:00
parent 54a081bdfd
commit ac48ad982e
13 changed files with 432 additions and 435 deletions

View File

@ -66,9 +66,9 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
self.deluge_web = DelugeWeb(daemon=False) 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 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.host_id = host[0]
self.deluge_web.start() self.deluge_web.start()

View File

@ -9,6 +9,7 @@ from __future__ import unicode_literals
import deluge.component as component import deluge.component as component
from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AuthManager from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AuthManager
from deluge.ui import hostlist
from .basetest import BaseTestCase from .basetest import BaseTestCase
@ -23,8 +24,7 @@ class AuthManagerTestCase(BaseTestCase):
return component.shutdown() return component.shutdown()
def test_authorize(self): def test_authorize(self):
from deluge.ui import common
self.assertEqual( self.assertEqual(
self.auth.authorize(*common.get_localhost_auth()), self.auth.authorize(*hostlist.get_localhost_auth()),
AUTH_LEVEL_ADMIN AUTH_LEVEL_ADMIN
) )

View File

@ -10,10 +10,11 @@ from __future__ import unicode_literals
from twisted.internet import defer from twisted.internet import defer
import deluge.component as component import deluge.component as component
import deluge.ui.common
from deluge import error from deluge import error
from deluge.common import AUTH_LEVEL_NORMAL
from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.ui.client import Client, DaemonSSLProxy, client from deluge.ui.client import Client, DaemonSSLProxy, client
from deluge.ui.hostlist import get_localhost_auth
from .basetest import BaseTestCase from .basetest import BaseTestCase
from .daemon_base import DaemonBase from .daemon_base import DaemonBase
@ -98,7 +99,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
return d return d
def test_connect_localclient(self): 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) d = client.connect('localhost', self.listen_port, username=username, password=password)
def on_connect(result): def on_connect(result):
@ -110,7 +111,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
return d return d
def test_connect_bad_password(self): 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') d = client.connect('localhost', self.listen_port, username=username, password=password + '1')
def on_failure(failure): def on_failure(failure):
@ -125,7 +126,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
return d return d
def test_connect_invalid_user(self): 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') d = client.connect('localhost', self.listen_port, username='invalid-user')
def on_failure(failure): def on_failure(failure):
@ -140,7 +141,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
return d return d
def test_connect_without_password(self): 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) d = client.connect('localhost', self.listen_port, username=username)
def on_failure(failure): def on_failure(failure):
@ -156,12 +157,12 @@ class ClientTestCase(BaseTestCase, DaemonBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_connect_with_password(self): 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.connect('localhost', self.listen_port, username=username, password=password)
yield client.core.create_account('testuser', 'testpw', 'DEFAULT') yield client.core.create_account('testuser', 'testpw', 'DEFAULT')
yield client.disconnect() yield client.disconnect()
ret = yield client.connect('localhost', self.listen_port, username='testuser', password='testpw') 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 yield
@defer.inlineCallbacks @defer.inlineCallbacks
@ -176,7 +177,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
yield d yield d
def test_connect_without_sending_client_version_fails(self): 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() no_version_sending_client = NoVersionSendingClient()
d = no_version_sending_client.connect( d = no_version_sending_client.connect(
'localhost', self.listen_port, username=username, password=password 'localhost', self.listen_port, username=username, password=password

View File

@ -15,7 +15,7 @@ from deluge.core import rpcserver
from deluge.core.authmanager import AuthManager from deluge.core.authmanager import AuthManager
from deluge.core.rpcserver import DelugeRPCProtocol, RPCServer from deluge.core.rpcserver import DelugeRPCProtocol, RPCServer
from deluge.log import setup_logger 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 from .basetest import BaseTestCase

View File

@ -25,6 +25,7 @@ import deluge.ui.console.main
import deluge.ui.web.server import deluge.ui.web.server
from deluge.common import utf8_encode_structure from deluge.common import utf8_encode_structure
from deluge.ui import ui_entry from deluge.ui import ui_entry
from deluge.ui.hostlist import get_localhost_auth
from deluge.ui.web.server import DelugeWeb from deluge.ui.web.server import DelugeWeb
from . import common from . import common
@ -338,7 +339,7 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_console_command_status(self): 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'] + self.patch(sys, 'argv', self.var['sys_arg_cmd'] + ['--port'] + ['58900'] + ['--username'] +
[username] + ['--password'] + [password] + ['status']) [username] + ['--password'] + [password] + ['status'])
fd = StringFileDescriptor(sys.stdout) fd = StringFileDescriptor(sys.stdout)

View File

@ -91,11 +91,11 @@ class WebAPITestCase(WebServerTestBase):
def test_get_host(self): def test_get_host(self):
self.assertFalse(self.deluge_web.web_api._get_host('invalid_id')) 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) self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn)
def test_add_host(self): 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])) self.assertFalse(self.deluge_web.web_api._get_host(conn[0]))
# Add valid host # Add valid host
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) 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 # Add already existing host
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) 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 # Add invalid port
conn[2] = 'bad port' conn[2] = 'bad port'
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4]) 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): def test_remove_host(self):
conn = ['connection_id', '', 0, '', ''] 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) self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn)
# Remove valid host # Remove valid host
self.assertTrue(self.deluge_web.web_api.remove_host(conn[0])) self.assertTrue(self.deluge_web.web_api.remove_host(conn[0]))

View File

@ -21,7 +21,7 @@ import deluge.common
from deluge import error from deluge import error
from deluge.decorators import deprecated from deluge.decorators import deprecated
from deluge.transfer import DelugeTransferProtocol 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_RESPONSE = 1
RPC_ERROR = 2 RPC_ERROR = 2

View File

@ -15,10 +15,8 @@ from __future__ import unicode_literals
import logging import logging
import os import os
import time
from hashlib import sha1 as sha from hashlib import sha1 as sha
import deluge.configmanager
from deluge import bencode from deluge import bencode
from deluge.common import decode_bytes from deluge.common import decode_bytes
@ -163,12 +161,6 @@ FILE_PRIORITY = {
del _ 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. # The keys from session statistics for cache status.
DISK_CACHE_KEYS = [ DISK_CACHE_KEYS = [
'disk.num_blocks_read', 'disk.num_blocks_written', 'disk.num_read_ops', 'disk.num_write_ops', '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) lines.append(' ' * depth + path)
self.walk(write) self.walk(write)
return '\n'.join(lines) 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)

View File

@ -9,17 +9,14 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import logging import logging
import time
import deluge.component as component import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.decorators import overrides from deluge.decorators import overrides
from deluge.ui import common as uicommon
from deluge.ui.client import Client, client from deluge.ui.client import Client, client
from deluge.ui.console.modes.basemode import BaseMode from deluge.ui.console.modes.basemode import BaseMode
from deluge.ui.console.widgets.popup import InputPopup, PopupsHandler, SelectablePopup from deluge.ui.console.widgets.popup import InputPopup, PopupsHandler, SelectablePopup
from deluge.ui.hostlist import HostList
try: try:
import curses import curses
@ -35,7 +32,7 @@ class ConnectionManager(BaseMode, PopupsHandler):
PopupsHandler.__init__(self) PopupsHandler.__init__(self)
self.statuses = {} self.statuses = {}
self.all_torrents = None self.all_torrents = None
self.config = ConfigManager('hostlist.conf.1.2', uicommon.DEFAULT_HOSTS) self.hostlist = HostList()
self.update_hosts_status() self.update_hosts_status()
BaseMode.__init__(self, stdscr, encoding=encoding) BaseMode.__init__(self, stdscr, encoding=encoding)
self.update_select_host_popup() 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 = 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" % 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) space_below=True)
self.push_popup(popup, clear=True) self.push_popup(popup, clear=True)
for host in self.config['hosts']: for host_entry in self.hostlist.get_host_info():
args = {'data': host[0], 'foreground': 'red'} host_id, hostname, port, user = host_entry
args = {'data': host_id, 'foreground': 'red'}
state = 'Offline' state = 'Offline'
if host[0] in self.statuses: if host_id in self.statuses:
state = 'Online' state = 'Online'
args.update({'data': self.statuses[host[0]], 'foreground': 'green'}) args.update({'data': self.statuses[host_id], 'foreground': 'green'})
host_str = '%s:%d [%s]' % (host[1], host[2], state) host_str = '%s:%d [%s]' % (hostname, port, state)
self.popup.add_line(host[0], host_str, selectable=True, use_underline=True, **args) self.popup.add_line(host_id, host_str, selectable=True, use_underline=True, **args)
if selected_index is not None: if selected_index is not None:
self.popup.set_selection(selected_index) self.popup.set_selection(selected_index)
@ -87,16 +85,13 @@ class ConnectionManager(BaseMode, PopupsHandler):
del self.statuses[host_id] del self.statuses[host_id]
self.update_select_host_popup() self.update_select_host_popup()
for host in self.config['hosts']: for host_entry in self.hostlist.get_hosts_info2():
c = Client() c = Client()
hadr = host[1] host_id, host, port, user, password = host_entry
port = host[2] log.debug('Connect: host=%s, port=%s, user=%s, pass=%s', host, port, user, password)
user = host[3] d = c.connect(host, port, user, password)
password = host[4] d.addCallback(on_connect, c, host_id)
log.debug('connect: hadr=%s, port=%s, user=%s, password=%s', hadr, port, user, password) d.addErrback(on_connect_failed, host_id)
d = c.connect(hadr, port, user, password)
d.addCallback(on_connect, c, host[0])
d.addErrback(on_connect_failed, host[0])
def _on_connected(self, result): def _on_connected(self, result):
d = component.get('ConsoleUI').start_console() d = component.get('ConsoleUI').start_console()
@ -112,57 +107,46 @@ class ConnectionManager(BaseMode, PopupsHandler):
log.exception(result) log.exception(result)
def _host_selected(self, selected_host, *args, **kwargs): def _host_selected(self, selected_host, *args, **kwargs):
if selected_host not in self.statuses: if selected_host in self.statuses:
return for host_entry in self.hostlist.get_hosts_info():
for host in self.config['hosts']: if host_entry[0] == selected_host:
if host[0] == selected_host: __, host, port, user, password = host_entry
d = client.connect(host[1], host[2], host[3], host[4]) d = client.connect(host, port, user, password)
d.addCallback(self._on_connected) d.addCallback(self._on_connected)
d.addErrback(self._on_connect_fail) d.addErrback(self._on_connect_fail)
return
return False
def _do_add(self, result, **kwargs): def _do_add(self, result, **kwargs):
if not result or kwargs.get('close', False): if not result or kwargs.get('close', False):
self.pop_popup() self.pop_popup()
return else:
hostname = result['hostname']['value'] self.add_host(result['hostname']['value'], result['port']['value'],
try: result['username']['value'], result['password']['value'])
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()
def add_popup(self): def add_popup(self):
self.inlist = False 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, border_off_north=1, border_off_east=1,
close_cb=self._do_add) close_cb=self._do_add)
popup.add_text_input('hostname', '%s:' % _('Hostname')) popup.add_text_input('hostname', _('Hostname:'))
popup.add_text_input('port', '%s:' % _('Port')) popup.add_text_input('port', _('Port:'))
popup.add_text_input('username', '%s:' % _('Username')) popup.add_text_input('username', _('Username:'))
popup.add_text_input('password', '%s:' % _('Password')) popup.add_text_input('password', _('Password:'))
self.push_popup(popup, clear=True) self.push_popup(popup, clear=True)
self.refresh() self.refresh()
def delete_current_host(self): def add_host(self, hostname, port, username, password):
idx, data = self.popup.current_selection() try:
log.debug('deleting host: %s', data) self.hostlist.add_host(hostname, port, username, password)
for host in self.config['hosts']: except ValueError as ex:
if host[0] == data: self.report_message(_('Error adding host'), '%s' % ex)
self.config['hosts'].remove(host) return
break else:
self.config.save() 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) @overrides(component.Component)
def start(self): def start(self):
@ -224,8 +208,8 @@ class ConnectionManager(BaseMode, PopupsHandler):
reactor.stop() reactor.stop()
return return
if chr(c) == 'D' and self.inlist: if chr(c) == 'D' and self.inlist:
self.delete_current_host() host_id = self.popup.current_selection()[1]
self.update_select_host_popup() self.delete_host(host_id)
return return
if chr(c) == 'a' and self.inlist: if chr(c) == 'a' and self.inlist:
self.add_popup() self.add_popup()

View File

@ -9,10 +9,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import logging import logging
import os import os
import time
from socket import gaierror, gethostbyname from socket import gaierror, gethostbyname
import gtk import gtk
@ -23,9 +21,9 @@ from deluge.common import resource_filename
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import AuthenticationRequired, BadLoginError, IncompatibleClient from deluge.error import AuthenticationRequired, BadLoginError, IncompatibleClient
from deluge.ui.client import Client, client 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.common import get_clipboard_text, get_deluge_icon, get_logo
from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog
from deluge.ui.hostlist import DEFAULT_PORT, HostList
try: try:
from urllib.parse import urlparse from urllib.parse import urlparse
@ -35,17 +33,15 @@ except ImportError:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 58846
HOSTLIST_COL_ID = 0 HOSTLIST_COL_ID = 0
HOSTLIST_COL_HOST = 1 HOSTLIST_COL_HOST = 1
HOSTLIST_COL_PORT = 2 HOSTLIST_COL_PORT = 2
HOSTLIST_COL_STATUS = 3 HOSTLIST_COL_USER = 3
HOSTLIST_COL_USER = 4 HOSTLIST_COL_PASS = 4
HOSTLIST_COL_PASS = 5 HOSTLIST_COL_STATUS = 5
HOSTLIST_COL_VERSION = 6 HOSTLIST_COL_VERSION = 6
LOCALHOST = ('127.0.0.1', 'localhost')
HOSTLIST_PIXBUFS = [ HOSTLIST_PIXBUFS = [
# This is populated in ConnectionManager.show # 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): def cell_render_status(column, cell, model, row, data):
status = model[row][data] status = model[row][data]
status = status if status else 'Offline'
pixbuf = None pixbuf = None
if status in HOSTLIST_STATUS: if status in HOSTLIST_STATUS:
pixbuf = HOSTLIST_PIXBUFS[HOSTLIST_STATUS.index(status)] pixbuf = HOSTLIST_PIXBUFS[HOSTLIST_STATUS.index(status)]
@ -79,7 +76,7 @@ class ConnectionManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, 'ConnectionManager') component.Component.__init__(self, 'ConnectionManager')
self.gtkui_config = ConfigManager('gtkui.conf') self.gtkui_config = ConfigManager('gtkui.conf')
self.config = self.__load_config()
self.running = False self.running = False
# Component overrides # Component overrides
@ -94,53 +91,27 @@ class ConnectionManager(component.Component):
def shutdown(self): def shutdown(self):
pass 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 # Public methods
def show(self): def show(self):
""" """
Show the ConnectionManager dialog. Show the ConnectionManager dialog.
""" """
self.config = self.__load_config()
# Get the gtk builder file for the connection manager # Get the gtk builder file for the connection manager
self.builder = gtk.Builder() self.builder = gtk.Builder()
# The main dialog # The main dialog
self.builder.add_from_file(resource_filename( 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 # The add host dialog
self.builder.add_from_file(resource_filename( 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 # The ask password dialog
self.builder.add_from_file(resource_filename( 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 # Setup the ConnectionManager dialog
self.connection_manager = self.builder.get_object('connection_manager') self.connection_manager = self.builder.get_object('connection_manager')
self.connection_manager.set_transient_for(component.get('MainWindow').window) self.connection_manager.set_transient_for(component.get('MainWindow').window)
self.connection_manager.set_icon(get_deluge_icon()) self.connection_manager.set_icon(get_deluge_icon())
self.builder.get_object('image1').set_from_pixbuf(get_logo(32)) self.builder.get_object('image1').set_from_pixbuf(get_logo(32))
self.askpassword_dialog = self.builder.get_object('askpassword_dialog') 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.set_icon(get_deluge_icon())
self.askpassword_dialog_entry = self.builder.get_object('askpassword_dialog_entry') self.askpassword_dialog_entry = self.builder.get_object('askpassword_dialog_entry')
self.hostlist_config = HostList()
self.hostlist = self.builder.get_object('hostlist') self.hostlist = self.builder.get_object('hostlist')
# Create status pixbufs # Create status pixbufs
@ -160,18 +132,19 @@ class ConnectionManager(component.Component):
) )
# Create the host list gtkliststore # 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) self.liststore = gtk.ListStore(str, str, int, str, str, str, str)
# Setup host list treeview # Setup host list treeview
self.hostlist.set_model(self.liststore) self.hostlist.set_model(self.liststore)
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column = gtk.TreeViewColumn(_('Status'), render) 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) self.hostlist.append_column(column)
render = gtk.CellRendererText() render = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Host'), render, text=HOSTLIST_COL_HOST) 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) column.set_expand(True)
self.hostlist.append_column(column) self.hostlist.append_column(column)
render = gtk.CellRendererText() render = gtk.CellRendererText()
@ -202,7 +175,6 @@ class ConnectionManager(component.Component):
# Save the toggle options # Save the toggle options
self.__save_options() self.__save_options()
self.__save_hostlist()
self.connection_manager.destroy() self.connection_manager.destroy()
del self.builder del self.builder
@ -210,42 +182,6 @@ class ConnectionManager(component.Component):
del self.liststore del self.liststore
del self.hostlist 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): def on_entry_host_paste_clipboard(self, widget):
text = get_clipboard_text() text = get_clipboard_text()
log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text) log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text)
@ -261,39 +197,21 @@ class ConnectionManager(component.Component):
if parsed.password: if parsed.password:
self.builder.get_object('entry_password').set_text(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): def __load_hostlist(self):
""" """Load saved host entries"""
Load saved host entries status = version = ''
""" for host_entry in self.hostlist_config.get_hosts_info2():
for host in self.config['hosts']: host_id, host, port, username, password = host_entry
new_row = self.liststore.append() self.liststore.append([host_id, host, port, username, password, status, version])
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] = ''
def __get_host_row(self, host_id): def __get_host_row(self, host_id):
""" """Get the row in the liststore for the host_id.
Returns the row in the liststore for `:param:host_id` or None
Args:
host_id (str): The host id.
Returns:
list: The listsrore row with host details.
""" """
for row in self.liststore: for row in self.liststore:
@ -352,14 +270,11 @@ class ConnectionManager(component.Component):
try: try:
ip = gethostbyname(host) ip = gethostbyname(host)
except gaierror as ex: 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 continue
if client.connected() and ( host_info = (ip, port, 'localclient' if not user and host in LOCALHOST else user)
ip, if client.connected() and host_info == client.connection_info():
port,
'localclient' if not user and host in ('127.0.0.1', 'localhost') else user
) == client.connection_info():
def on_info(info, row): def on_info(info, row):
if not self.running: if not self.running:
return return
@ -383,14 +298,11 @@ class ConnectionManager(component.Component):
Set the widgets to show the correct options from the config. Set the widgets to show the correct options from the config.
""" """
self.builder.get_object('chk_autoconnect').set_active( 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.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( 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): def __save_options(self):
""" """
@ -402,17 +314,15 @@ class ConnectionManager(component.Component):
'chk_donotshow').get_active() 'chk_donotshow').get_active()
def __update_buttons(self): def __update_buttons(self):
""" """Updates the buttons states."""
Updates the buttons states.
"""
if len(self.liststore) == 0: if len(self.liststore) == 0:
# There is nothing in the list # 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_connect').set_sensitive(False)
self.builder.get_object('button_removehost').set_sensitive(False) self.builder.get_object('button_removehost').set_sensitive(False)
self.builder.get_object('image_startdaemon').set_from_stock( self.builder.get_object('image_startdaemon').set_from_stock(
gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) 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() model, row = self.hostlist.get_selection().get_selected()
if not row: if not row:
@ -420,25 +330,23 @@ class ConnectionManager(component.Component):
return return
self.builder.get_object('button_edithost').set_sensitive(True) 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_startdaemon').set_sensitive(True)
self.builder.get_object('button_connect').set_sensitive(True) self.builder.get_object('button_connect').set_sensitive(True)
self.builder.get_object('button_removehost').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 # See if this is the currently connected host
if status == 'Connected': if status == 'Connected':
# Display a disconnect button if we're connected to this host # 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': if status == 'Connected' or status == 'Online':
self.builder.get_object('image_startdaemon').set_from_stock( self.builder.get_object('image_startdaemon').set_from_stock(
gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
self.builder.get_object('label_startdaemon').set_text( self.builder.get_object('label_startdaemon').set_text_with_mnemonic(_('_Stop Daemon'))
_('_Stop Daemon'))
# Update the start daemon button if the selected host is localhost # Update the start daemon button if the selected host is localhost
if localhost and status == 'Offline': if localhost and status == 'Offline':
# The localhost is not online # The localhost is not online
self.builder.get_object('image_startdaemon').set_from_stock( self.builder.get_object('image_startdaemon').set_from_stock(
gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
self.builder.get_object('label_startdaemon').set_text( self.builder.get_object('label_startdaemon').set_text_with_mnemonic(_('_Start Daemon'))
_('_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 # If we're connected, we can stop the dameon
self.builder.get_object('button_startdaemon').set_sensitive(True) 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 # In this case we also have all the info to shutdown the daemon
self.builder.get_object('button_startdaemon').set_sensitive(True) self.builder.get_object('button_startdaemon').set_sensitive(True)
else: else:
# Can't stop non localhost daemons, specially without the necessary info # Can't stop non localhost daemons, specially without the necessary info
self.builder.get_object('button_startdaemon').set_sensitive(False) 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): def start_daemon(self, port, config):
""" """
Attempts to start a daemon process and will show an ErrorDialog if unable 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) self.connection_manager.response(gtk.RESPONSE_OK)
component.start() 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): try_counter):
log.debug('Failed to connect: %s', reason.value) log.debug('Failed to connect: %s', reason.value)
@ -552,9 +455,8 @@ class ConnectionManager(component.Component):
if try_counter: if try_counter:
log.info('Retrying connection.. Retries left: %s', try_counter) log.info('Retrying connection.. Retries left: %s', try_counter)
return reactor.callLater( return reactor.callLater(
0.5, self.__connect, host_id, host, port, user, passwd, 0.5, self.__connect, host_id, host, port, user, password,
try_counter=try_counter - 1 try_counter=try_counter - 1)
)
msg = str(reason.value) msg = str(reason.value)
if not self.builder.get_object('chk_autostart').get_active(): 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() model, row = self.hostlist.get_selection().get_selected()
if not row: if not row:
return return
status = model[row][HOSTLIST_COL_STATUS] status = model[row][HOSTLIST_COL_STATUS]
# If status is connected then connect button disconnects instead.
if status == 'Connected': if status == 'Connected':
def on_disconnect(reason): def on_disconnect(reason):
self.__update_list() self.__update_list()
client.disconnect().addCallback(on_disconnect) client.disconnect().addCallback(on_disconnect)
return return
host_id = model[row][HOSTLIST_COL_ID] host_id, host, port, user, password, __, __ = model[row]
host = model[row][HOSTLIST_COL_HOST] try_counter = 0
port = model[row][HOSTLIST_COL_PORT] auto_start = self.builder.get_object('chk_autostart').get_active()
user = model[row][HOSTLIST_COL_USER] if status == 'Offline' and auto_start and host in LOCALHOST:
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')):
if not self.start_daemon(port, get_config_dir()): if not self.start_daemon(port, get_config_dir()):
log.debug('Failed to auto-start daemon') log.debug('Failed to auto-start daemon')
return return
return self.__connect( try_counter = 6
host_id, host, port, user, password, try_counter=6
) return self.__connect(host_id, host, port, user, password, try_counter=try_counter)
return self.__connect(host_id, host, port, user, password)
def on_button_close_clicked(self, widget): def on_button_close_clicked(self, widget):
self.connection_manager.response(gtk.RESPONSE_CLOSE) self.connection_manager.response(gtk.RESPONSE_CLOSE)
@ -610,25 +510,30 @@ class ConnectionManager(component.Component):
username = username_entry.get_text() username = username_entry.get_text()
password = password_entry.get_text() password = password_entry.get_text()
hostname = hostname_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()
try: 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: 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('') username_entry.set_text('')
password_entry.set_text('') password_entry.set_text('')
hostname_entry.set_text('') hostname_entry.set_text('')
port_spinbutton.set_value(58846) port_spinbutton.set_value(DEFAULT_PORT)
dialog.hide() dialog.hide()
def on_button_edithost_clicked(self, widget=None): def on_button_edithost_clicked(self, widget=None):
log.debug('on_button_edithost_clicked') log.debug('on_button_edithost_clicked')
model, row = self.hostlist.get_selection().get_selected() model, row = self.hostlist.get_selection().get_selected()
status = model[row][HOSTLIST_COL_STATUS] status = model[row][HOSTLIST_COL_STATUS]
host_id = model[row][HOSTLIST_COL_ID]
if status == 'Connected': if status == 'Connected':
def on_disconnect(reason): def on_disconnect(reason):
self.__update_list() self.__update_list()
@ -658,18 +563,14 @@ class ConnectionManager(component.Component):
username = username_entry.get_text() username = username_entry.get_text()
password = password_entry.get_text() password = password_entry.get_text()
hostname = hostname_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']: try:
username, password = get_localhost_auth() self.hostlist_config.update_host(host_id, hostname, port, username, password)
except ValueError as ex:
self.liststore[row][HOSTLIST_COL_HOST] = hostname ErrorDialog(_('Error Updating Host'), ex, parent=dialog).run()
self.liststore[row][HOSTLIST_COL_PORT] = port_spinbutton.get_value_as_int() else:
self.liststore[row][HOSTLIST_COL_USER] = username self.liststore[row] = host_id, hostname, port, username, password, '', ''
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 # Update the status of the hosts
self.__update_list() self.__update_list()
@ -677,44 +578,38 @@ class ConnectionManager(component.Component):
username_entry.set_text('') username_entry.set_text('')
password_entry.set_text('') password_entry.set_text('')
hostname_entry.set_text('') hostname_entry.set_text('')
port_spinbutton.set_value(58846) port_spinbutton.set_value(DEFAULT_PORT)
dialog.hide() dialog.hide()
def on_button_removehost_clicked(self, widget): def on_button_removehost_clicked(self, widget):
log.debug('on_button_removehost_clicked') log.debug('on_button_removehost_clicked')
# Get the selected rows # Get the selected rows
paths = self.hostlist.get_selection().get_selected_rows()[1] model, row = self.hostlist.get_selection().get_selected()
for path in paths: self.hostlist_config.remove_host(model[row][HOSTLIST_COL_ID])
self.liststore.remove(self.liststore.get_iter(path)) self.liststore.remove(row)
# Update the hostlist # Update the hostlist
self.__update_list() self.__update_list()
# Save the host list
self.__save_hostlist()
def on_button_startdaemon_clicked(self, widget): def on_button_startdaemon_clicked(self, widget):
log.debug('on_button_startdaemon_clicked') log.debug('on_button_startdaemon_clicked')
if self.liststore.iter_n_children(None) < 1: if self.liststore.iter_n_children(None) < 1:
# There is nothing in the list, so lets create a localhost entry # 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. # ..and start the daemon.
self.start_daemon( self.start_daemon(DEFAULT_PORT, get_config_dir())
DEFAULT_PORT, get_config_dir()
)
return return
paths = self.hostlist.get_selection().get_selected_rows()[1] paths = self.hostlist.get_selection().get_selected_rows()[1]
if len(paths) < 1: if len(paths) < 1:
return return
status = self.liststore[paths[0]][HOSTLIST_COL_STATUS] __, host, port, user, password, status, __ = self.liststore[paths[0]]
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]
if host not in ('127.0.0.1', 'localhost'): if host not in LOCALHOST:
return return
if status in ('Online', 'Connected'): if status in ('Online', 'Connected'):
@ -754,15 +649,3 @@ class ConnectionManager(component.Component):
def on_askpassword_dialog_entry_activate(self, entry): def on_askpassword_dialog_entry_activate(self, entry):
self.askpassword_dialog.response(gtk.RESPONSE_OK) 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

211
deluge/ui/hostlist.py Normal file
View File

@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Calum Lind 2017 <calumlind+deluge@gmail.com>
#
# 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())

View File

@ -18,17 +18,11 @@ from email.utils import formatdate
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from deluge.common import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE
log = logging.getLogger(__name__) 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): class AuthError(Exception):
""" """
An exception that might be raised when checking a request for An exception that might be raised when checking a request for
@ -36,7 +30,7 @@ class AuthError(Exception):
""" """
pass 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 from deluge.ui.web.json_api import export, JSONComponent # NOQA, isort:skip pylint: disable=wrong-import-position

View File

@ -10,13 +10,11 @@
from __future__ import division, unicode_literals from __future__ import division, unicode_literals
import base64 import base64
import hashlib
import json import json
import logging import logging
import os import os
import shutil import shutil
import tempfile import tempfile
import time
from types import FunctionType from types import FunctionType
from twisted.internet import defer, reactor 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 twisted.web import http, resource, server
from deluge import common, component, httpdownloader from deluge import common, component, httpdownloader
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import get_config_dir
from deluge.ui import common as uicommon
from deluge.ui.client import Client, client from deluge.ui.client import Client, client
from deluge.ui.common import FileTree2, TorrentInfo
from deluge.ui.coreconfig import CoreConfig from deluge.ui.coreconfig import CoreConfig
from deluge.ui.hostlist import HostList
from deluge.ui.sessionproxy import SessionProxy from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.translations_util import get_languages from deluge.ui.translations_util import get_languages
from deluge.ui.web.common import _, compress from deluge.ui.web.common import _, compress
@ -58,7 +57,8 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
""" """
global AUTH_LEVEL_DEFAULT, AuthError global AUTH_LEVEL_DEFAULT, AuthError
if AUTH_LEVEL_DEFAULT is None: 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): def wrap(func, *args, **kwargs):
func._json_export = True func._json_export = True
@ -262,19 +262,6 @@ class JSON(resource.Resource, component.Component):
self._local_methods[name + '.' + d] = getattr(obj, d) 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'] FILES_KEYS = ['files', 'file_progress', 'file_priorities']
@ -370,9 +357,7 @@ class WebApi(JSONComponent):
def __init__(self): def __init__(self):
super(WebApi, self).__init__('Web', depend=['SessionProxy']) super(WebApi, self).__init__('Web', depend=['SessionProxy'])
self.host_list = ConfigManager('hostlist.conf.1.2', uicommon.DEFAULT_HOSTS) self.hostlist = HostList()
if not os.path.isfile(self.host_list.config_file):
self.host_list.save()
self.core_config = CoreConfig() self.core_config = CoreConfig()
self.event_queue = EventQueue() self.event_queue = EventQueue()
try: try:
@ -410,23 +395,6 @@ class WebApi(JSONComponent):
component.get('Web.PluginManager').stop() component.get('Web.PluginManager').stop()
return self.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): def start(self):
self.core_config.start() self.core_config.start()
return self.sessionproxy.start() return self.sessionproxy.start()
@ -611,7 +579,7 @@ class WebApi(JSONComponent):
item.update(info[path]) item.update(info[path])
return item return item
file_tree = uicommon.FileTree2(paths) file_tree = FileTree2(paths)
file_tree.walk(walk) file_tree.walk(walk)
d.callback(file_tree.get_tree()) d.callback(file_tree.get_tree())
@ -685,7 +653,7 @@ class WebApi(JSONComponent):
:rtype: dictionary :rtype: dictionary
""" """
try: try:
torrent_info = uicommon.TorrentInfo(filename.strip(), 2) torrent_info = TorrentInfo(filename.strip(), 2)
return torrent_info.as_dict('name', 'info_hash', 'files_tree') return torrent_info.as_dict('name', 'info_hash', 'files_tree')
except Exception as ex: except Exception as ex:
log.error(ex) log.error(ex)
@ -730,13 +698,25 @@ class WebApi(JSONComponent):
deferreds.append(d) deferreds.append(d)
return DeferredList(deferreds, consumeErrors=False) 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 @export
def get_hosts(self): def get_hosts(self):
""" """
Return the hosts in the hostlist. Return the hosts in the hostlist.
""" """
log.debug('get_hosts called') 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 @export
def get_host_status(self, host_id): 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) d.addCallback(on_connect, c, host_id).addErrback(on_connect_failed, host_id)
return d 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 @export
def start_daemon(self, port): def start_daemon(self, port):
""" """
@ -827,55 +840,6 @@ class WebApi(JSONComponent):
main_deferred.callback((False, 'An error occurred')) main_deferred.callback((False, 'An error occurred'))
return main_deferred 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 @export
def get_config(self): def get_config(self):
""" """