[UI] Further refactoring of the connection managers

* Add host Edit button to WebUI.
 * Updated and fixed associated tests.
 * Refactored related gtkui code to better understand code flow.
 * Removed dead code in gtkui.
This commit is contained in:
Calum Lind 2017-04-01 11:40:15 +01:00
parent 2f11bb8303
commit 31555ee5ed
19 changed files with 822 additions and 965 deletions

View file

@ -997,6 +997,40 @@ def create_localclient_account(append=False):
os.fsync(_file.fileno())
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.
"""
from deluge.configmanager import get_config_dir
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 set_env_variable(name, value):
"""
:param name: environment variable name

View file

@ -66,7 +66,7 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
self.deluge_web = DelugeWeb(daemon=False)
host = list(self.deluge_web.web_api.hostlist.get_hosts_info2()[0])
host = list(self.deluge_web.web_api.hostlist.config['hosts'][0])
host[2] = self.listen_port
self.deluge_web.web_api.hostlist.config['hosts'][0] = tuple(host)
self.host_id = host[0]

View file

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

View file

@ -11,10 +11,9 @@ from twisted.internet import defer
import deluge.component as component
from deluge import error
from deluge.common import AUTH_LEVEL_NORMAL
from deluge.common import AUTH_LEVEL_NORMAL, get_localhost_auth
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

View file

@ -11,11 +11,11 @@ from __future__ import unicode_literals
import deluge.component as component
import deluge.error
from deluge.common import get_localhost_auth
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.hostlist import get_localhost_auth
from .basetest import BaseTestCase

View file

@ -23,9 +23,8 @@ import deluge.ui.console
import deluge.ui.console.cmdline.commands.quit
import deluge.ui.console.main
import deluge.ui.web.server
from deluge.common import utf8_encode_structure
from deluge.common import get_localhost_auth, 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
@ -334,7 +333,7 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
def set_up(self):
# Avoid calling reactor.shutdown after commands are executed by main.exec_args()
self.patch(deluge.ui.console.cmdline.commands.quit, 'reactor', common.ReactorOverride())
deluge.ui.console.main.reactor = common.ReactorOverride()
return UIWithDaemonBaseTestCase.set_up(self)
@defer.inlineCallbacks
@ -344,7 +343,6 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
[username] + ['--password'] + [password] + ['status'])
fd = StringFileDescriptor(sys.stdout)
self.patch(sys, 'stdout', fd)
self.patch(deluge.ui.console.main, 'reactor', common.ReactorOverride())
yield self.exec_command()

View file

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

View file

@ -17,11 +17,10 @@ import sys
from twisted.internet import defer, reactor, ssl
from twisted.internet.protocol import ClientFactory
import deluge.common
from deluge import error
from deluge.common import get_localhost_auth, get_version
from deluge.decorators import deprecated
from deluge.transfer import DelugeTransferProtocol
from deluge.ui.hostlist import get_localhost_auth
RPC_RESPONSE = 1
RPC_ERROR = 2
@ -384,8 +383,7 @@ class DaemonSSLProxy(DaemonProxy):
def authenticate(self, username, password):
log.debug('%s.authenticate: %s', self.__class__.__name__, username)
login_deferred = defer.Deferred()
d = self.call('daemon.login', username, password,
client_version=deluge.common.get_version())
d = self.call('daemon.login', username, password, client_version=get_version())
d.addCallbacks(self.__on_login, self.__on_login_fail, callbackArgs=[username, login_deferred],
errbackArgs=[login_deferred])
return login_deferred
@ -619,17 +617,14 @@ class Client(object):
self.stop_standalone()
def start_daemon(self, port, config):
"""
Starts a daemon process.
"""Starts a daemon process.
:param port: the port for the daemon to listen on
:type port: int
:param config: the path to the current config folder
:type config: str
:returns: True if started, False if not
:rtype: bool
Args:
port (int): Port for the daemon to listen on.
config (str): Config path to pass to daemon.
:raises OSError: received from subprocess.call()
Returns:
bool: True is successfully started the daemon, False otherwise.
"""
# subprocess.popen does not work with unicode args (with non-ascii characters) on windows
@ -644,13 +639,12 @@ class Client(object):
'the deluged package is installed, or added to your PATH.'))
else:
log.exception(ex)
raise ex
except Exception as ex:
log.error('Unable to start daemon!')
log.exception(ex)
return False
else:
return True
return False
def is_localhost(self):
"""

View file

@ -13,7 +13,7 @@ import logging
import deluge.component as component
from deluge.decorators import overrides
from deluge.ui.client import Client, client
from deluge.ui.client import 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
@ -48,7 +48,7 @@ class ConnectionManager(BaseMode, PopupsHandler):
space_below=True)
self.push_popup(popup, clear=True)
for host_entry in self.hostlist.get_host_info():
for host_entry in self.hostlist.get_hosts_info():
host_id, hostname, port, user = host_entry
args = {'data': host_id, 'foreground': 'red'}
state = 'Offline'
@ -64,34 +64,13 @@ class ConnectionManager(BaseMode, PopupsHandler):
self.refresh()
def update_hosts_status(self):
"""Updates the host status"""
def on_connect(result, c, host_id):
def on_info(info, c):
self.statuses[host_id] = info
self.update_select_host_popup()
c.disconnect()
def on_info_fail(reason, c):
if host_id in self.statuses:
del self.statuses[host_id]
c.disconnect()
d = c.daemon.info()
d.addCallback(on_info, c)
d.addErrback(on_info_fail, c)
def on_connect_failed(reason, host_id):
if host_id in self.statuses:
del self.statuses[host_id]
for host_entry in self.hostlist.get_hosts_info():
def on_host_status(status_info):
self.statuses[status_info[0]] = status_info
self.update_select_host_popup()
for host_entry in self.hostlist.get_hosts_info2():
c = Client()
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)
self.hostlist.get_host_status(host_entry[0]).addCallback(on_host_status)
def _on_connected(self, result):
d = component.get('ConsoleUI').start_console()
@ -108,12 +87,9 @@ class ConnectionManager(BaseMode, PopupsHandler):
def _host_selected(self, selected_host, *args, **kwargs):
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)
d = self.hostlist.connect_host(selected_host)
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):

View file

@ -14,14 +14,14 @@ import os
from socket import gaierror, gethostbyname
import gtk
from twisted.internet import reactor
from twisted.internet import defer, reactor
import deluge.component as component
from deluge.common import resource_filename
from deluge.common import resource_filename, windows_check
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.gtkui.common import get_clipboard_text, get_deluge_icon
from deluge.ui.gtkui.common import get_clipboard_text
from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog
from deluge.ui.hostlist import DEFAULT_PORT, HostList
@ -68,7 +68,6 @@ def cell_render_status(column, cell, model, row, data):
pixbuf = None
if status in HOSTLIST_STATUS:
pixbuf = HOSTLIST_PIXBUFS[HOSTLIST_STATUS.index(status)]
cell.set_property('pixbuf', pixbuf)
@ -76,7 +75,7 @@ class ConnectionManager(component.Component):
def __init__(self):
component.Component.__init__(self, 'ConnectionManager')
self.gtkui_config = ConfigManager('gtkui.conf')
self.hostlist = HostList()
self.running = False
# Component overrides
@ -93,92 +92,408 @@ class ConnectionManager(component.Component):
# Public methods
def show(self):
"""
Show the ConnectionManager dialog.
"""
# Get the gtk builder file for the connection manager
"""Show the ConnectionManager dialog."""
self.builder = gtk.Builder()
# The main dialog
self.builder.add_from_file(resource_filename(
'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')))
# The ask password dialog
self.builder.add_from_file(resource_filename(
'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.askpassword_dialog = self.builder.get_object('askpassword_dialog')
self.askpassword_dialog.set_transient_for(self.connection_manager)
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
if not HOSTLIST_PIXBUFS:
for stock_id in (gtk.STOCK_NO, gtk.STOCK_YES, gtk.STOCK_CONNECT):
HOSTLIST_PIXBUFS.append(
self.connection_manager.render_icon(
stock_id, gtk.ICON_SIZE_MENU
)
)
self.connection_manager.render_icon(stock_id, gtk.ICON_SIZE_MENU))
# Create the host list gtkliststore
# id-hash, hostname, port, username, password, status, version
self.liststore = gtk.ListStore(str, str, int, str, str, str, str)
# Setup the hostlist liststore and treeview
self.treeview = self.builder.get_object('treeview_hostlist')
self.liststore = self.builder.get_object('liststore_hostlist')
# 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, HOSTLIST_COL_STATUS)
self.hostlist.append_column(column)
self.treeview.append_column(column)
render = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Host'), render, text=HOSTLIST_COL_HOST)
column.set_cell_data_func(
render, cell_render_host, (HOSTLIST_COL_HOST, HOSTLIST_COL_PORT, HOSTLIST_COL_USER))
host_data = (HOSTLIST_COL_HOST, HOSTLIST_COL_PORT, HOSTLIST_COL_USER)
column.set_cell_data_func(render, cell_render_host, host_data)
column.set_expand(True)
self.hostlist.append_column(column)
render = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Version'), render, text=HOSTLIST_COL_VERSION)
self.hostlist.append_column(column)
self.treeview.append_column(column)
column = gtk.TreeViewColumn(_('Version'), gtk.CellRendererText(), text=HOSTLIST_COL_VERSION)
self.treeview.append_column(column)
# Load any saved host entries
self._load_liststore()
# Set widgets to values from gtkui config.
self._load_widget_config()
self._update_widget_buttons()
# Connect the signals to the handlers
self.builder.connect_signals(self)
self.hostlist.get_selection().connect(
'changed', self.on_hostlist_selection_changed
)
# Load any saved host entries
self.__load_hostlist()
self.__load_options()
self.__update_list()
self.treeview.get_selection().connect('changed', self.on_hostlist_selection_changed)
# Set running True before update status call.
self.running = True
# Trigger the on_selection_changed code and select the first host
# if possible
self.hostlist.get_selection().unselect_all()
if len(self.liststore) > 0:
self.hostlist.get_selection().select_path(0)
if windows_check():
# Call to simulate() required to workaround showing daemon status (see #2813)
reactor.simulate()
self._update_host_status()
# Trigger the on_selection_changed code and select the first host if possible
self.treeview.get_selection().unselect_all()
if len(self.liststore):
self.treeview.get_selection().select_path(0)
# Run the dialog
self.connection_manager.run()
# Dialog closed so cleanup.
self.running = False
# Save the toggle options
self.__save_options()
self.connection_manager.destroy()
del self.builder
del self.connection_manager
del self.liststore
del self.hostlist
del self.treeview
def _load_liststore(self):
"""Load saved host entries"""
for host_entry in self.hostlist.get_hosts_info():
host_id, host, port, username = host_entry
self.liststore.append([host_id, host, port, username, '', '', ''])
def _load_widget_config(self):
"""Set the widgets to show the correct options from the config."""
self.builder.get_object('chk_autoconnect').set_active(
self.gtkui_config['autoconnect'])
self.builder.get_object('chk_autostart').set_active(
self.gtkui_config['autostart_localhost'])
self.builder.get_object('chk_donotshow').set_active(
not self.gtkui_config['show_connection_manager_on_start'])
def _update_host_status(self):
"""Updates the host status"""
if not self.running:
# Callback likely fired after the window closed.
return
def on_host_status(status_info, row):
if self.running and row:
row[HOSTLIST_COL_STATUS] = status_info[1]
row[HOSTLIST_COL_VERSION] = status_info[2]
self._update_widget_buttons()
deferreds = []
for row in self.liststore:
host_id = row[HOSTLIST_COL_ID]
d = self.hostlist.get_host_status(host_id)
try:
d.addCallback(on_host_status, row)
except AttributeError:
on_host_status(d, row)
else:
deferreds.append(d)
defer.DeferredList(deferreds)
def _update_widget_buttons(self):
"""Updates the dialog button states."""
self.builder.get_object('button_refresh').set_sensitive(len(self.liststore))
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_label(_('C_onnect'))
self.builder.get_object('button_edithost').set_sensitive(False)
self.builder.get_object('button_removehost').set_sensitive(False)
self.builder.get_object('button_startdaemon').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_with_mnemonic('_Start Daemon')
model, row = self.treeview.get_selection().get_selected()
if row:
self.builder.get_object('button_edithost').set_sensitive(True)
self.builder.get_object('button_removehost').set_sensitive(True)
else:
return
# Get selected host info.
__, host, port, __, __, status, __ = model[row]
try:
gethostbyname(host)
except gaierror as ex:
log.error('Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1])
self.builder.get_object('button_connect').set_sensitive(False)
return
log.debug('Host Status: %s, %s', host, status)
# Check to see if the host is online
if status == 'Connected' or status == 'Online':
self.builder.get_object('button_connect').set_sensitive(True)
self.builder.get_object('image_startdaemon').set_from_stock(
gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
self.builder.get_object('label_startdaemon').set_text_with_mnemonic(_('_Stop Daemon'))
self.builder.get_object('button_startdaemon').set_sensitive(True)
if status == 'Connected':
# Display a disconnect button if we're connected to this host
self.builder.get_object('button_connect').set_label(_('_Disconnect'))
self.builder.get_object('button_removehost').set_sensitive(False)
elif host in LOCALHOST:
# If localhost we can start the dameon.
self.builder.get_object('button_startdaemon').set_sensitive(True)
def start_daemon(self, port, config):
"""Attempts to start local daemon process and will show an ErrorDialog if not.
Args:
port (int): Port for the daemon to listen on.
config (str): Config path to pass to daemon.
Returns:
bool: True is successfully started the daemon, False otherwise.
"""
if client.start_daemon(port, config):
log.debug('Localhost daemon started')
reactor.callLater(0.5, self._update_host_status)
return True
else:
ErrorDialog(
_('Unable to start daemon!'),
_('Check deluged package is installed and logs for further details')).run()
return False
# Signal handlers
def _connect(self, host_id, username=None, password=None, try_counter=0):
def do_connect(result, username=None, password=None, *args):
log.debug('Attempting to connect to daemon...')
for host_entry in self.hostlist.config['hosts']:
if host_entry[0] == host_id:
__, host, port, host_user, host_pass = host_entry
username = username if username else host_user
password = password if password else host_pass
d = client.connect(host, port, username, password)
d.addCallback(self._on_connect, host_id)
d.addErrback(self._on_connect_fail, host_id, try_counter)
return d
if client.connected():
return client.disconnect().addCallback(do_connect, username, password)
else:
return do_connect(None, username, password)
def _on_connect(self, daemon_info, host_id):
log.debug('Connected to daemon: %s', host_id)
if self.gtkui_config['autoconnect']:
self.gtkui_config['autoconnect_host_id'] = host_id
if self.running:
# When connected to a client, and then trying to connect to another,
# this component will be stopped(while the connect deferred is
# running), so, self.connection_manager will be deleted.
# If that's not the case, close the dialog.
self.connection_manager.response(gtk.RESPONSE_OK)
component.start()
def _on_connect_fail(self, reason, host_id, try_counter):
log.debug('Failed to connect: %s', reason.value)
if reason.check(AuthenticationRequired, BadLoginError):
log.debug('PasswordRequired exception')
dialog = AuthenticationDialog(reason.value.message, reason.value.username)
def dialog_finished(response_id):
if response_id == gtk.RESPONSE_OK:
self.__connect(host_id, dialog.get_username(), dialog.get_password())
return dialog.run().addCallback(dialog_finished)
elif reason.trap(IncompatibleClient):
return ErrorDialog(_('Incompatible Client'), reason.value.message).run()
if try_counter:
log.info('Retrying connection.. Retries left: %s', try_counter)
return reactor.callLater(0.8, self._connect, host_id, try_counter=try_counter - 1)
msg = str(reason.value)
if not self.gtkui_config['autostart_localhost']:
msg += '\n' + _('Auto-starting the daemon locally is not enabled. '
'See "Options" on the "Connection Manager".')
ErrorDialog(_('Failed To Connect'), msg).run()
def on_button_connect_clicked(self, widget=None):
"""Button handler for connect to or disconnect from daemon."""
model, row = self.treeview.get_selection().get_selected()
if not row:
return
host_id, host, port, __, __, status, __ = model[row]
# If status is connected then connect button disconnects instead.
if status == 'Connected':
def on_disconnect(reason):
self._update_host_status()
return client.disconnect().addCallback(on_disconnect)
try_counter = 0
auto_start = self.builder.get_object('chk_autostart').get_active()
if auto_start and host in LOCALHOST and status == 'Offline':
# Start the local daemon and then connect with retries set.
if self.start_daemon(port, get_config_dir()):
try_counter = 4
else:
# Don't attempt to connect to offline daemon.
return
self._connect(host_id, try_counter=try_counter)
def on_button_close_clicked(self, widget):
self.connection_manager.response(gtk.RESPONSE_CLOSE)
def _run_addhost_dialog(self, edit_host_info=None):
"""Create and runs the add host dialog.
Supplying edit_host_info changes the dialog to an edit dialog.
Args:
edit_host_info (list): A list of (host, port, user, pass) to edit.
Returns:
list: The new host info values (host, port, user, pass).
"""
self.builder.add_from_file(resource_filename(
'deluge.ui.gtkui', os.path.join('glade', 'connection_manager.addhost.ui')))
dialog = self.builder.get_object('addhost_dialog')
dialog.set_transient_for(self.connection_manager)
hostname_entry = self.builder.get_object('entry_hostname')
port_spinbutton = self.builder.get_object('spinbutton_port')
username_entry = self.builder.get_object('entry_username')
password_entry = self.builder.get_object('entry_password')
if edit_host_info:
dialog.set_title(_('Edit Host'))
hostname_entry.set_text(edit_host_info[0])
port_spinbutton.set_value(edit_host_info[1])
username_entry.set_text(edit_host_info[2])
password_entry.set_text(edit_host_info[3])
response = dialog.run()
new_host_info = []
if response:
new_host_info.append(hostname_entry.get_text())
new_host_info.append(port_spinbutton.get_value_as_int())
new_host_info.append(username_entry.get_text())
new_host_info.append(password_entry.get_text())
dialog.destroy()
return new_host_info
def on_button_addhost_clicked(self, widget):
log.debug('on_button_addhost_clicked')
host_info = self._run_addhost_dialog()
if host_info:
hostname, port, username, password = host_info
try:
host_id = self.hostlist.add_host(hostname, port, username, password)
except ValueError as ex:
ErrorDialog(_('Error Adding Host'), ex).run()
else:
self.liststore.append([host_id, hostname, port, username, password, 'Offline', ''])
self._update_host_status()
def on_button_edithost_clicked(self, widget=None):
log.debug('on_button_edithost_clicked')
model, row = self.treeview.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_host_status()
client.disconnect().addCallback(on_disconnect)
return
host_info = [
self.liststore[row][HOSTLIST_COL_HOST],
self.liststore[row][HOSTLIST_COL_PORT],
self.liststore[row][HOSTLIST_COL_USER],
self.liststore[row][HOSTLIST_COL_PASS]]
new_host_info = self._run_addhost_dialog(edit_host_info=host_info)
if new_host_info:
hostname, port, username, password = new_host_info
try:
self.hostlist.update_host(host_id, hostname, port, username, password)
except ValueError as ex:
ErrorDialog(_('Error Updating Host'), ex).run()
else:
self.liststore[row] = host_id, hostname, port, username, password, '', ''
self._update_host_status()
def on_button_removehost_clicked(self, widget):
log.debug('on_button_removehost_clicked')
# Get the selected rows
model, row = self.treeview.get_selection().get_selected()
self.hostlist.remove_host(model[row][HOSTLIST_COL_ID])
self.liststore.remove(row)
# Update the hostlist
self._update_host_status()
def on_button_startdaemon_clicked(self, widget):
log.debug('on_button_startdaemon_clicked')
if not self.liststore.iter_n_children(None):
# There is nothing in the list, so lets create a localhost entry
try:
self.hostlist.add_default_host()
except ValueError as ex:
log.error('Error adding default host: %s', ex)
else:
self.start_daemon(DEFAULT_PORT, get_config_dir())
finally:
return
paths = self.treeview.get_selection().get_selected_rows()[1]
if len(paths):
__, host, port, user, password, status, __ = self.liststore[paths[0]]
else:
return
if host not in LOCALHOST:
return
def on_daemon_status_change(d):
"""Daemon start/stop callback"""
reactor.callLater(0.7, self._update_host_status)
if status in ('Online', 'Connected'):
# Button will stop the daemon if status is online or connected.
def on_connect(d, c):
"""Client callback to call daemon shutdown"""
c.daemon.shutdown().addCallback(on_daemon_status_change)
if client.connected() and (host, port, user) == client.connection_info():
client.daemon.shutdown().addCallback(on_daemon_status_change)
elif user and password:
c = Client()
c.connect(host, port, user, password).addCallback(on_connect, c)
else:
# Otherwise button will start the daemon.
self.start_daemon(port, get_config_dir()).addCallback(on_daemon_status_change)
def on_button_refresh_clicked(self, widget):
self._update_host_status()
def on_hostlist_row_activated(self, tree, path, view_column):
self.on_button_connect_clicked()
def on_hostlist_selection_changed(self, treeselection):
self._update_widget_buttons()
def on_chk_toggled(self, widget):
self.gtkui_config['autoconnect'] = self.builder.get_object('chk_autoconnect').get_active()
self.gtkui_config['autostart_localhost'] = self.builder.get_object('chk_autostart').get_active()
self.gtkui_config['show_connection_manager_on_start'] = not self.builder.get_object(
'chk_donotshow').get_active()
def on_entry_host_paste_clipboard(self, widget):
text = get_clipboard_text()
@ -194,456 +509,3 @@ class ConnectionManager(component.Component):
self.builder.get_object('entry_username').set_text(parsed.username)
if parsed.password:
self.builder.get_object('entry_password').set_text(parsed.password)
def __load_hostlist(self):
"""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):
"""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:
if host_id == row[HOSTLIST_COL_ID]:
return row
return None
def __update_list(self):
"""Updates the host status"""
if not hasattr(self, 'liststore'):
# This callback was probably fired after the window closed
return
def on_connect(result, c, host_id):
# Return if the deferred callback was done after the dialog was closed
if not self.running:
return
row = self.__get_host_row(host_id)
def on_info(info, c):
if not self.running:
return
if row:
row[HOSTLIST_COL_STATUS] = 'Online'
row[HOSTLIST_COL_VERSION] = info
self.__update_buttons()
c.disconnect()
def on_info_fail(reason, c):
if not self.running:
return
if row:
row[HOSTLIST_COL_STATUS] = 'Offline'
self.__update_buttons()
c.disconnect()
d = c.daemon.info()
d.addCallback(on_info, c)
d.addErrback(on_info_fail, c)
def on_connect_failed(reason, host_id):
if not self.running:
return
row = self.__get_host_row(host_id)
if row:
row[HOSTLIST_COL_STATUS] = 'Offline'
row[HOSTLIST_COL_VERSION] = ''
self.__update_buttons()
for row in self.liststore:
host_id = row[HOSTLIST_COL_ID]
host = row[HOSTLIST_COL_HOST]
port = row[HOSTLIST_COL_PORT]
user = row[HOSTLIST_COL_USER]
try:
ip = gethostbyname(host)
except gaierror as ex:
log.error('Error resolving host %s to ip: %s', host, ex.args[1])
continue
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
log.debug('Client connected, query info: %s', info)
row[HOSTLIST_COL_VERSION] = info
self.__update_buttons()
row[HOSTLIST_COL_STATUS] = 'Connected'
log.debug('Query daemon info')
client.daemon.info().addCallback(on_info, row)
continue
# Create a new Client instance
c = Client()
d = c.connect(host, port, skip_authentication=True)
d.addCallback(on_connect, c, host_id)
d.addErrback(on_connect_failed, host_id)
def __load_options(self):
"""
Set the widgets to show the correct options from the config.
"""
self.builder.get_object('chk_autoconnect').set_active(
self.gtkui_config['autoconnect'])
self.builder.get_object('chk_autostart').set_active(
self.gtkui_config['autostart_localhost'])
self.builder.get_object('chk_donotshow').set_active(
not self.gtkui_config['show_connection_manager_on_start'])
def __save_options(self):
"""
Set options in gtkui config from the toggle buttons.
"""
self.gtkui_config['autoconnect'] = self.builder.get_object('chk_autoconnect').get_active()
self.gtkui_config['autostart_localhost'] = self.builder.get_object('chk_autostart').get_active()
self.gtkui_config['show_connection_manager_on_start'] = not self.builder.get_object(
'chk_donotshow').get_active()
def __update_buttons(self):
"""Updates the buttons states."""
if len(self.liststore) == 0:
# There is nothing in the list
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_with_mnemonic('_Start Daemon')
model, row = self.hostlist.get_selection().get_selected()
if not row:
self.builder.get_object('button_edithost').set_sensitive(False)
return
self.builder.get_object('button_edithost').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_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
self.builder.get_object('button_connect').set_label('gtk-disconnect')
self.builder.get_object('button_removehost').set_sensitive(False)
else:
self.builder.get_object('button_connect').set_label('gtk-connect')
if status == 'Offline' and not localhost:
self.builder.get_object('button_connect').set_sensitive(False)
# Check to see if the host is online
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_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_with_mnemonic(_('_Start Daemon'))
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 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)
def start_daemon(self, port, config):
"""
Attempts to start a daemon process and will show an ErrorDialog if unable
to.
"""
try:
return client.start_daemon(port, config)
except OSError as ex:
from errno import ENOENT
if ex.errno == ENOENT:
ErrorDialog(
_('Unable to start daemon!'),
_('Deluge cannot find the `deluged` executable, check that '
'the deluged package is installed, or added to your PATH.')).run()
return False
else:
raise ex
except Exception:
import traceback
import sys
tb = sys.exc_info()
ErrorDialog(
_('Unable to start daemon!'),
_('Please examine the details for more information.'),
details=traceback.format_exc(tb[2])).run()
# Signal handlers
def __connect(self, host_id, host, port, username, password,
skip_authentication=False, try_counter=0):
def do_connect(*args):
d = client.connect(host, port, username, password, skip_authentication)
d.addCallback(self.__on_connected, host_id)
d.addErrback(self.__on_connected_failed, host_id, host, port,
username, password, try_counter)
return d
if client.connected():
return client.disconnect().addCallback(do_connect)
else:
return do_connect()
def __on_connected(self, daemon_info, host_id):
if self.gtkui_config['autoconnect']:
self.gtkui_config['autoconnect_host_id'] = host_id
if self.running:
# When connected to a client, and then trying to connect to another,
# this component will be stopped(while the connect deferred is
# running), so, self.connection_manager will be deleted.
# If that's not the case, close the dialog.
self.connection_manager.response(gtk.RESPONSE_OK)
component.start()
def __on_connected_failed(self, reason, host_id, host, port, user, password,
try_counter):
log.debug('Failed to connect: %s', reason.value)
if reason.check(AuthenticationRequired, BadLoginError):
log.debug('PasswordRequired exception')
dialog = AuthenticationDialog(reason.value.message, reason.value.username)
def dialog_finished(response_id, host, port, user):
if response_id == gtk.RESPONSE_OK:
self.__connect(host_id, host, port,
user and user or dialog.get_username(),
dialog.get_password())
d = dialog.run().addCallback(dialog_finished, host, port, user)
return d
elif reason.trap(IncompatibleClient):
return ErrorDialog(_('Incompatible Client'), reason.value.message).run()
if try_counter:
log.info('Retrying connection.. Retries left: %s', try_counter)
return reactor.callLater(
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():
msg += '\n' + _('Auto-starting the daemon locally is not enabled. '
'See "Options" on the "Connection Manager".')
ErrorDialog(_('Failed To Connect'), msg).run()
def on_button_connect_clicked(self, widget=None):
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, 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
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)
def on_button_addhost_clicked(self, widget):
log.debug('on_button_addhost_clicked')
dialog = self.builder.get_object('addhost_dialog')
dialog.set_transient_for(self.connection_manager)
dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
hostname_entry = self.builder.get_object('entry_hostname')
port_spinbutton = self.builder.get_object('spinbutton_port')
username_entry = self.builder.get_object('entry_username')
password_entry = self.builder.get_object('entry_password')
button_addhost_save = self.builder.get_object('button_addhost_save')
button_addhost_save.hide()
button_addhost_add = self.builder.get_object('button_addhost_add')
button_addhost_add.show()
response = dialog.run()
if response == 1:
username = username_entry.get_text()
password = password_entry.get_text()
hostname = hostname_entry.get_text()
port = port_spinbutton.get_value_as_int()
try:
host_id = self.hostlist_config.add_host(hostname, port, username, password)
except ValueError as ex:
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(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()
client.disconnect().addCallback(on_disconnect)
return
dialog = self.builder.get_object('addhost_dialog')
dialog.set_transient_for(self.connection_manager)
dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
hostname_entry = self.builder.get_object('entry_hostname')
port_spinbutton = self.builder.get_object('spinbutton_port')
username_entry = self.builder.get_object('entry_username')
password_entry = self.builder.get_object('entry_password')
button_addhost_save = self.builder.get_object('button_addhost_save')
button_addhost_save.show()
button_addhost_add = self.builder.get_object('button_addhost_add')
button_addhost_add.hide()
username_entry.set_text(self.liststore[row][HOSTLIST_COL_USER])
password_entry.set_text(self.liststore[row][HOSTLIST_COL_PASS])
hostname_entry.set_text(self.liststore[row][HOSTLIST_COL_HOST])
port_spinbutton.set_value(self.liststore[row][HOSTLIST_COL_PORT])
response = dialog.run()
if response == 2:
username = username_entry.get_text()
password = password_entry.get_text()
hostname = hostname_entry.get_text()
port = port_spinbutton.get_value_as_int()
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()
username_entry.set_text('')
password_entry.set_text('')
hostname_entry.set_text('')
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
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()
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
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())
return
paths = self.hostlist.get_selection().get_selected_rows()[1]
if len(paths) < 1:
return
__, host, port, user, password, status, __ = self.liststore[paths[0]]
if host not in LOCALHOST:
return
if status in ('Online', 'Connected'):
# We need to stop this daemon
# Call the shutdown method on the daemon
def on_daemon_shutdown(d):
# Update display to show change
reactor.callLater(0.8, self.__update_list)
if client.connected() and client.connection_info() == (host, port, user):
client.daemon.shutdown().addCallback(on_daemon_shutdown)
elif user and password:
# Create a new client instance
c = Client()
def on_connect(d, c):
log.debug('on_connect')
c.daemon.shutdown().addCallback(on_daemon_shutdown)
c.connect(host, port, user, password).addCallback(on_connect, c)
elif status == 'Offline':
self.start_daemon(port, get_config_dir())
reactor.callLater(0.8, self.__update_list)
def on_button_refresh_clicked(self, widget):
self.__update_list()
def on_hostlist_row_activated(self, tree, path, view_column):
self.on_button_connect_clicked()
def on_hostlist_selection_changed(self, treeselection):
self.__update_buttons()
def on_askpassword_dialog_connect_button_clicked(self, widget):
log.debug('on on_askpassword_dialog_connect_button_clicked')
self.askpassword_dialog.response(gtk.RESPONSE_OK)
def on_askpassword_dialog_entry_activate(self, entry):
self.askpassword_dialog.response(gtk.RESPONSE_OK)

View file

@ -13,7 +13,7 @@
<property name="border_width">5</property>
<property name="title" translatable="yes">Add Host</property>
<property name="modal">True</property>
<property name="window_position">center</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
@ -43,14 +43,14 @@
</child>
<child>
<object class="GtkButton" id="button_addhost_add">
<property name="label">gtk-add</property>
<property name="label">_Save</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
@ -58,20 +58,6 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_addhost_save">
<property name="label">gtk-save</property>
<property name="use_action_appearance">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -114,7 +100,7 @@
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" />
<signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/>
</object>
</child>
</object>
@ -253,15 +239,11 @@
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">button_addhost_cancel</action-widget>
<action-widget response="1">button_addhost_add</action-widget>
<action-widget response="2">button_addhost_save</action-widget>
<action-widget response="0">button_addhost_add</action-widget>
</action-widgets>
</object>
</interface>

View file

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkDialog" id="askpassword_dialog">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Password Required</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="askpassword_dialog_connect_button">
<property name="label">gtk-connect</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_askpassword_dialog_connect_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="askpassword_dialog_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon-size">6</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="askpassword_dialog_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">●</property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="1">askpassword_dialog_connect_button</action-widget>
</action-widgets>
</object>
</interface>

View file

@ -46,10 +46,11 @@
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="hostlist">
<object class="GtkTreeView" id="treeview_hostlist">
<property name="height_request">80</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_hostlist</property>
<signal name="row-activated" handler="on_hostlist_row_activated" swapped="no"/>
</object>
</child>
@ -242,12 +243,12 @@
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_close">
<property name="label">gtk-close</property>
<property name="label">_Close</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_button_close_clicked" swapped="no"/>
</object>
<packing>
@ -258,12 +259,12 @@
</child>
<child>
<object class="GtkButton" id="button_connect">
<property name="label">gtk-connect</property>
<property name="label">C_onnect</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_button_connect_clicked" swapped="no"/>
</object>
<packing>
@ -303,6 +304,7 @@
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@ -318,6 +320,7 @@
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@ -333,6 +336,7 @@
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@ -368,4 +372,22 @@
<action-widget response="0">button_connect</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="liststore_hostlist">
<columns>
<!-- column-name host_id -->
<column type="gchararray"/>
<!-- column-name hostname -->
<column type="gchararray"/>
<!-- column-name port -->
<column type="gint"/>
<!-- column-name username -->
<column type="gchararray"/>
<!-- column-name password -->
<column type="gchararray"/>
<!-- column-name status -->
<column type="gchararray"/>
<!-- column-name version -->
<column type="gchararray"/>
</columns>
</object>
</interface>

View file

@ -21,7 +21,7 @@ pygtk.require('2.0') # NOQA: E402
# isort:imports-thirdparty
from gobject import set_prgname
from gtk import RESPONSE_OK, RESPONSE_YES
from gtk import RESPONSE_YES
from gtk.gdk import WINDOWING, threads_enter, threads_init, threads_leave
from twisted.internet import defer, gtk2reactor
from twisted.internet.error import ReactorAlreadyInstalledError
@ -38,12 +38,12 @@ except ReactorAlreadyInstalledError as ex:
import deluge.component as component
from deluge.common import fsize, fspeed, get_default_download_dir, osx_check, windows_check
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import AuthenticationRequired, BadLoginError, DaemonRunningError
from deluge.error import DaemonRunningError
from deluge.ui.client import client
from deluge.ui.gtkui.addtorrentdialog import AddTorrentDialog
from deluge.ui.gtkui.common import associate_magnet_links
from deluge.ui.gtkui.connectionmanager import ConnectionManager
from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog, YesNoDialog
from deluge.ui.gtkui.dialogs import YesNoDialog
from deluge.ui.gtkui.filtertreeview import FilterTreeView
from deluge.ui.gtkui.ipcinterface import IPCInterface, process_args
from deluge.ui.gtkui.mainwindow import MainWindow
@ -61,7 +61,7 @@ from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.translations_util import set_language, setup_translations
set_prgname(b'deluge')
set_prgname('deluge'.encode('utf8'))
log = logging.getLogger(__name__)
try:
@ -220,7 +220,7 @@ class GtkUI(object):
# Setup RPC stats logging
# daemon_bps: time, bytes_sent, bytes_recv
self.daemon_bps = (0, 0, 0)
self.rpc_stats = LoopingCall(self.print_rpc_stats)
self.rpc_stats = LoopingCall(self.log_rpc_stats)
self.closing = False
# Twisted catches signals to terminate, so have it call a pre_shutdown method.
@ -238,8 +238,8 @@ class GtkUI(object):
# Initialize gdk threading
threads_enter()
reactor.run()
# Reactor is not running. Any async callbacks (Deferreds) can no longer
# be processed from this point on.
# Reactor no longer running so async callbacks (Deferreds) cannot be
# processed after this point.
threads_leave()
def shutdown(self, *args, **kwargs):
@ -267,11 +267,12 @@ class GtkUI(object):
reactor.stop()
# Restart the application after closing if MainWindow attribute set.
# Restart the application after closing if MainWindow restart attribute set.
if component.get('MainWindow').restart:
os.execv(sys.argv[0], sys.argv)
def print_rpc_stats(self):
def log_rpc_stats(self):
"""Log RPC statistics for thinclient mode."""
if not client.connected():
return
@ -290,144 +291,73 @@ class GtkUI(object):
log.debug('_on_reactor_start')
self.mainwindow.first_show()
if self.config['standalone']:
def on_dialog_response(response):
if response != RESPONSE_YES:
# The user does not want to turn Standalone Mode off, so just quit
self.mainwindow.quit()
return
if not self.config['standalone']:
return self._start_thinclient()
err_msg = ''
try:
client.start_standalone()
except DaemonRunningError:
err_msg = _('A Deluge daemon (deluged) is already running.\n'
'To use Standalone mode, stop local daemon and restart Deluge.')
except ImportError as ex:
if 'No module named libtorrent' in ex.message:
err_msg = _('Only Thin Client mode is available because libtorrent is not installed.\n'
'To use Standalone mode, please install libtorrent package.')
else:
log.exception(ex)
err_msg = _('Only Thin Client mode is available due to unknown Import Error.\n'
'To use Standalone mode, please see logs for error details.')
except Exception as ex:
log.exception(ex)
err_msg = _('Only Thin Client mode is available due to unknown Import Error.\n'
'To use Standalone mode, please see logs for error details.')
else:
component.start()
return
def on_dialog_response(response):
"""User response to switching mode dialog."""
if response == RESPONSE_YES:
# Turning off standalone
self.config['standalone'] = False
self.__start_thinclient()
self._start_thinclient()
else:
# User want keep Standalone Mode so just quit.
self.mainwindow.quit()
try:
try:
client.start_standalone()
except DaemonRunningError:
d = YesNoDialog(
_('Switch to Thin Client Mode?'),
_('A Deluge daemon process (deluged) is already running. '
'To use Standalone mode, stop this daemon and restart Deluge.'
'\n\n'
'Continue in Thin Client mode?')).run()
d.addCallback(on_dialog_response)
except ImportError as ex:
if 'No module named libtorrent' in ex.message:
d = YesNoDialog(
_('Switch to Thin Client Mode?'),
_('Only Thin Client mode is available because libtorrent is not installed.'
'\n\n'
'To use Deluge Standalone mode, please install libtorrent.')).run()
d.addCallback(on_dialog_response)
else:
raise ex
else:
component.start()
return
except Exception:
import traceback
tb = sys.exc_info()
ed = ErrorDialog(
_('Error Starting Core'),
_('An error occurred starting the core component required to run Deluge in Standalone mode.'
'\n\n'
'Please see the details below for more information.'), details=traceback.format_exc(tb[2])).run()
# An error occurred so ask user to switch from Standalone to Thin Client mode.
err_msg += '\n\n' + _('Continue in Thin Client mode?')
d = YesNoDialog(_('Change User Interface Mode'), err_msg).run()
d.addCallback(on_dialog_response)
def on_ed_response(response):
d = YesNoDialog(
_('Switch to Thin Client Mode?'),
_('Unable to start Standalone mode would you like to continue in Thin Client mode?')
).run()
d.addCallback(on_dialog_response)
ed.addCallback(on_ed_response)
else:
def _start_thinclient(self):
"""Start the gtkui in thinclient mode"""
if log.isEnabledFor(logging.DEBUG):
self.rpc_stats.start(10)
self.__start_thinclient()
def __start_thinclient(self):
# Check to see if we need to start the localhost daemon
if self.config['autostart_localhost']:
port = 0
for host_config in self.connectionmanager.hostlist.config['hosts']:
if host_config[1] in self.connectionmanager.LOCALHOST:
port = host_config[2]
log.debug('Autostarting localhost: %s', host_config[0:3])
if port:
self.connectionmanager.start_daemon(port, get_config_dir())
# Autoconnect to a host
if self.config['autoconnect']:
def update_connection_manager():
if not self.connectionmanager.running:
return
self.connectionmanager.builder.get_object('button_refresh').emit('clicked')
def close_connection_manager():
if not self.connectionmanager.running:
return
self.connectionmanager.builder.get_object('button_close').emit('clicked')
for host_config in self.connectionmanager.config['hosts']:
hostid, host, port, user, passwd = host_config
if hostid == self.config['autoconnect_host_id']:
try_connect = True
# Check to see if we need to start the localhost daemon
if self.config['autostart_localhost'] and host in ('localhost', '127.0.0.1'):
log.debug('Autostarting localhost:%s', host)
try_connect = client.start_daemon(
port, get_config_dir()
)
log.debug('Localhost started: %s', try_connect)
if not try_connect:
ErrorDialog(
_('Error Starting Daemon'),
_('There was an error starting the daemon '
'process. Try running it from a console '
'to see if there is an error.')
).run()
# Daemon Started, let's update it's info
reactor.callLater(0.5, update_connection_manager)
def on_connect(connector):
component.start()
reactor.callLater(0.2, update_connection_manager)
reactor.callLater(0.5, close_connection_manager)
def on_connect_fail(reason, try_counter,
host, port, user, passwd):
if not try_counter:
return
if reason.check(AuthenticationRequired, BadLoginError):
log.debug('PasswordRequired exception')
dialog = AuthenticationDialog(reason.value.message, reason.value.username)
def dialog_finished(response_id, host, port):
if response_id == RESPONSE_OK:
reactor.callLater(
0.5, do_connect, try_counter - 1,
host, port, dialog.get_username(),
dialog.get_password())
dialog.run().addCallback(dialog_finished, host, port)
return
log.info('Connection to host failed..')
log.info('Retrying connection.. Retries left: '
'%s', try_counter)
reactor.callLater(0.5, update_connection_manager)
reactor.callLater(0.5, do_connect, try_counter - 1,
host, port, user, passwd)
def do_connect(try_counter, host, port, user, passwd):
log.debug('Trying to connect to %s@%s:%s',
user, host, port)
d = client.connect(host, port, user, passwd)
d.addCallback(on_connect)
d.addErrback(on_connect_fail, try_counter,
host, port, user, passwd)
if try_connect:
reactor.callLater(
0.5, do_connect, 6, host, port, user, passwd
)
for host_config in self.connectionmanager.hostlist.config['hosts']:
host_id, host, port, user, __ = host_config
if host_id == self.config['autoconnect_host_id']:
log.debug('Trying to connect to %s@%s:%s', user, host, port)
reactor.callLater(0.3, self.connectionmanager._connect, host_id, try_counter=6)
break
if self.config['show_connection_manager_on_start']:
if windows_check():
# Call to simulate() required to workaround showing daemon status (see #2813)
reactor.simulate()
# Dialog is blocking so call last.
self.connectionmanager.show()
def __on_disconnect(self):

View file

@ -7,19 +7,19 @@
# 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 twisted.internet import defer
from deluge.common import get_localhost_auth
from deluge.config import Config
from deluge.configmanager import get_config_dir
from deluge.ui.client import Client, client
log = logging.getLogger(__name__)
@ -29,45 +29,12 @@ LOCALHOST = ('127.0.0.1', 'localhost')
def default_hostlist():
"""Create a new hosts for hostlist with a localhost entry"""
"""Create a new hosts key 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.
@ -84,25 +51,25 @@ def validate_host_info(hostname, port):
except gaierror as ex:
raise ValueError('Host %s: %s', hostname, ex.args[1])
try:
int(port)
except ValueError:
if not isinstance(port, int):
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
"""Mirgrates old hostlist config files to new format"""
localclient_username, localclient_password = get_localhost_auth()
if not localclient_username:
# Nothing to do here, there's no auth file
return
for idx, (__, host, __, username, __) in enumerate(config['hosts'][:]):
if host in LOCALHOST and not username:
config['hosts'][idx][3] = localclient_username
config['hosts'][idx][4] = localclient_password
return config
class HostList(object):
"""This class contains methods for adding, removing and looking up hosts in hostlist.conf."""
def __init__(self):
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)
@ -152,31 +119,87 @@ class HostList(object):
def get_host_info(self, host_id):
"""Get the host details for host_id.
Includes password details!
Args:
host_id (str): The host id to get info on.
Returns:
list: A list of (host_id, hostname, port, username).
"""
for host_entry in self.config['hosts']:
if host_entry[0] == host_id:
return host_entry
return host_entry[0:4]
else:
return []
def get_hosts_info(self):
"""Get all the hosts in the hostlist
"""Get information of all the hosts in the hostlist.
Returns:
list of lists: Host information in the format [(host_id, hostname, port, username)].
Excluding password details.
"""
return [host[0:4 + 1] for host in self.config['hosts']]
return [host_entry[0:4] for host_entry in self.config['hosts']]
def get_hosts_info2(self):
"""Get all the hosts in the hostlist
def get_host_status(self, host_id):
"""Gets the current status (online/offline) of the host
Args:
host_id (str): The host id to check status of.
Returns:
tuple: A tuple of strings (host_id, status, version).
Excluding password details.
"""
return [host for host in self.config['hosts']]
status_offline = (host_id, 'Offline', '')
def on_connect(result, c, host_id):
"""Successfully connected to a daemon"""
def on_info(info, c):
c.disconnect()
return host_id, 'Online', info
def on_info_fail(reason, c):
c.disconnect()
return status_offline
return c.daemon.info().addCallback(on_info, c).addErrback(on_info_fail, c)
def on_connect_failed(reason, host_id):
"""Connection to daemon failed"""
log.debug('Host status failed for %s: %s', host_id, reason)
return status_offline
try:
host_id, host, port, user = self.get_host_info(host_id)
except ValueError:
log.warning('Problem getting host_id info from hostlist')
return status_offline
try:
ip = gethostbyname(host)
except gaierror as ex:
log.error('Error resolving host %s to ip: %s', host, ex.args[1])
return status_offline
host_conn_info = (ip, port, 'localclient' if not user and host in LOCALHOST else user)
if client.connected() and host_conn_info == client.connection_info():
# Currently connected to host_id daemon.
def on_info(info, host_id):
log.debug('Client connected, query info: %s', info)
return host_id, 'Connected', info
return client.daemon.info().addCallback(on_info, host_id)
else:
# Attempt to connect to daemon with host_id details.
c = Client()
d = c.connect(host, port, skip_authentication=True)
d.addCallback(on_connect, c, host_id)
d.addErrback(on_connect_failed, host_id)
return d
def update_host(self, host_id, hostname, port, username, password):
"""Update the host with new details.
"""Update the supplied host id with new connection details.
Args:
host_id (str): The host id to update.
@ -192,13 +215,23 @@ class HostList(object):
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']:
for idx, host_entry in enumerate(self.config['hosts']):
if host_id == host_entry[0]:
host_entry = host_id, hostname, port, username, password
self.config['hosts'][idx] = host_id, hostname, port, username, password
self.config.save()
return True
return False
def remove_host(self, host_id):
"""Removes the host entry from hostlist config.
Args:
host_id (str): The host id to remove.
Returns:
bool: True is successfully removed, False otherwise.
"""
for host_entry in self.config['hosts']:
if host_id == host_entry[0]:
self.config['hosts'].remove(host_entry)
@ -209,3 +242,12 @@ class HostList(object):
def add_default_host(self):
self.add_host(DEFAULT_HOST, DEFAULT_PORT, *get_localhost_auth())
def connect_host(self, host_id):
"""Connect to host daemon"""
for host_entry in self.config['hosts']:
if host_entry[0] == host_id:
__, host, port, username, password = host_entry
return client.connect(host, port, username, password)
return defer.fail(Exception('Bad host id'))

View file

@ -78,7 +78,7 @@ Deluge.AddConnectionWindow = Ext.extend(Ext.Window, {
onAddClick: function() {
var values = this.form.getForm().getValues();
deluge.client.web.add_host(values.host, values.port, values.username, values.password, {
deluge.client.web.add_host(values.host, Number(values.port), values.username, values.password, {
success: function(result) {
if (!result[0]) {
Ext.MessageBox.show({

View file

@ -91,6 +91,13 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
iconCls: 'icon-add',
handler: this.onAddClick,
scope: this
}, {
id: 'cm-edit',
cls: 'x-btn-text-icon',
text: _('Edit'),
iconCls: 'icon-edit',
handler: this.onEditClick,
scope: this
}, {
id: 'cm-remove',
cls: 'x-btn-text-icon',
@ -164,27 +171,25 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
var button = this.buttons[1], status = record.get('status');
// Update the Connect/Disconnect button
if (status == 'Connected') {
button.enable();
button.enable();
if (status.toLowerCase() == 'connected') {
button.setText(_('Disconnect'));
} else if (status == 'Offline') {
button.disable();
} else {
button.enable();
button.setText(_('Connect'));
if (status.toLowerCase() != 'online') button.disable();
}
// Update the Stop/Start Daemon button
if (status == 'Offline') {
if (status.toLowerCase() == 'connected' || status.toLowerCase() == 'online') {
this.stopHostButton.enable();
this.stopHostButton.setText(_('Stop Daemon'));
} else {
if (record.get('host') == '127.0.0.1' || record.get('host') == 'localhost') {
this.stopHostButton.enable();
this.stopHostButton.setText(_('Start Daemon'));
} else {
this.stopHostButton.disable();
}
} else {
this.stopHostButton.enable();
this.stopHostButton.setText(_('Stop Daemon'));
}
},
@ -192,13 +197,25 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
onAddClick: function(button, e) {
if (!this.addWindow) {
this.addWindow = new Deluge.AddConnectionWindow();
this.addWindow.on('hostadded', this.onHostAdded, this);
this.addWindow.on('hostadded', this.onHostChange, this);
}
this.addWindow.show();
},
// private
onHostAdded: function() {
onEditClick: function(button, e) {
var connection = this.list.getSelectedRecords()[0];
if (!connection) return;
if (!this.editWindow) {
this.editWindow = new Deluge.EditConnectionWindow();
this.editWindow.on('hostedited', this.onHostChange, this);
}
this.editWindow.show(connection);
},
// private
onHostChange: function() {
this.loadHosts();
},
@ -212,7 +229,7 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
var selected = this.list.getSelectedRecords()[0];
if (!selected) return;
if (selected.get('status') == 'Connected') {
if (selected.get('status').toLowerCase() == 'connected') {
deluge.client.web.disconnect({
success: function(result) {
this.update(this);
@ -248,8 +265,8 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
// private
onGetHostStatus: function(host) {
var record = this.list.getStore().getById(host[0]);
record.set('status', host[3])
record.set('version', host[4])
record.set('status', host[1])
record.set('version', host[2])
record.commit();
if (this.list.getSelectedRecords()[0] == record) this.updateButtons(record);
},
@ -312,11 +329,13 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
// private
onSelectionChanged: function(list, selections) {
if (selections[0]) {
this.editHostButton.enable();
this.removeHostButton.enable();
this.stopHostButton.enable();
this.stopHostButton.setText(_('Stop Daemon'));
this.updateButtons(this.list.getRecord(selections[0]));
} else {
this.editHostButton.disable();
this.removeHostButton.disable();
this.stopHostButton.disable();
}
@ -328,6 +347,7 @@ Deluge.ConnectionManager = Ext.extend(Ext.Window, {
if (!this.addHostButton) {
var bbar = this.panel.getBottomToolbar();
this.addHostButton = bbar.items.get('cm-add');
this.editHostButton = bbar.items.get('cm-edit');
this.removeHostButton = bbar.items.get('cm-remove');
this.stopHostButton = bbar.items.get('cm-stop');
}

View file

@ -0,0 +1,114 @@
/*!
* Deluge.EditConnectionWindow.js
*
* Copyright (c) Damien Churchill 2009-2010 <damoxc@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.
*/
Ext.ns('Deluge');
/**
* @class Deluge.EditConnectionWindow
* @extends Ext.Window
*/
Deluge.EditConnectionWindow = Ext.extend(Ext.Window, {
title: _('Edit Connection'),
iconCls: 'x-deluge-add-window-icon',
layout: 'fit',
width: 300,
height: 195,
constrainHeader: true,
bodyStyle: 'padding: 10px 5px;',
closeAction: 'hide',
initComponent: function() {
Deluge.EditConnectionWindow.superclass.initComponent.call(this);
this.addEvents('hostedited');
this.addButton(_('Close'), this.hide, this);
this.addButton(_('Edit'), this.onEditClick, this);
this.on('hide', this.onHide, this);
this.form = this.add({
xtype: 'form',
defaultType: 'textfield',
baseCls: 'x-plain',
labelWidth: 60,
items: [{
fieldLabel: _('Host:'),
labelSeparator : '',
name: 'host',
anchor: '75%',
value: ''
}, {
xtype: 'spinnerfield',
fieldLabel: _('Port:'),
labelSeparator : '',
name: 'port',
strategy: {
xtype: 'number',
decimalPrecision: 0,
minValue: 0,
maxValue: 65535
},
anchor: '40%',
value: 58846
}, {
fieldLabel: _('Username:'),
labelSeparator : '',
name: 'username',
anchor: '75%',
value: ''
}, {
fieldLabel: _('Password:'),
labelSeparator : '',
anchor: '75%',
name: 'password',
inputType: 'password',
value: ''
}]
});
},
show: function(connection) {
Deluge.EditConnectionWindow.superclass.show.call(this);
this.form.getForm().findField('host').setValue(connection.get('host'));
this.form.getForm().findField('port').setValue(connection.get('port'));
this.form.getForm().findField('username').setValue(connection.get('user'));
this.host_id = connection.id
},
onEditClick: function() {
var values = this.form.getForm().getValues();
deluge.client.web.edit_host(this.host_id, values.host, Number(values.port), values.username, values.password, {
success: function(result) {
if (!result) {
console.log(result)
Ext.MessageBox.show({
title: _('Error'),
msg: String.format(_('Unable to edit host')),
buttons: Ext.MessageBox.OK,
modal: false,
icon: Ext.MessageBox.ERROR,
iconCls: 'x-deluge-icon-error'
});
} else {
this.fireEvent('hostedited');
}
this.hide();
},
scope: this
});
},
onHide: function() {
this.form.getForm().reset();
}
});

View file

@ -164,7 +164,8 @@ class JSON(resource.Resource, component.Component):
except AuthError:
error = {'message': 'Not authenticated', 'code': 1}
except Exception as ex:
log.error('Error calling method `%s`', method)
log.error('Error calling method `%s`: %s', method, ex)
log.exception(ex)
error = {'message': '%s: %s' % (ex.__class__.__name__, str(ex)), 'code': 3}
return request_id, result, error
@ -404,11 +405,10 @@ class WebApi(JSONComponent):
self.sessionproxy.stop()
return defer.succeed(True)
def _connect_daemon(self, host='localhost', port=58846, username='', password=''):
def _connect_daemon(self, host_id):
"""
Connects the client to a daemon
"""
d = client.connect(host, port, username, password)
def on_client_connected(connection_id):
"""
@ -420,7 +420,7 @@ class WebApi(JSONComponent):
self.start()
return d
return d.addCallback(on_client_connected)
return self.hostlist.connect_host(host_id).addCallback(on_client_connected)
@export
def connect(self, host_id):
@ -432,10 +432,7 @@ class WebApi(JSONComponent):
:returns: the methods the daemon supports
:rtype: list
"""
host = self._get_host(host_id)
if host:
return self._connect_daemon(*host[1:])
return defer.fail(Exception('Bad host id'))
return self._connect_daemon(host_id)
@export
def connected(self):
@ -716,7 +713,7 @@ class WebApi(JSONComponent):
Return the hosts in the hostlist.
"""
log.debug('get_hosts called')
return self.hostlist.get_hosts() + ['']
return self.hostlist.get_hosts_info()
@export
def get_host_status(self, host_id):
@ -725,46 +722,13 @@ class WebApi(JSONComponent):
:param host_id: the hash id of the host
:type host_id: string
"""
def response(status, info=None):
return host_id, host, port, status, info
def response(result):
log.critical('%s', result)
return result
try:
host_id, host, port, user, password = self._get_host(host_id)
except TypeError:
host = None
port = None
return response('Offline')
def on_connect(connected, c, host_id):
def on_info(info, c):
c.disconnect()
return response('Online', info)
def on_info_fail(reason, c):
c.disconnect()
return response('Offline')
if not connected:
return response('Offline')
return c.daemon.info().addCallback(on_info, c).addErrback(on_info_fail, c)
def on_connect_failed(reason, host_id):
return response('Offline')
if client.connected() and (host, port, 'localclient' if not
user and host in ('127.0.0.1', 'localhost') else
user) == client.connection_info():
def on_info(info):
return response('Connected', info)
return client.daemon.info().addCallback(on_info)
else:
c = Client()
d = c.connect(host, port, user, password)
d.addCallback(on_connect, c, host_id).addErrback(on_connect_failed, host_id)
return d
return self.hostlist.get_host_status(host_id).addCallback(response)
@export
def add_host(self, host, port, username='', password=''):
@ -787,15 +751,33 @@ class WebApi(JSONComponent):
else:
return True, host_id
@export
def edit_host(self, host_id, host, port, username='', password=''):
"""Edit host details in the hostlist.
Args:
host_id (str): The host identifying hash.
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:
bool: True if succesful, False otherwise.
"""
return self.hostlist.update_host(host_id, host, port, username, password)
@export
def remove_host(self, host_id):
"""Removes a host from the list.
"""Removes a host from the hostlist.
Args:
host_id (str): The host identifying hash.
Returns:
bool: True if succesful, False otherwise.
"""
return self.hostlist.remove_host(host_id)