From 43e3fe2a1a366a0312e976a0b4133853ad385840 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sun, 24 Apr 2011 17:38:35 +0100 Subject: [PATCH] Account Management Implemented. Account management is now implemented for the GTK UI. Some changes to the core were required since the clients need to know which authentication levels exist, and, to expose account creation, update, and removal to the clients. The best effort is done to try not to compromise the auth file. --- deluge/core/authmanager.py | 128 ++++++---- deluge/core/core.py | 63 +++-- deluge/error.py | 18 +- deluge/ui/client.py | 63 +++-- deluge/ui/gtkui/dialogs.py | 86 ++++++- .../ui/gtkui/glade/preferences_dialog.glade | 203 ++++++++++------ deluge/ui/gtkui/menubar.py | 62 +++-- deluge/ui/gtkui/preferences.py | 220 +++++++++++++++++- 8 files changed, 661 insertions(+), 182 deletions(-) diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py index 7c2d5c1d2..bed8ec0c2 100644 --- a/deluge/core/authmanager.py +++ b/deluge/core/authmanager.py @@ -2,6 +2,7 @@ # authmanager.py # # Copyright (C) 2009 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -40,7 +41,7 @@ import logging import deluge.component as component import deluge.configmanager as configmanager -from deluge.error import BadLoginError, AuthenticationRequired +from deluge.error import AuthManagerError, AuthenticationRequired, BadLoginError log = logging.getLogger(__name__) @@ -51,21 +52,35 @@ AUTH_LEVEL_ADMIN = 10 AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL +AUTH_LEVELS_MAPPING = { + 'NONE': AUTH_LEVEL_NONE, + 'READONLY': AUTH_LEVEL_READONLY, + 'DEFAULT': AUTH_LEVEL_NORMAL, + 'NORMAL': AUTH_LEVEL_DEFAULT, + 'ADMIN': AUTH_LEVEL_ADMIN +} + +AUTH_LEVELS_MAPPING_REVERSE = {} +for key, value in AUTH_LEVELS_MAPPING.iteritems(): + AUTH_LEVELS_MAPPING_REVERSE[value] = key + class Account(object): - __slots__ = ('username', 'password', 'auth_level') - def __init__(self, username, password, auth_level): + __slots__ = ('username', 'password', 'authlevel') + def __init__(self, username, password, authlevel): self.username = username self.password = password - self.auth_level = auth_level + self.authlevel = authlevel - def data(self, include_private=True): - rv = self.__dict__.copy() - if not include_private: - rv['password'] = '' - return rv + def data(self): + return { + 'username': self.username, + 'password': self.password, + 'authlevel': AUTH_LEVELS_MAPPING_REVERSE[self.authlevel], + 'authlevel_int': self.authlevel + } def __repr__(self): - return ('' % + return ('' % self.__dict__) @@ -104,52 +119,71 @@ class AuthManager(component.Component): "Username and Password are required.", username ) - self.__test_existing_account(username) + if username not in self.__auth: + # Let's try to re-load the file.. Maybe it's been updated + self.__load_auth_file() + if username not in self.__auth: + raise BadLoginError("Username does not exist", username) if self.__auth[username].password == password: # Return the users auth level - return self.__auth[username].auth_level + return self.__auth[username].authlevel elif not password and self.__auth[username].password: raise AuthenticationRequired("Password is required", username) else: - raise BadLoginError("Password does not match") + raise BadLoginError("Password does not match", username) - def get_known_accounts(self, include_private_data=False): + def get_known_accounts(self): """ Returns a list of known deluge usernames. """ self.__load_auth_file() - rv = {} - for account in self.__auth.items(): - rv[account.username] = account.data(include_private_data) - return rv + return [account.data() for account in self.__auth.values()] - def create_account(self, username, password='', auth_level=AUTH_LEVEL_DEFAULT): + def create_account(self, username, password, authlevel): if username in self.__auth: - raise Something() - self.__create_account(username, password, auth_level) + raise AuthManagerError("Username in use.", username) + try: + self.__auth[username] = Account(username, password, + AUTH_LEVELS_MAPPING[authlevel]) + self.write_auth_file() + return True + except Exception, err: + log.exception(err) + raise err - def update_account(self, username, password='', auth_level=AUTH_LEVEL_DEFAULT): - if username in self.__auth: - raise Something() - self.__create_account(username, password, auth_level) + def update_account(self, username, password, authlevel): + if username not in self.__auth: + raise AuthManagerError("Username not known", username) + try: + self.__auth[username].username = username + self.__auth[username].password = password + self.__auth[username].authlevel = AUTH_LEVELS_MAPPING[authlevel] + self.write_auth_file() + return True + except Exception, err: + log.exception(err) + raise err def remove_account(self, username): - if username in self.__auth: - raise Something() + if username not in self.__auth: + raise AuthManagerError("Username not known", username) + elif username == component.get("RPCServer").get_session_user(): + raise AuthManagerError( + "You cannot delete your own account while logged in!", username + ) + del self.__auth[username] self.write_auth_file() - if component.get("RPCServer").get_session_user() == username: - # Force a client logout by the server - component.get("RPCServer").logout_current_session() + return True def write_auth_file(self): old_auth_file = configmanager.get_config_dir("auth") new_auth_file = old_auth_file + '.new' fd = open(new_auth_file, "w") - for account in self.__auth.items(): + for account in self.__auth.values(): fd.write( - "%(username)s:%(password)s:%(auth_level)s\n" % account.__dict__ + "%(username)s:%(password)s:%(authlevel_int)s\n" % account.data() ) fd.flush() os.fsync(fd.fileno()) @@ -157,18 +191,6 @@ class AuthManager(component.Component): os.rename(new_auth_file, old_auth_file) self.__load_auth_file() - def __add_account(self, username, password, auth_level): - self.__auth[username] = Account(username, password, auth_level) - self.write_auth_file() - - def __test_existing_account(self, username): - if username not in self.__auth: - # Let's try to re-load the file.. Maybe it's been updated - self.__load_auth_file() - if username not in self.__auth: - raise BadLoginError("Username does not exist") - return True - def __create_localclient_account(self): """ Returns the string. @@ -217,23 +239,27 @@ class AuthManager(component.Component): log.warning("Your auth entry for %s contains no auth level, " "using AUTH_LEVEL_DEFAULT(%s)..", username, AUTH_LEVEL_DEFAULT) - auth_level = AUTH_LEVEL_DEFAULT + authlevel = AUTH_LEVEL_DEFAULT elif len(lsplit) == 3: - username, password, auth_level = lsplit + username, password, authlevel = lsplit else: - log.error("Your auth file is malformed: Incorrect number of fields!") + log.error("Your auth file is malformed: " + "Incorrect number of fields!") continue username = username.strip() password = password.strip() try: - auth_level = int(auth_level) + authlevel = int(authlevel) except ValueError: - log.error("Your auth file is malformed: %r is not a valid auth " - "level" % auth_level) + try: + authlevel = AUTH_LEVELS_MAPPING[authlevel] + except KeyError: + log.error("Your auth file is malformed: %r is not a valid auth " + "level" % authlevel) continue - self.__auth[username] = Account(username, password, auth_level) + self.__auth[username] = Account(username, password, authlevel) if "localclient" not in self.__auth: self.__create_localclient_account() diff --git a/deluge/core/core.py b/deluge/core/core.py index 225979730..6586fff05 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -2,6 +2,7 @@ # core.py # # Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -43,8 +44,6 @@ import threading import tempfile from urlparse import urljoin -from twisted.internet import reactor, defer -from twisted.internet.task import LoopingCall import twisted.web.client import twisted.web.error @@ -55,7 +54,8 @@ import deluge.common import deluge.component as component from deluge.event import * from deluge.error import * -from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT +from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE +from deluge.core.authmanager import AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager @@ -77,7 +77,8 @@ class Core(component.Component): log.info("Starting libtorrent %s session..", lt.version) # Create the client fingerprint - version = [int(value.split("-")[0]) for value in deluge.common.get_version().split(".")] + version = [int(value.split("-")[0]) for value in + deluge.common.get_version().split(".")] while len(version) < 4: version.append(0) @@ -147,16 +148,16 @@ class Core(component.Component): def __save_session_state(self): """Saves the libtorrent session state""" try: - open(deluge.configmanager.get_config_dir("session.state"), "wb").write( - lt.bencode(self.session.state())) + session_state = deluge.configmanager.get_config_dir("session.state") + open(session_state, "wb").write(lt.bencode(self.session.state())) except Exception, e: log.warning("Failed to save lt state: %s", e) def __load_session_state(self): """Loads the libtorrent session state""" try: - self.session.load_state(lt.bdecode( - open(deluge.configmanager.get_config_dir("session.state"), "rb").read())) + session_state = deluge.configmanager.get_config_dir("session.state") + self.session.load_state(lt.bdecode(open(session_state, "rb").read())) except Exception, e: log.warning("Failed to load lt state: %s", e) @@ -212,7 +213,9 @@ class Core(component.Component): log.exception(e) try: - torrent_id = self.torrentmanager.add(filedump=filedump, options=options, filename=filename) + torrent_id = self.torrentmanager.add( + filedump=filedump, options=options, filename=filename + ) except Exception, e: log.error("There was an error adding the torrent file %s", filename) log.exception(e) @@ -245,16 +248,23 @@ class Core(component.Component): os.remove(filename) except Exception, e: log.warning("Couldn't remove temp file: %s", e) - return self.add_torrent_file(filename, base64.encodestring(data), options) + return self.add_torrent_file( + filename, base64.encodestring(data), options + ) def on_download_fail(failure): if failure.check(twisted.web.error.PageRedirect): new_url = urljoin(url, failure.getErrorMessage().split(" to ")[1]) - result = download_file(new_url, tempfile.mkstemp()[1], headers=headers, force_filename=True) + result = download_file( + new_url, tempfile.mkstemp()[1], headers=headers, + force_filename=True + ) result.addCallbacks(on_download_success, on_download_fail) elif failure.check(twisted.web.client.PartialDownloadError): - result = download_file(url, tempfile.mkstemp()[1], headers=headers, force_filename=True, - allow_compression=False) + result = download_file( + url, tempfile.mkstemp()[1], headers=headers, + force_filename=True, allow_compression=False + ) result.addCallbacks(on_download_success, on_download_fail) else: # Log the error and pass the failure onto the client @@ -263,7 +273,9 @@ class Core(component.Component): result = failure return result - d = download_file(url, tempfile.mkstemp()[1], headers=headers, force_filename=True) + d = download_file( + url, tempfile.mkstemp()[1], headers=headers, force_filename=True + ) d.addCallbacks(on_download_success, on_download_fail) return d @@ -829,9 +841,22 @@ class Core(component.Component): """ return lt.version - @export(AUTH_LEVEL_DEFAULT) + @export(AUTH_LEVEL_ADMIN) def get_known_accounts(self): - auth_level = component.get("RPCServer").get_session_auth_level() - return self.authmanager.get_known_accounts( - include_private_data=(auth_level==AUTH_LEVEL_ADMIN) - ) + return self.authmanager.get_known_accounts() + + @export(AUTH_LEVEL_NONE) + def get_auth_levels_mappings(self): + return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE) + + @export(AUTH_LEVEL_ADMIN) + def create_account(self, username, password, authlevel): + return self.authmanager.create_account(username, password, authlevel) + + @export(AUTH_LEVEL_ADMIN) + def update_account(self, username, password, authlevel): + return self.authmanager.update_account(username, password, authlevel) + + @export(AUTH_LEVEL_ADMIN) + def remove_account(self, username): + return self.authmanager.remove_account(username) diff --git a/deluge/error.py b/deluge/error.py index 055d6b338..cdb67091a 100644 --- a/deluge/error.py +++ b/deluge/error.py @@ -2,6 +2,7 @@ # error.py # # Copyright (C) 2008 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -52,10 +53,9 @@ class InvalidPathError(DelugeError): class NotAuthorizedError(DelugeError): pass -class BadLoginError(DelugeError): - pass -class AuthenticationRequired(BadLoginError): +class _UsernameBasedException(DelugeError): + def _get_message(self): return self._message def _set_message(self, message): @@ -70,9 +70,17 @@ class AuthenticationRequired(BadLoginError): username = property(_get_username, _set_username) del _get_username, _set_username - def __init__(self, message, username): - super(AuthenticationRequired, self).__init__(message) + super(_UsernameBasedException, self).__init__(message) self.message = message self.username = username + +class BadLoginError(_UsernameBasedException): + pass + +class AuthenticationRequired(BadLoginError): + pass + +class AuthManagerError(_UsernameBasedException): + pass diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 26cef631f..049595383 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -2,6 +2,7 @@ # client.py # # Copyright (C) 2009 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -76,6 +77,20 @@ class DelugeRPCError(object): self.exception_msg = exception_msg self.traceback = traceback + def logable(self): + # Create a delugerpcrequest to print out a nice RPCRequest string + r = DelugeRPCRequest() + r.method = self.method + r.args = self.args + r.kwargs = self.kwargs + msg = "RPCError Message Received!" + msg += "\n" + "-" * 80 + msg += "\n" + "RPCRequest: " + r.__repr__() + msg += "\n" + "-" * 80 + msg += "\n" + self.traceback + "\n" + self.exception_type + ": " + self.exception_msg + msg += "\n" + "-" * 80 + return msg + class DelugeRPCRequest(object): """ This object is created whenever there is a RPCRequest to be sent to the @@ -118,6 +133,7 @@ class DelugeRPCRequest(object): return (self.request_id, self.method, self.args, self.kwargs) class DelugeRPCProtocol(Protocol): + def connectionMade(self): self.__rpc_requests = {} self.__buffer = None @@ -273,6 +289,9 @@ class DaemonSSLProxy(DaemonProxy): self.disconnect_deferred = None self.disconnect_callback = None + self.auth_levels_mapping = None + self.auth_levels_mapping_reverse = None + def connect(self, host, port): """ Connects to a daemon at host:port @@ -402,21 +421,8 @@ class DaemonSSLProxy(DaemonProxy): except: pass - # Get the DelugeRPCError object from the error_data - error = error_data.value - - # Create a delugerpcrequest to print out a nice RPCRequest string - r = DelugeRPCRequest() - r.method = error.method - r.args = error.args - r.kwargs = error.kwargs - msg = "RPCError Message Received!" - msg += "\n" + "-" * 80 - msg += "\n" + "RPCRequest: " + r.__repr__() - msg += "\n" + "-" * 80 - msg += "\n" + error.traceback + "\n" + error.exception_type + ": " + error.exception_msg - msg += "\n" + "-" * 80 - log.error(msg) + if error_data.value.exception_type != 'AuthManagerError': + log.error(error_data.value.logable()) return error_data def __on_connect(self, result): @@ -452,13 +458,24 @@ class DaemonSSLProxy(DaemonProxy): self.authentication_level = result # We need to tell the daemon what events we're interested in receiving if self.__factory.event_handlers: - self.call("daemon.set_event_interest", self.__factory.event_handlers.keys()) + self.call("daemon.set_event_interest", + self.__factory.event_handlers.keys()) + + self.call("core.get_auth_levels_mappings").addCallback( + self.__on_auth_levels_mappings + ) + self.login_deferred.callback(result) def __on_login_fail(self, result): log.debug("_on_login_fail(): %s", result) self.login_deferred.errback(result) + def __on_auth_levels_mappings(self, result): + auth_levels_mapping, auth_levels_mapping_reverse = result + self.auth_levels_mapping = auth_levels_mapping + self.auth_levels_mapping_reverse = auth_levels_mapping_reverse + def set_disconnect_callback(self, cb): """ Set a function to be called when the connection to the daemon is lost @@ -481,9 +498,13 @@ class DaemonClassicProxy(DaemonProxy): self.host = "localhost" self.port = 58846 # Running in classic mode, it's safe to import auth level - from deluge.core.authmanager import AUTH_LEVEL_ADMIN + from deluge.core.authmanager import (AUTH_LEVEL_ADMIN, + AUTH_LEVELS_MAPPING, + AUTH_LEVELS_MAPPING_REVERSE) self.username = "localclient" self.authentication_level = AUTH_LEVEL_ADMIN + self.auth_levels_mapping = AUTH_LEVELS_MAPPING + self.auth_levels_mapping_reverse = AUTH_LEVELS_MAPPING_REVERSE # Register the event handlers for event in event_handlers: for handler in event_handlers[event]: @@ -776,5 +797,13 @@ class Client(object): """ return self._daemon_proxy.authentication_level + @property + def auth_levels_mapping(self): + return self._daemon_proxy.auth_levels_mapping + + @property + def auth_levels_mapping_reverse(self): + return self._daemon_proxy.auth_levels_mapping_reverse + # This is the object clients will use client = Client() diff --git a/deluge/ui/gtkui/dialogs.py b/deluge/ui/gtkui/dialogs.py index 633ac3e64..d0c23471b 100644 --- a/deluge/ui/gtkui/dialogs.py +++ b/deluge/ui/gtkui/dialogs.py @@ -176,7 +176,7 @@ class ErrorDialog(BaseDialog): details = tb if details: - self.set_default_size(500, 400) + self.set_default_size(600, 400) textview = gtk.TextView() textview.set_editable(False) textview.get_buffer().set_text(details) @@ -245,3 +245,87 @@ class AuthenticationDialog(BaseDialog): def on_password_activate(self, widget): self.response(gtk.RESPONSE_OK) + +class AccountDialog(BaseDialog): + def __init__(self, username=None, password=None, authlevel=None, + levels_mapping=None, parent=None): + if username: + super(AccountDialog, self).__init__( + _("Edit Account"), + _("Edit existing account"), + gtk.STOCK_DIALOG_INFO, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_APPLY, gtk.RESPONSE_OK), + parent) + else: + super(AccountDialog, self).__init__( + _("New Account"), + _("Create a new account"), + gtk.STOCK_DIALOG_INFO, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_ADD, gtk.RESPONSE_OK), + parent) + + self.levels_mapping = levels_mapping + + table = gtk.Table(2, 3, False) + self.username_label = gtk.Label() + self.username_label.set_markup(_("Username:")) + self.username_label.set_alignment(1.0, 0.5) + self.username_label.set_padding(5, 5) + self.username_entry = gtk.Entry() + table.attach(self.username_label, 0, 1, 0, 1) + table.attach(self.username_entry, 1, 2, 0, 1) + + self.authlevel_label = gtk.Label() + self.authlevel_label.set_markup(_("Authentication Level:")) + self.authlevel_label.set_alignment(1.0, 0.5) + self.authlevel_label.set_padding(5, 5) + + self.authlevel_combo = gtk.combo_box_new_text() + active_idx = None + for idx, level in enumerate(levels_mapping.keys()): + self.authlevel_combo.append_text(level) + if authlevel and authlevel==level: + active_idx = idx + elif not authlevel and level == 'DEFAULT': + active_idx = idx + + print 'aidx', active_idx + if active_idx is not None: + self.authlevel_combo.set_active(active_idx) + + table.attach(self.authlevel_label, 0, 1, 1, 2) + table.attach(self.authlevel_combo, 1, 2, 1, 2) + + self.password_label = gtk.Label() + self.password_label.set_markup(_("Password:")) + self.password_label.set_alignment(1.0, 0.5) + self.password_label.set_padding(5, 5) + self.password_entry = gtk.Entry() + self.password_entry.set_visibility(False) + table.attach(self.password_label, 0, 1, 2, 3) + table.attach(self.password_entry, 1, 2, 2, 3) + + self.vbox.pack_start(table, False, False, padding=5) + if username: + self.username_entry.set_text(username) + self.username_entry.set_editable(False) + else: + self.set_focus(self.username_entry) + + if password: + self.password_entry.set_text(username) + + self.show_all() + + def get_username(self): + return self.username_entry.get_text() + + def get_password(self): + return self.password_entry.get_text() + + def get_authlevel(self): + combobox = self.authlevel_combo + level = combobox.get_model()[combobox.get_active()][0] + return level diff --git a/deluge/ui/gtkui/glade/preferences_dialog.glade b/deluge/ui/gtkui/glade/preferences_dialog.glade index 76c71ce58..9383846a0 100644 --- a/deluge/ui/gtkui/glade/preferences_dialog.glade +++ b/deluge/ui/gtkui/glade/preferences_dialog.glade @@ -1,4 +1,4 @@ - + @@ -11,13 +11,11 @@ 530 True dialog - False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 2 @@ -50,7 +48,7 @@ False True - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -66,7 +64,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -294,7 +291,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - + 1 @@ -501,7 +498,7 @@ - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -517,7 +514,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -557,7 +553,6 @@ True - vertical True @@ -755,7 +750,6 @@ True - vertical 5 @@ -880,7 +874,7 @@ True Enter the IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default. 60 - + 30 @@ -927,7 +921,6 @@ True - vertical True @@ -1127,7 +1120,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -1159,7 +1151,6 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -1193,7 +1184,6 @@ Either True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -1291,7 +1281,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1307,7 +1297,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -1347,7 +1336,6 @@ Disabled True - vertical 5 @@ -1361,7 +1349,7 @@ Disabled True True 1 - -1 -1 9999 1 10 0 + 0 -1 9999 1 10 0 True @@ -1377,7 +1365,7 @@ Disabled True True 1 - -1 -1 9999 1 10 0 + 0 -1 9999 1 10 0 True @@ -1456,7 +1444,7 @@ Disabled The maximum number of connections allowed. Set -1 for unlimited. 4 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 1 True True @@ -1488,7 +1476,7 @@ Disabled GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK The maximum download speed for all torrents. Set -1 for unlimited. 1 - -1 -1 60000 1 10 0 + 0 -1 60000 1 10 0 1 1 True @@ -1507,7 +1495,7 @@ Disabled True The maximum upload speed for all torrents. Set -1 for unlimited. 1 - -1 -1 60000 1 10 0 + 0 -1 60000 1 10 0 1 1 True @@ -1526,7 +1514,7 @@ Disabled True The maximum upload slots for all torrents. Set -1 for unlimited. 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 1 True True @@ -1631,7 +1619,7 @@ Disabled True The maximum upload slots per torrent. Set -1 for unlimited. 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 1 True True @@ -1650,7 +1638,7 @@ Disabled True The maximum number of connections per torrent. Set -1 for unlimited. 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 True True @@ -1716,7 +1704,7 @@ Disabled True The maximum number of connections per torrent. Set -1 for unlimited. 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 1 True @@ -1734,7 +1722,7 @@ Disabled True The maximum number of connections per torrent. Set -1 for unlimited. 1 - -1 -1 9000 1 10 0 + 0 -1 9000 1 10 0 1 True @@ -1790,7 +1778,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -1806,7 +1794,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -1892,7 +1879,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical Show session speed in titlebar @@ -1943,7 +1929,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical Always show @@ -2013,7 +1998,6 @@ Disabled True - vertical Enable system tray icon @@ -2196,7 +2180,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -2212,7 +2196,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2254,7 +2237,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2316,7 +2298,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2392,7 +2373,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2417,7 +2397,7 @@ Disabled True True If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country. - + 1 @@ -2530,7 +2510,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -2546,7 +2526,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2588,7 +2567,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2736,6 +2714,111 @@ Disabled 4 + + + True + 0 + none + + + True + 12 + + + True + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 0 + + + + + True + 5 + True + start + + + gtk-add + True + True + True + True + + + + False + False + 0 + + + + + gtk-edit + True + False + True + True + True + + + + False + False + 1 + + + + + gtk-delete + True + False + True + True + True + + + + False + False + 2 + + + + + False + 4 + 1 + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + 10 + 10 + <b>Accounts</b> + True + + + label_item + + + + + 5 + + @@ -2757,7 +2840,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -2773,7 +2856,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -2803,7 +2885,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 5 @@ -2821,7 +2902,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical Queue new torrents to top @@ -2872,7 +2952,6 @@ Disabled True - vertical 5 @@ -3024,7 +3103,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical 2 @@ -3234,7 +3312,7 @@ Disabled - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -3250,7 +3328,6 @@ Disabled True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -3278,7 +3355,6 @@ Disabled True - vertical 5 @@ -3372,7 +3448,7 @@ Disabled True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 65535 1 10 0 + 100 0 65535 1 10 0 True @@ -3556,7 +3632,7 @@ HTTP W/ Auth True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 65535 1 10 0 + 100 0 65535 1 10 0 True @@ -3740,7 +3816,7 @@ HTTP W/ Auth True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 65535 1 10 0 + 100 0 65535 1 10 0 True @@ -3927,7 +4003,7 @@ HTTP W/ Auth True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 8080 0 65535 1 10 0 + 100 0 65535 1 10 0 True @@ -4042,7 +4118,7 @@ HTTP W/ Auth - + True True automatic @@ -4055,7 +4131,6 @@ HTTP W/ Auth True - vertical True @@ -4087,7 +4162,6 @@ HTTP W/ Auth True - vertical True @@ -4132,9 +4206,9 @@ HTTP W/ Auth True True - + 1 - 512 0 99999 1 10 0 + 100 0 99999 1 10 0 True if-valid @@ -4149,7 +4223,7 @@ HTTP W/ Auth True True 5 - + 5 1 60 1 32000 1 10 0 @@ -4197,7 +4271,6 @@ HTTP W/ Auth True - vertical True @@ -4590,7 +4663,7 @@ HTTP W/ Auth - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -4606,7 +4679,6 @@ HTTP W/ Auth True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -4637,7 +4709,6 @@ HTTP W/ Auth True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical True @@ -4996,7 +5067,7 @@ HTTP W/ Auth - 9 + 2 diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 88b19c9a4..6026f1387 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -2,6 +2,7 @@ # menubar.py # # Copyright (C) 2007, 2008 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -212,7 +213,9 @@ class MenuBar(component.Component): self.menuitem_change_owner.set_visible(False) # Get Known accounts to allow chaning ownership - client.core.get_known_accounts().addCallback(self._on_known_accounts) + client.core.get_known_accounts().addCallback( + self._on_known_accounts).addErrback(self._on_known_accounts_fail + ) def stop(self): log.debug("MenuBar stopping") @@ -354,9 +357,9 @@ class MenuBar(component.Component): def show_move_storage_dialog(self, status): log.debug("show_move_storage_dialog") - glade = gtk.glade.XML( - pkg_resources.resource_filename("deluge.ui.gtkui", - "glade/move_storage_dialog.glade")) + glade = gtk.glade.XML(pkg_resources.resource_filename( + "deluge.ui.gtkui", "glade/move_storage_dialog.glade" + )) dialog = glade.get_widget("move_storage_dialog") dialog.set_transient_for(self.window.window) entry = glade.get_widget("entry_destination") @@ -439,10 +442,21 @@ class MenuBar(component.Component): } # widget: (header, type_str, image_stockid, image_filename, default) other_dialog_info = { - "menuitem_down_speed": (_("Set Maximum Download Speed"), "KiB/s", None, "downloading.svg", -1.0), - "menuitem_up_speed": (_("Set Maximum Upload Speed"), "KiB/s", None, "seeding.svg", -1.0), - "menuitem_max_connections": (_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1), - "menuitem_upload_slots": (_("Set Maximum Upload Slots"), "", gtk.STOCK_SORT_ASCENDING, None, -1) + "menuitem_down_speed": ( + _("Set Maximum Download Speed"), + "KiB/s", None, "downloading.svg", -1.0 + ), + "menuitem_up_speed": ( + _("Set Maximum Upload Speed"), + "KiB/s", None, "seeding.svg", -1.0 + ), + "menuitem_max_connections": ( + _("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1 + ), + "menuitem_upload_slots": ( + _("Set Maximum Upload Slots"), + "", gtk.STOCK_SORT_ASCENDING, None, -1 + ) } # Show the other dialog @@ -483,7 +497,15 @@ class MenuBar(component.Component): getattr(self.window.main_glade.get_widget(item), attr)() def _on_known_accounts(self, known_accounts): - log.debug("_on_known_accounts: %s", known_accounts) + known_accounts_to_log = [] + for account in known_accounts: + account_to_log = {} + for key, value in account.copy().iteritems(): + if key == 'password': + value = '*' * len(value) + account_to_log[key] = value + known_accounts_to_log.append(account_to_log) + log.debug("_on_known_accounts: %s", known_accounts_to_log) if len(known_accounts) <= 1: return @@ -496,14 +518,18 @@ class MenuBar(component.Component): self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup) for account in known_accounts: - self.change_owner_submenu_items[account] = item = gtk.RadioMenuItem(maingroup, account) + username = account["username"] + item = gtk.RadioMenuItem(maingroup, username) + self.change_owner_submenu_items[username] = item self.change_owner_submenu.append(item) - item.connect("toggled", self._on_change_owner_toggled, account) + item.connect("toggled", self._on_change_owner_toggled, username) self.change_owner_submenu.show_all() self.change_owner_submenu_items[None].set_active(True) self.change_owner_submenu_items[None].hide() - self.menuitem_change_owner.connect("activate", self._on_change_owner_submenu_active) + self.menuitem_change_owner.connect( + "activate", self._on_change_owner_submenu_active + ) self.menuitem_change_owner.set_submenu(self.change_owner_submenu) def _on_known_accounts_fail(self, reason): @@ -517,18 +543,18 @@ class MenuBar(component.Component): return torrent_owner = component.get("TorrentView").get_torrent_status(selected[0])["owner"] - for account, item in self.change_owner_submenu_items.iteritems(): - item.set_active(account == torrent_owner) + for username, item in self.change_owner_submenu_items.iteritems(): + item.set_active(username == torrent_owner) - def _on_change_owner_toggled(self, widget, account): + def _on_change_owner_toggled(self, widget, username): log.debug("_on_change_owner_toggled") update_torrents = [] selected = component.get("TorrentView").get_selected_torrents() for torrent_id in selected: torrent_status = component.get("TorrentView").get_torrent_status(torrent_id) - if torrent_status["owner"] != account: + if torrent_status["owner"] != username: update_torrents.append(torrent_id) if update_torrents: - log.debug("Setting torrent owner \"%s\" on %s", account, update_torrents) - client.core.set_torrents_owner(update_torrents, account) + log.debug("Setting torrent owner \"%s\" on %s", username, update_torrents) + client.core.set_torrents_owner(update_torrents, username) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index a13937a2d..508effa4a 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -2,6 +2,7 @@ # preferences.py # # Copyright (C) 2007, 2008 Andrew Resch +# Copyright (C) 2011 Pedro Algarvio # # Deluge is free software. # @@ -46,11 +47,14 @@ from deluge.ui.client import client import deluge.common import deluge.error import common +import dialogs from deluge.configmanager import ConfigManager import deluge.configmanager log = logging.getLogger(__name__) +ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = range(3) + class Preferences(component.Component): def __init__(self): component.Component.__init__(self, "Preferences") @@ -81,6 +85,35 @@ class Preferences(component.Component): self.liststore.append([i, category]) i += 1 + # Setup accounts tab lisview + self.accounts_levels_mapping = None + self.accounts_authlevel = self.glade.get_widget("accounts_authlevel") + self.accounts_liststore = gtk.ListStore(str, str, str, int) + self.accounts_liststore.set_sort_column_id(ACCOUNTS_USERNAME, + gtk.SORT_ASCENDING) + self.accounts_listview = self.glade.get_widget("accounts_listview") + self.accounts_listview.append_column( + gtk.TreeViewColumn( + _("Username"), gtk.CellRendererText(), text=ACCOUNTS_USERNAME + ) + ) + self.accounts_listview.append_column( + gtk.TreeViewColumn( + _("Level"), gtk.CellRendererText(), text=ACCOUNTS_LEVEL + ) + ) + password_column = gtk.TreeViewColumn( + 'password', gtk.CellRendererText(), text=ACCOUNTS_PASSWORD + ) + self.accounts_listview.append_column(password_column) + password_column.set_visible(False) + self.accounts_listview.set_model(self.accounts_liststore) + + self.accounts_listview.get_selection().connect( + "changed", self._on_accounts_selection_changed + ) + self.accounts_frame = self.glade.get_widget("AccountsFrame") + # Setup plugin tab listview self.plugin_liststore = gtk.ListStore(str, bool) self.plugin_liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) @@ -96,11 +129,13 @@ class Preferences(component.Component): # Connect to the 'changed' event of TreeViewSelection to get selection # changes. - self.treeview.get_selection().connect("changed", - self.on_selection_changed) + self.treeview.get_selection().connect( + "changed", self.on_selection_changed + ) - self.plugin_listview.get_selection().connect("changed", - self.on_plugin_selection_changed) + self.plugin_listview.get_selection().connect( + "changed", self.on_plugin_selection_changed + ) self.glade.signal_autoconnect({ "on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, @@ -114,7 +149,10 @@ class Preferences(component.Component): "on_button_find_plugins_clicked": self._on_button_find_plugins_clicked, "on_button_cache_refresh_clicked": self._on_button_cache_refresh_clicked, "on_combo_proxy_type_changed": self._on_combo_proxy_type_changed, - "on_button_associate_magnet_clicked": self._on_button_associate_magnet_clicked + "on_button_associate_magnet_clicked": self._on_button_associate_magnet_clicked, + "on_accounts_add_clicked": self._on_accounts_add_clicked, + "on_accounts_delete_clicked": self._on_accounts_delete_clicked, + "on_accounts_edit_clicked": self._on_accounts_edit_clicked }) # These get updated by requests done to the core @@ -191,9 +229,12 @@ class Preferences(component.Component): component.get("PluginManager").run_on_show_prefs() + # Update the preferences dialog to reflect current config settings self.core_config = {} if client.connected(): + self._get_accounts_tab_data() + def _on_get_config(config): self.core_config = config client.core.get_available_plugins().addCallback(_on_get_available_plugins) @@ -832,6 +873,9 @@ class Preferences(component.Component): # Show the correct notebook page based on what row is selected. (model, row) = treeselection.get_selected() try: + if model.get_value(row, 1) == _("Daemon"): + # Let's see update the accounts related stuff + self._get_accounts_tab_data() self.notebook.set_current_page(model.get_value(row, 0)) except TypeError: pass @@ -961,3 +1005,169 @@ class Preferences(component.Component): def _on_button_associate_magnet_clicked(self, widget): common.associate_magnet_links(True) + + + def _get_accounts_tab_data(self): + def on_ok(accounts): + self.accounts_frame.show() + self._on_get_known_accounts(accounts) + + def on_fail(failure): + if failure.value.exception_type == 'NotAuthorizedError': + self.accounts_frame.hide() + else: + dialogs.ErrorDialog( + _("Server Side Error"), + _("An error ocurred on the server"), + self.pref_dialog, details=failure.value.logable() + ).run() + client.core.get_known_accounts().addCallback(on_ok).addErrback(on_fail) + + def _on_get_known_accounts(self, known_accounts): + known_accounts_to_log = [] + for account in known_accounts: + account_to_log = {} + for key, value in account.copy().iteritems(): + if key == 'password': + value = '*' * len(value) + account_to_log[key] = value + known_accounts_to_log.append(account_to_log) + log.debug("_on_known_accounts: %s", known_accounts_to_log) + + self.accounts_liststore.clear() + + for account in known_accounts: + iter = self.accounts_liststore.append() + self.accounts_liststore.set_value( + iter, ACCOUNTS_USERNAME, account['username'] + ) + self.accounts_liststore.set_value( + iter, ACCOUNTS_LEVEL, account['authlevel'] + ) + self.accounts_liststore.set_value( + iter, ACCOUNTS_PASSWORD, account['password'] + ) + + def _on_accounts_selection_changed(self, treeselection): + log.debug("_on_accounts_selection_changed") + (model, itr) = treeselection.get_selected() + if not itr: + return + username = model[itr][0] + if username: + self.glade.get_widget("accounts_edit").set_sensitive(True) + self.glade.get_widget("accounts_delete").set_sensitive(True) + else: + self.glade.get_widget("accounts_edit").set_sensitive(False) + self.glade.get_widget("accounts_delete").set_sensitive(False) + + def _on_accounts_add_clicked(self, widget): + dialog = dialogs.AccountDialog( + levels_mapping=client.auth_levels_mapping, + parent=self.pref_dialog + ) + + def dialog_finished(response_id): + username = dialog.get_username() + password = dialog.get_password() + authlevel = dialog.get_authlevel() + + def add_ok(rv): + iter = self.accounts_liststore.append() + self.accounts_liststore.set_value( + iter, ACCOUNTS_USERNAME, username + ) + self.accounts_liststore.set_value( + iter, ACCOUNTS_LEVEL, authlevel + ) + self.accounts_liststore.set_value( + iter, ACCOUNTS_PASSWORD, password + ) + + def add_fail(failure): + if failure.value.exception_type == 'AuthManagerError': + dialogs.ErrorDialog( + _("Error Adding Account"), + failure.value.exception_msg + ).run() + else: + dialogs.ErrorDialog( + _("Error Adding Account"), + _("An error ocurred while adding account"), + self.pref_dialog, details=failure.value.logable() + ).run() + + if response_id == gtk.RESPONSE_OK: + client.core.create_account( + username, password, authlevel + ).addCallback(add_ok).addErrback(add_fail) + + dialog.run().addCallback(dialog_finished) + + def _on_accounts_edit_clicked(self, widget): + (model, itr) = self.accounts_listview.get_selection().get_selected() + if not itr: + return + + dialog = dialogs.AccountDialog( + model[itr][ACCOUNTS_USERNAME], + model[itr][ACCOUNTS_PASSWORD], + model[itr][ACCOUNTS_LEVEL], + levels_mapping=client.auth_levels_mapping, + parent=self.pref_dialog + ) + + def dialog_finished(response_id): + + def update_ok(rc): + model.set_value(itr, ACCOUNTS_PASSWORD, dialog.get_username()) + model.set_value(itr, ACCOUNTS_LEVEL, dialog.get_authlevel()) + + def update_fail(failure): + dialogs.ErrorDialog( + _("Error Updating Account"), + _("An error ocurred while updating account"), + self.pref_dialog, details=failure.value.logable() + ).run() + + if response_id == gtk.RESPONSE_OK: + client.core.update_account( + dialog.get_username(), + dialog.get_password(), + dialog.get_authlevel() + ).addCallback(update_ok).addErrback(update_fail) + + dialog.run().addCallback(dialog_finished) + + def _on_accounts_delete_clicked(self, widget): + (model, itr) = self.accounts_listview.get_selection().get_selected() + if not itr: + return + + username = model[itr][0] + header = _("Remove Account") + text = _("Are you sure you wan't do remove the account with the " + "username \"%(username)s\"?" % dict(username=username)) + dialog = dialogs.YesNoDialog(header, text, parent=self.pref_dialog) + + def dialog_finished(response_id): + def remove_ok(rc): + model.remove(itr) + + def remove_fail(failure): + if failure.value.exception_type == 'AuthManagerError': + dialogs.ErrorDialog( + _("Error Removing Account"), + failure.value.exception_msg + ).run() + else: + dialogs.ErrorDialog( + _("Error Removing Account"), + _("An error ocurred while removing account"), + self.pref_dialog, details=failure.value.logable() + ).run() + if response_id == gtk.RESPONSE_YES: + client.core.remove_account( + username + ).addCallback(remove_ok).addErrback(remove_fail) + dialog.run().addCallback(dialog_finished)