diff --git a/deluge/common.py b/deluge/common.py
index 2d71df04a..4efb1af2d 100644
--- a/deluge/common.py
+++ b/deluge/common.py
@@ -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
diff --git a/deluge/tests/common_web.py b/deluge/tests/common_web.py
index 1ca5fb68d..3f93f3b66 100644
--- a/deluge/tests/common_web.py
+++ b/deluge/tests/common_web.py
@@ -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]
diff --git a/deluge/tests/test_authmanager.py b/deluge/tests/test_authmanager.py
index 2d25d0f33..e62ce9364 100644
--- a/deluge/tests/test_authmanager.py
+++ b/deluge/tests/test_authmanager.py
@@ -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
)
diff --git a/deluge/tests/test_client.py b/deluge/tests/test_client.py
index 84d7fd9f6..a5a85ff7c 100644
--- a/deluge/tests/test_client.py
+++ b/deluge/tests/test_client.py
@@ -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
diff --git a/deluge/tests/test_rpcserver.py b/deluge/tests/test_rpcserver.py
index dc74f850e..0afcfd63b 100644
--- a/deluge/tests/test_rpcserver.py
+++ b/deluge/tests/test_rpcserver.py
@@ -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
diff --git a/deluge/tests/test_ui_entry.py b/deluge/tests/test_ui_entry.py
index 272a523e9..e3d69ddea 100644
--- a/deluge/tests/test_ui_entry.py
+++ b/deluge/tests/test_ui_entry.py
@@ -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()
diff --git a/deluge/tests/test_web_api.py b/deluge/tests/test_web_api.py
index 6fb7d1008..7baabc056 100644
--- a/deluge/tests/test_web_api.py
+++ b/deluge/tests/test_web_api.py
@@ -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]))
diff --git a/deluge/ui/client.py b/deluge/ui/client.py
index 564bf625d..172a6d68b 100644
--- a/deluge/ui/client.py
+++ b/deluge/ui/client.py
@@ -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):
"""
diff --git a/deluge/ui/console/modes/connectionmanager.py b/deluge/ui/console/modes/connectionmanager.py
index 705279f8b..cb53e28be 100644
--- a/deluge/ui/console/modes/connectionmanager.py
+++ b/deluge/ui/console/modes/connectionmanager.py
@@ -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):
diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py
index 21dcc0f9c..cfffaa29c 100644
--- a/deluge/ui/gtkui/connectionmanager.py
+++ b/deluge/ui/gtkui/connectionmanager.py
@@ -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)
diff --git a/deluge/ui/gtkui/glade/connection_manager.addhost.ui b/deluge/ui/gtkui/glade/connection_manager.addhost.ui
index 21972260b..595465f3e 100644
--- a/deluge/ui/gtkui/glade/connection_manager.addhost.ui
+++ b/deluge/ui/gtkui/glade/connection_manager.addhost.ui
@@ -13,7 +13,7 @@
5
Add Host
True
- center
+ center-on-parent
True
dialog
@@ -43,14 +43,14 @@
False
@@ -58,20 +58,6 @@
1
-
-
-
- False
- False
- 2
-
-
False
@@ -114,7 +100,7 @@
False
True
True
-
+
@@ -253,15 +239,11 @@
2
-
-
-
button_addhost_cancel
- button_addhost_add
- button_addhost_save
+ button_addhost_add
diff --git a/deluge/ui/gtkui/glade/connection_manager.askpassword.ui b/deluge/ui/gtkui/glade/connection_manager.askpassword.ui
deleted file mode 100644
index 6025ac018..000000000
--- a/deluge/ui/gtkui/glade/connection_manager.askpassword.ui
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
diff --git a/deluge/ui/gtkui/glade/connection_manager.ui b/deluge/ui/gtkui/glade/connection_manager.ui
index d69b78015..af12310c4 100644
--- a/deluge/ui/gtkui/glade/connection_manager.ui
+++ b/deluge/ui/gtkui/glade/connection_manager.ui
@@ -46,10 +46,11 @@
automatic
automatic
-
+
80
True
True
+ liststore_hostlist
@@ -242,12 +243,12 @@
end
- gtk-close
+ _Close
False
True
True
True
- True
+ True
@@ -258,12 +259,12 @@
- gtk-connect
+ C_onnect
False
True
True
True
- True
+ True
@@ -303,6 +304,7 @@
True
False
True
+
True
@@ -318,6 +320,7 @@
True
False
True
+
True
@@ -333,6 +336,7 @@
True
False
True
+
True
@@ -368,4 +372,22 @@
button_connect
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py
index 708356cca..c40dfa41c 100644
--- a/deluge/ui/gtkui/gtkui.py
+++ b/deluge/ui/gtkui/gtkui.py
@@ -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):
diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py
index 5eb43889e..90cd17a9c 100644
--- a/deluge/ui/hostlist.py
+++ b/deluge/ui/hostlist.py
@@ -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'))
diff --git a/deluge/ui/web/js/deluge-all/AddConnectionWindow.js b/deluge/ui/web/js/deluge-all/AddConnectionWindow.js
index a72b81706..92256c693 100644
--- a/deluge/ui/web/js/deluge-all/AddConnectionWindow.js
+++ b/deluge/ui/web/js/deluge-all/AddConnectionWindow.js
@@ -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({
diff --git a/deluge/ui/web/js/deluge-all/ConnectionManager.js b/deluge/ui/web/js/deluge-all/ConnectionManager.js
index b6ac56973..1e807be52 100644
--- a/deluge/ui/web/js/deluge-all/ConnectionManager.js
+++ b/deluge/ui/web/js/deluge-all/ConnectionManager.js
@@ -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');
}
diff --git a/deluge/ui/web/js/deluge-all/EditConnectionWindow.js b/deluge/ui/web/js/deluge-all/EditConnectionWindow.js
new file mode 100644
index 000000000..07b883236
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/EditConnectionWindow.js
@@ -0,0 +1,114 @@
+/*!
+ * Deluge.EditConnectionWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010
+ *
+ * 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();
+ }
+});
diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py
index 62ccd7709..d31c778f1 100644
--- a/deluge/ui/web/json_api.py
+++ b/deluge/ui/web/json_api.py
@@ -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)