[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)
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()

View File

@ -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
)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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]))

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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

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 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

View File

@ -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):
"""