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.
This commit is contained in:
Pedro Algarvio 2011-04-24 17:38:35 +01:00
parent 6ed3136c8e
commit 43e3fe2a1a
8 changed files with 661 additions and 182 deletions

View File

@ -2,6 +2,7 @@
# authmanager.py # authmanager.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -40,7 +41,7 @@ import logging
import deluge.component as component import deluge.component as component
import deluge.configmanager as configmanager import deluge.configmanager as configmanager
from deluge.error import BadLoginError, AuthenticationRequired from deluge.error import AuthManagerError, AuthenticationRequired, BadLoginError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -51,21 +52,35 @@ AUTH_LEVEL_ADMIN = 10
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL 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): class Account(object):
__slots__ = ('username', 'password', 'auth_level') __slots__ = ('username', 'password', 'authlevel')
def __init__(self, username, password, auth_level): def __init__(self, username, password, authlevel):
self.username = username self.username = username
self.password = password self.password = password
self.auth_level = auth_level self.authlevel = authlevel
def data(self, include_private=True): def data(self):
rv = self.__dict__.copy() return {
if not include_private: 'username': self.username,
rv['password'] = '' 'password': self.password,
return rv 'authlevel': AUTH_LEVELS_MAPPING_REVERSE[self.authlevel],
'authlevel_int': self.authlevel
}
def __repr__(self): def __repr__(self):
return ('<Account username="%(username)s" auth_level=%(auth_level)s>' % return ('<Account username="%(username)s" authlevel=%(authlevel)s>' %
self.__dict__) self.__dict__)
@ -104,52 +119,71 @@ class AuthManager(component.Component):
"Username and Password are required.", username "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: if self.__auth[username].password == password:
# Return the users auth level # Return the users auth level
return self.__auth[username].auth_level return self.__auth[username].authlevel
elif not password and self.__auth[username].password: elif not password and self.__auth[username].password:
raise AuthenticationRequired("Password is required", username) raise AuthenticationRequired("Password is required", username)
else: 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. Returns a list of known deluge usernames.
""" """
self.__load_auth_file() self.__load_auth_file()
rv = {} return [account.data() for account in self.__auth.values()]
for account in self.__auth.items():
rv[account.username] = account.data(include_private_data)
return rv
def create_account(self, username, password='', auth_level=AUTH_LEVEL_DEFAULT): def create_account(self, username, password, authlevel):
if username in self.__auth: if username in self.__auth:
raise Something() raise AuthManagerError("Username in use.", username)
self.__create_account(username, password, auth_level) 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): def update_account(self, username, password, authlevel):
if username in self.__auth: if username not in self.__auth:
raise Something() raise AuthManagerError("Username not known", username)
self.__create_account(username, password, auth_level) 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): def remove_account(self, username):
if username in self.__auth: if username not in self.__auth:
raise Something() 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] del self.__auth[username]
self.write_auth_file() self.write_auth_file()
if component.get("RPCServer").get_session_user() == username: return True
# Force a client logout by the server
component.get("RPCServer").logout_current_session()
def write_auth_file(self): def write_auth_file(self):
old_auth_file = configmanager.get_config_dir("auth") old_auth_file = configmanager.get_config_dir("auth")
new_auth_file = old_auth_file + '.new' new_auth_file = old_auth_file + '.new'
fd = open(new_auth_file, "w") fd = open(new_auth_file, "w")
for account in self.__auth.items(): for account in self.__auth.values():
fd.write( fd.write(
"%(username)s:%(password)s:%(auth_level)s\n" % account.__dict__ "%(username)s:%(password)s:%(authlevel_int)s\n" % account.data()
) )
fd.flush() fd.flush()
os.fsync(fd.fileno()) os.fsync(fd.fileno())
@ -157,18 +191,6 @@ class AuthManager(component.Component):
os.rename(new_auth_file, old_auth_file) os.rename(new_auth_file, old_auth_file)
self.__load_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): def __create_localclient_account(self):
""" """
Returns the string. Returns the string.
@ -217,23 +239,27 @@ class AuthManager(component.Component):
log.warning("Your auth entry for %s contains no auth level, " log.warning("Your auth entry for %s contains no auth level, "
"using AUTH_LEVEL_DEFAULT(%s)..", username, "using AUTH_LEVEL_DEFAULT(%s)..", username,
AUTH_LEVEL_DEFAULT) AUTH_LEVEL_DEFAULT)
auth_level = AUTH_LEVEL_DEFAULT authlevel = AUTH_LEVEL_DEFAULT
elif len(lsplit) == 3: elif len(lsplit) == 3:
username, password, auth_level = lsplit username, password, authlevel = lsplit
else: else:
log.error("Your auth file is malformed: Incorrect number of fields!") log.error("Your auth file is malformed: "
"Incorrect number of fields!")
continue continue
username = username.strip() username = username.strip()
password = password.strip() password = password.strip()
try: try:
auth_level = int(auth_level) authlevel = int(authlevel)
except ValueError: except ValueError:
log.error("Your auth file is malformed: %r is not a valid auth " try:
"level" % auth_level) authlevel = AUTH_LEVELS_MAPPING[authlevel]
except KeyError:
log.error("Your auth file is malformed: %r is not a valid auth "
"level" % authlevel)
continue continue
self.__auth[username] = Account(username, password, auth_level) self.__auth[username] = Account(username, password, authlevel)
if "localclient" not in self.__auth: if "localclient" not in self.__auth:
self.__create_localclient_account() self.__create_localclient_account()

View File

@ -2,6 +2,7 @@
# core.py # core.py
# #
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -43,8 +44,6 @@ import threading
import tempfile import tempfile
from urlparse import urljoin from urlparse import urljoin
from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall
import twisted.web.client import twisted.web.client
import twisted.web.error import twisted.web.error
@ -55,7 +54,8 @@ import deluge.common
import deluge.component as component import deluge.component as component
from deluge.event import * from deluge.event import *
from deluge.error 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.torrentmanager import TorrentManager
from deluge.core.pluginmanager import PluginManager from deluge.core.pluginmanager import PluginManager
from deluge.core.alertmanager import AlertManager from deluge.core.alertmanager import AlertManager
@ -77,7 +77,8 @@ class Core(component.Component):
log.info("Starting libtorrent %s session..", lt.version) log.info("Starting libtorrent %s session..", lt.version)
# Create the client fingerprint # 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: while len(version) < 4:
version.append(0) version.append(0)
@ -147,16 +148,16 @@ class Core(component.Component):
def __save_session_state(self): def __save_session_state(self):
"""Saves the libtorrent session state""" """Saves the libtorrent session state"""
try: try:
open(deluge.configmanager.get_config_dir("session.state"), "wb").write( session_state = deluge.configmanager.get_config_dir("session.state")
lt.bencode(self.session.state())) open(session_state, "wb").write(lt.bencode(self.session.state()))
except Exception, e: except Exception, e:
log.warning("Failed to save lt state: %s", e) log.warning("Failed to save lt state: %s", e)
def __load_session_state(self): def __load_session_state(self):
"""Loads the libtorrent session state""" """Loads the libtorrent session state"""
try: try:
self.session.load_state(lt.bdecode( session_state = deluge.configmanager.get_config_dir("session.state")
open(deluge.configmanager.get_config_dir("session.state"), "rb").read())) self.session.load_state(lt.bdecode(open(session_state, "rb").read()))
except Exception, e: except Exception, e:
log.warning("Failed to load lt state: %s", e) log.warning("Failed to load lt state: %s", e)
@ -212,7 +213,9 @@ class Core(component.Component):
log.exception(e) log.exception(e)
try: 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: except Exception, e:
log.error("There was an error adding the torrent file %s", filename) log.error("There was an error adding the torrent file %s", filename)
log.exception(e) log.exception(e)
@ -245,16 +248,23 @@ class Core(component.Component):
os.remove(filename) os.remove(filename)
except Exception, e: except Exception, e:
log.warning("Couldn't remove temp file: %s", 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): def on_download_fail(failure):
if failure.check(twisted.web.error.PageRedirect): if failure.check(twisted.web.error.PageRedirect):
new_url = urljoin(url, failure.getErrorMessage().split(" to ")[1]) 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) result.addCallbacks(on_download_success, on_download_fail)
elif failure.check(twisted.web.client.PartialDownloadError): elif failure.check(twisted.web.client.PartialDownloadError):
result = download_file(url, tempfile.mkstemp()[1], headers=headers, force_filename=True, result = download_file(
allow_compression=False) url, tempfile.mkstemp()[1], headers=headers,
force_filename=True, allow_compression=False
)
result.addCallbacks(on_download_success, on_download_fail) result.addCallbacks(on_download_success, on_download_fail)
else: else:
# Log the error and pass the failure onto the client # Log the error and pass the failure onto the client
@ -263,7 +273,9 @@ class Core(component.Component):
result = failure result = failure
return result 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) d.addCallbacks(on_download_success, on_download_fail)
return d return d
@ -829,9 +841,22 @@ class Core(component.Component):
""" """
return lt.version return lt.version
@export(AUTH_LEVEL_DEFAULT) @export(AUTH_LEVEL_ADMIN)
def get_known_accounts(self): def get_known_accounts(self):
auth_level = component.get("RPCServer").get_session_auth_level() return self.authmanager.get_known_accounts()
return self.authmanager.get_known_accounts(
include_private_data=(auth_level==AUTH_LEVEL_ADMIN) @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)

View File

@ -2,6 +2,7 @@
# error.py # error.py
# #
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -52,10 +53,9 @@ class InvalidPathError(DelugeError):
class NotAuthorizedError(DelugeError): class NotAuthorizedError(DelugeError):
pass pass
class BadLoginError(DelugeError):
pass
class AuthenticationRequired(BadLoginError): class _UsernameBasedException(DelugeError):
def _get_message(self): def _get_message(self):
return self._message return self._message
def _set_message(self, message): def _set_message(self, message):
@ -70,9 +70,17 @@ class AuthenticationRequired(BadLoginError):
username = property(_get_username, _set_username) username = property(_get_username, _set_username)
del _get_username, _set_username del _get_username, _set_username
def __init__(self, message, username): def __init__(self, message, username):
super(AuthenticationRequired, self).__init__(message) super(_UsernameBasedException, self).__init__(message)
self.message = message self.message = message
self.username = username self.username = username
class BadLoginError(_UsernameBasedException):
pass
class AuthenticationRequired(BadLoginError):
pass
class AuthManagerError(_UsernameBasedException):
pass

View File

@ -2,6 +2,7 @@
# client.py # client.py
# #
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -76,6 +77,20 @@ class DelugeRPCError(object):
self.exception_msg = exception_msg self.exception_msg = exception_msg
self.traceback = traceback 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): class DelugeRPCRequest(object):
""" """
This object is created whenever there is a RPCRequest to be sent to the 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) return (self.request_id, self.method, self.args, self.kwargs)
class DelugeRPCProtocol(Protocol): class DelugeRPCProtocol(Protocol):
def connectionMade(self): def connectionMade(self):
self.__rpc_requests = {} self.__rpc_requests = {}
self.__buffer = None self.__buffer = None
@ -273,6 +289,9 @@ class DaemonSSLProxy(DaemonProxy):
self.disconnect_deferred = None self.disconnect_deferred = None
self.disconnect_callback = None self.disconnect_callback = None
self.auth_levels_mapping = None
self.auth_levels_mapping_reverse = None
def connect(self, host, port): def connect(self, host, port):
""" """
Connects to a daemon at host:port Connects to a daemon at host:port
@ -402,21 +421,8 @@ class DaemonSSLProxy(DaemonProxy):
except: except:
pass pass
# Get the DelugeRPCError object from the error_data if error_data.value.exception_type != 'AuthManagerError':
error = error_data.value log.error(error_data.value.logable())
# 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)
return error_data return error_data
def __on_connect(self, result): def __on_connect(self, result):
@ -452,13 +458,24 @@ class DaemonSSLProxy(DaemonProxy):
self.authentication_level = result self.authentication_level = result
# We need to tell the daemon what events we're interested in receiving # We need to tell the daemon what events we're interested in receiving
if self.__factory.event_handlers: 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) self.login_deferred.callback(result)
def __on_login_fail(self, result): def __on_login_fail(self, result):
log.debug("_on_login_fail(): %s", result) log.debug("_on_login_fail(): %s", result)
self.login_deferred.errback(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): def set_disconnect_callback(self, cb):
""" """
Set a function to be called when the connection to the daemon is lost 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.host = "localhost"
self.port = 58846 self.port = 58846
# Running in classic mode, it's safe to import auth level # 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.username = "localclient"
self.authentication_level = AUTH_LEVEL_ADMIN 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 # Register the event handlers
for event in event_handlers: for event in event_handlers:
for handler in event_handlers[event]: for handler in event_handlers[event]:
@ -776,5 +797,13 @@ class Client(object):
""" """
return self._daemon_proxy.authentication_level 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 # This is the object clients will use
client = Client() client = Client()

View File

@ -176,7 +176,7 @@ class ErrorDialog(BaseDialog):
details = tb details = tb
if details: if details:
self.set_default_size(500, 400) self.set_default_size(600, 400)
textview = gtk.TextView() textview = gtk.TextView()
textview.set_editable(False) textview.set_editable(False)
textview.get_buffer().set_text(details) textview.get_buffer().set_text(details)
@ -245,3 +245,87 @@ class AuthenticationDialog(BaseDialog):
def on_password_activate(self, widget): def on_password_activate(self, widget):
self.response(gtk.RESPONSE_OK) 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(_("<b>Username:</b>"))
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(_("<b>Authentication Level:</b>"))
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(_("<b>Password:</b>"))
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

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?> <?xml version="1.0" encoding="UTF-8"?>
<glade-interface> <glade-interface>
<!-- interface-requires gtk+ 2.12 --> <!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual --> <!-- interface-naming-policy toplevel-contextual -->
@ -11,13 +11,11 @@
<property name="default_height">530</property> <property name="default_height">530</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property> <property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<signal name="delete_event" handler="on_pref_dialog_delete_event"/> <signal name="delete_event" handler="on_pref_dialog_delete_event"/>
<child internal-child="vbox"> <child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox1"> <widget class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<child> <child>
<widget class="GtkHPaned" id="hpaned1"> <widget class="GtkHPaned" id="hpaned1">
@ -50,7 +48,7 @@
<property name="show_tabs">False</property> <property name="show_tabs">False</property>
<property name="scrollable">True</property> <property name="scrollable">True</property>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow1"> <widget class="GtkScrolledWindow" id="DownloadsScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -66,7 +64,6 @@
<widget class="GtkVBox" id="vbox1"> <widget class="GtkVBox" id="vbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label21"> <widget class="GtkLabel" id="label21">
<property name="visible">True</property> <property name="visible">True</property>
@ -294,7 +291,7 @@
<widget class="GtkEntry" id="entry_torrents_path"> <widget class="GtkEntry" id="entry_torrents_path">
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="invisible_char">&#x25CF;</property> <property name="invisible_char"></property>
</widget> </widget>
<packing> <packing>
<property name="position">1</property> <property name="position">1</property>
@ -501,7 +498,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow2"> <widget class="GtkScrolledWindow" id="NetworkScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -517,7 +514,6 @@
<widget class="GtkVBox" id="vbox2"> <widget class="GtkVBox" id="vbox2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label22"> <widget class="GtkLabel" id="label22">
<property name="visible">True</property> <property name="visible">True</property>
@ -557,7 +553,6 @@
<child> <child>
<widget class="GtkVBox" id="vbox3"> <widget class="GtkVBox" id="vbox3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkHBox" id="hbox2"> <widget class="GtkHBox" id="hbox2">
<property name="visible">True</property> <property name="visible">True</property>
@ -755,7 +750,6 @@
<child> <child>
<widget class="GtkVBox" id="vbox25"> <widget class="GtkVBox" id="vbox25">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_random_outgoing_ports"> <widget class="GtkCheckButton" id="chk_random_outgoing_ports">
@ -880,7 +874,7 @@
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">Enter the IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default.</property> <property name="tooltip" translatable="yes">Enter the IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default.</property>
<property name="max_length">60</property> <property name="max_length">60</property>
<property name="invisible_char">&#x25CF;</property> <property name="invisible_char"></property>
<property name="width_chars">30</property> <property name="width_chars">30</property>
</widget> </widget>
<packing> <packing>
@ -927,7 +921,6 @@
<child> <child>
<widget class="GtkVBox" id="vbox4"> <widget class="GtkVBox" id="vbox4">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkHBox" id="hbox4"> <widget class="GtkHBox" id="hbox4">
<property name="visible">True</property> <property name="visible">True</property>
@ -1127,7 +1120,6 @@
<widget class="GtkVBox" id="vbox10"> <widget class="GtkVBox" id="vbox10">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label7"> <widget class="GtkLabel" id="label7">
<property name="visible">True</property> <property name="visible">True</property>
@ -1159,7 +1151,6 @@
<widget class="GtkVBox" id="vbox12"> <widget class="GtkVBox" id="vbox12">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkComboBox" id="combo_encin"> <widget class="GtkComboBox" id="combo_encin">
<property name="visible">True</property> <property name="visible">True</property>
@ -1193,7 +1184,6 @@ Either</property>
<widget class="GtkVBox" id="vbox15"> <widget class="GtkVBox" id="vbox15">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkHBox" id="hbox15"> <widget class="GtkHBox" id="hbox15">
<property name="visible">True</property> <property name="visible">True</property>
@ -1291,7 +1281,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow3"> <widget class="GtkScrolledWindow" id="BandwidthScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -1307,7 +1297,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox7"> <widget class="GtkVBox" id="vbox7">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label23"> <widget class="GtkLabel" id="label23">
<property name="visible">True</property> <property name="visible">True</property>
@ -1347,7 +1336,6 @@ Disabled</property>
<child> <child>
<widget class="GtkVBox" id="vbox21"> <widget class="GtkVBox" id="vbox21">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
<widget class="GtkTable" id="table1"> <widget class="GtkTable" id="table1">
@ -1361,7 +1349,7 @@ Disabled</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9999 1 10 0</property> <property name="adjustment">0 -1 9999 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
<packing> <packing>
@ -1377,7 +1365,7 @@ Disabled</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9999 1 10 0</property> <property name="adjustment">0 -1 9999 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
<packing> <packing>
@ -1456,7 +1444,7 @@ Disabled</property>
<property name="tooltip" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property>
<property name="max_length">4</property> <property name="max_length">4</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="climb_rate">1</property> <property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property> <property name="snap_to_ticks">True</property>
<property name="numeric">True</property> <property name="numeric">True</property>
@ -1488,7 +1476,7 @@ Disabled</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="tooltip" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 60000 1 10 0</property> <property name="adjustment">0 -1 60000 1 10 0</property>
<property name="climb_rate">1</property> <property name="climb_rate">1</property>
<property name="digits">1</property> <property name="digits">1</property>
<property name="numeric">True</property> <property name="numeric">True</property>
@ -1507,7 +1495,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 60000 1 10 0</property> <property name="adjustment">0 -1 60000 1 10 0</property>
<property name="climb_rate">1</property> <property name="climb_rate">1</property>
<property name="digits">1</property> <property name="digits">1</property>
<property name="numeric">True</property> <property name="numeric">True</property>
@ -1526,7 +1514,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="climb_rate">1</property> <property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property> <property name="snap_to_ticks">True</property>
<property name="numeric">True</property> <property name="numeric">True</property>
@ -1631,7 +1619,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum upload slots per torrent. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum upload slots per torrent. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="climb_rate">1</property> <property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property> <property name="snap_to_ticks">True</property>
<property name="numeric">True</property> <property name="numeric">True</property>
@ -1650,7 +1638,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="snap_to_ticks">True</property> <property name="snap_to_ticks">True</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
@ -1716,7 +1704,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="digits">1</property> <property name="digits">1</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
@ -1734,7 +1722,7 @@ Disabled</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property> <property name="tooltip" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">-1 -1 9000 1 10 0</property> <property name="adjustment">0 -1 9000 1 10 0</property>
<property name="digits">1</property> <property name="digits">1</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
@ -1790,7 +1778,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow4"> <widget class="GtkScrolledWindow" id="InterfaceScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -1806,7 +1794,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox8"> <widget class="GtkVBox" id="vbox8">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label25"> <widget class="GtkLabel" id="label25">
<property name="visible">True</property> <property name="visible">True</property>
@ -1892,7 +1879,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox27"> <widget class="GtkVBox" id="vbox27">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_show_rate_in_title"> <widget class="GtkCheckButton" id="chk_show_rate_in_title">
<property name="label" translatable="yes">Show session speed in titlebar</property> <property name="label" translatable="yes">Show session speed in titlebar</property>
@ -1943,7 +1929,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox5"> <widget class="GtkVBox" id="vbox5">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_show_dialog"> <widget class="GtkCheckButton" id="chk_show_dialog">
<property name="label" translatable="yes">Always show</property> <property name="label" translatable="yes">Always show</property>
@ -2013,7 +1998,6 @@ Disabled</property>
<child> <child>
<widget class="GtkVBox" id="vbox17"> <widget class="GtkVBox" id="vbox17">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_use_tray"> <widget class="GtkCheckButton" id="chk_use_tray">
<property name="label" translatable="yes">Enable system tray icon</property> <property name="label" translatable="yes">Enable system tray icon</property>
@ -2196,7 +2180,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow7"> <widget class="GtkScrolledWindow" id="OtherScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -2212,7 +2196,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox16"> <widget class="GtkVBox" id="vbox16">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label40"> <widget class="GtkLabel" id="label40">
<property name="visible">True</property> <property name="visible">True</property>
@ -2254,7 +2237,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox18"> <widget class="GtkVBox" id="vbox18">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkAlignment" id="alignment36"> <widget class="GtkAlignment" id="alignment36">
<property name="visible">True</property> <property name="visible">True</property>
@ -2316,7 +2298,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox19"> <widget class="GtkVBox" id="vbox19">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label44"> <widget class="GtkLabel" id="label44">
<property name="visible">True</property> <property name="visible">True</property>
@ -2392,7 +2373,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox29"> <widget class="GtkVBox" id="vbox29">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkAlignment" id="alignment50"> <widget class="GtkAlignment" id="alignment50">
<property name="visible">True</property> <property name="visible">True</property>
@ -2417,7 +2397,7 @@ Disabled</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="tooltip" translatable="yes">If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country.</property> <property name="tooltip" translatable="yes">If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country.</property>
<property name="invisible_char">&#x25CF;</property> <property name="invisible_char"></property>
</widget> </widget>
<packing> <packing>
<property name="position">1</property> <property name="position">1</property>
@ -2530,7 +2510,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow6"> <widget class="GtkScrolledWindow" id="DaemonScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -2546,7 +2526,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox13"> <widget class="GtkVBox" id="vbox13">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label34"> <widget class="GtkLabel" id="label34">
<property name="visible">True</property> <property name="visible">True</property>
@ -2588,7 +2567,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox14"> <widget class="GtkVBox" id="vbox14">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkHBox" id="hbox12"> <widget class="GtkHBox" id="hbox12">
<property name="visible">True</property> <property name="visible">True</property>
@ -2736,6 +2714,111 @@ Disabled</property>
<property name="position">4</property> <property name="position">4</property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkFrame" id="AccountsFrame">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<widget class="GtkAlignment" id="alignment33">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<widget class="GtkHBox" id="hbox22">
<property name="visible">True</property>
<child>
<widget class="GtkTreeView" id="accounts_listview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkVButtonBox" id="vbuttonbox1">
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<property name="layout_style">start</property>
<child>
<widget class="GtkButton" id="accounts_add">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_accounts_add_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="accounts_edit">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_accounts_edit_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="accounts_delete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_accounts_delete_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label61">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">0</property>
<property name="xpad">10</property>
<property name="ypad">10</property>
<property name="label" translatable="yes">&lt;b&gt;Accounts&lt;/b&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="position">5</property>
</packing>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>
@ -2757,7 +2840,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow8"> <widget class="GtkScrolledWindow" id="QueueScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -2773,7 +2856,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox9"> <widget class="GtkVBox" id="vbox9">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label46"> <widget class="GtkLabel" id="label46">
<property name="visible">True</property> <property name="visible">True</property>
@ -2803,7 +2885,6 @@ Disabled</property>
<widget class="GtkVBox" id="queue_prefs_box2"> <widget class="GtkVBox" id="queue_prefs_box2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
<widget class="GtkFrame" id="frame10"> <widget class="GtkFrame" id="frame10">
@ -2821,7 +2902,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox6"> <widget class="GtkVBox" id="vbox6">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_queue_new_top"> <widget class="GtkCheckButton" id="chk_queue_new_top">
<property name="label" translatable="yes">Queue new torrents to top</property> <property name="label" translatable="yes">Queue new torrents to top</property>
@ -2872,7 +2952,6 @@ Disabled</property>
<child> <child>
<widget class="GtkVBox" id="vbox20"> <widget class="GtkVBox" id="vbox20">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
<widget class="GtkTable" id="table3"> <widget class="GtkTable" id="table3">
@ -3024,7 +3103,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox11"> <widget class="GtkVBox" id="vbox11">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property> <property name="spacing">2</property>
<child> <child>
<widget class="GtkTable" id="table2"> <widget class="GtkTable" id="table2">
@ -3234,7 +3312,7 @@ Disabled</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow9"> <widget class="GtkScrolledWindow" id="ProxyScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -3250,7 +3328,6 @@ Disabled</property>
<widget class="GtkVBox" id="vbox22"> <widget class="GtkVBox" id="vbox22">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label83"> <widget class="GtkLabel" id="label83">
<property name="visible">True</property> <property name="visible">True</property>
@ -3278,7 +3355,6 @@ Disabled</property>
<child> <child>
<widget class="GtkVBox" id="vbox26"> <widget class="GtkVBox" id="vbox26">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property> <property name="spacing">5</property>
<child> <child>
<widget class="GtkFrame" id="frame23"> <widget class="GtkFrame" id="frame23">
@ -3372,7 +3448,7 @@ Disabled</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="adjustment">8080 0 65535 1 10 0</property> <property name="adjustment">100 0 65535 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
</child> </child>
@ -3556,7 +3632,7 @@ HTTP W/ Auth</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="adjustment">8080 0 65535 1 10 0</property> <property name="adjustment">100 0 65535 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
</child> </child>
@ -3740,7 +3816,7 @@ HTTP W/ Auth</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="adjustment">8080 0 65535 1 10 0</property> <property name="adjustment">100 0 65535 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
</child> </child>
@ -3927,7 +4003,7 @@ HTTP W/ Auth</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="adjustment">8080 0 65535 1 10 0</property> <property name="adjustment">100 0 65535 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
</widget> </widget>
</child> </child>
@ -4042,7 +4118,7 @@ HTTP W/ Auth</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow13"> <widget class="GtkScrolledWindow" id="CacheScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property> <property name="hscrollbar_policy">automatic</property>
@ -4055,7 +4131,6 @@ HTTP W/ Auth</property>
<child> <child>
<widget class="GtkVBox" id="vbox30"> <widget class="GtkVBox" id="vbox30">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label111"> <widget class="GtkLabel" id="label111">
<property name="visible">True</property> <property name="visible">True</property>
@ -4087,7 +4162,6 @@ HTTP W/ Auth</property>
<child> <child>
<widget class="GtkVBox" id="vbox31"> <widget class="GtkVBox" id="vbox31">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkFrame" id="frame32"> <widget class="GtkFrame" id="frame32">
<property name="visible">True</property> <property name="visible">True</property>
@ -4132,9 +4206,9 @@ HTTP W/ Auth</property>
<widget class="GtkSpinButton" id="spin_cache_size"> <widget class="GtkSpinButton" id="spin_cache_size">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property> <property name="invisible_char"></property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">512 0 99999 1 10 0</property> <property name="adjustment">100 0 99999 1 10 0</property>
<property name="numeric">True</property> <property name="numeric">True</property>
<property name="update_policy">if-valid</property> <property name="update_policy">if-valid</property>
</widget> </widget>
@ -4149,7 +4223,7 @@ HTTP W/ Auth</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="max_length">5</property> <property name="max_length">5</property>
<property name="invisible_char">&#x25CF;</property> <property name="invisible_char"></property>
<property name="width_chars">5</property> <property name="width_chars">5</property>
<property name="xalign">1</property> <property name="xalign">1</property>
<property name="adjustment">60 1 32000 1 10 0</property> <property name="adjustment">60 1 32000 1 10 0</property>
@ -4197,7 +4271,6 @@ HTTP W/ Auth</property>
<child> <child>
<widget class="GtkVBox" id="vbox32"> <widget class="GtkVBox" id="vbox32">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkFrame" id="frame34"> <widget class="GtkFrame" id="frame34">
<property name="visible">True</property> <property name="visible">True</property>
@ -4590,7 +4663,7 @@ HTTP W/ Auth</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow5"> <widget class="GtkScrolledWindow" id="PluginsScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -4606,7 +4679,6 @@ HTTP W/ Auth</property>
<widget class="GtkVBox" id="vbox28"> <widget class="GtkVBox" id="vbox28">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkLabel" id="label51"> <widget class="GtkLabel" id="label51">
<property name="visible">True</property> <property name="visible">True</property>
@ -4637,7 +4709,6 @@ HTTP W/ Auth</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow12"> <widget class="GtkScrolledWindow" id="scrolledwindow12">
<property name="visible">True</property> <property name="visible">True</property>
@ -4996,7 +5067,7 @@ HTTP W/ Auth</property>
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="position">9</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child> <child>

View File

@ -2,6 +2,7 @@
# menubar.py # menubar.py
# #
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -212,7 +213,9 @@ class MenuBar(component.Component):
self.menuitem_change_owner.set_visible(False) self.menuitem_change_owner.set_visible(False)
# Get Known accounts to allow chaning ownership # 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): def stop(self):
log.debug("MenuBar stopping") log.debug("MenuBar stopping")
@ -354,9 +357,9 @@ class MenuBar(component.Component):
def show_move_storage_dialog(self, status): def show_move_storage_dialog(self, status):
log.debug("show_move_storage_dialog") log.debug("show_move_storage_dialog")
glade = gtk.glade.XML( glade = gtk.glade.XML(pkg_resources.resource_filename(
pkg_resources.resource_filename("deluge.ui.gtkui", "deluge.ui.gtkui", "glade/move_storage_dialog.glade"
"glade/move_storage_dialog.glade")) ))
dialog = glade.get_widget("move_storage_dialog") dialog = glade.get_widget("move_storage_dialog")
dialog.set_transient_for(self.window.window) dialog.set_transient_for(self.window.window)
entry = glade.get_widget("entry_destination") entry = glade.get_widget("entry_destination")
@ -439,10 +442,21 @@ class MenuBar(component.Component):
} }
# widget: (header, type_str, image_stockid, image_filename, default) # widget: (header, type_str, image_stockid, image_filename, default)
other_dialog_info = { other_dialog_info = {
"menuitem_down_speed": (_("Set Maximum Download Speed"), "KiB/s", None, "downloading.svg", -1.0), "menuitem_down_speed": (
"menuitem_up_speed": (_("Set Maximum Upload Speed"), "KiB/s", None, "seeding.svg", -1.0), _("Set Maximum Download Speed"),
"menuitem_max_connections": (_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1), "KiB/s", None, "downloading.svg", -1.0
"menuitem_upload_slots": (_("Set Maximum Upload Slots"), "", gtk.STOCK_SORT_ASCENDING, None, -1) ),
"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 # Show the other dialog
@ -483,7 +497,15 @@ class MenuBar(component.Component):
getattr(self.window.main_glade.get_widget(item), attr)() getattr(self.window.main_glade.get_widget(item), attr)()
def _on_known_accounts(self, known_accounts): 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: if len(known_accounts) <= 1:
return return
@ -496,14 +518,18 @@ class MenuBar(component.Component):
self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup) self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup)
for account in known_accounts: 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) 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.show_all()
self.change_owner_submenu_items[None].set_active(True) self.change_owner_submenu_items[None].set_active(True)
self.change_owner_submenu_items[None].hide() 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) self.menuitem_change_owner.set_submenu(self.change_owner_submenu)
def _on_known_accounts_fail(self, reason): def _on_known_accounts_fail(self, reason):
@ -517,18 +543,18 @@ class MenuBar(component.Component):
return return
torrent_owner = component.get("TorrentView").get_torrent_status(selected[0])["owner"] torrent_owner = component.get("TorrentView").get_torrent_status(selected[0])["owner"]
for account, item in self.change_owner_submenu_items.iteritems(): for username, item in self.change_owner_submenu_items.iteritems():
item.set_active(account == torrent_owner) 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") log.debug("_on_change_owner_toggled")
update_torrents = [] update_torrents = []
selected = component.get("TorrentView").get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents()
for torrent_id in selected: for torrent_id in selected:
torrent_status = component.get("TorrentView").get_torrent_status(torrent_id) 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) update_torrents.append(torrent_id)
if update_torrents: if update_torrents:
log.debug("Setting torrent owner \"%s\" on %s", account, update_torrents) log.debug("Setting torrent owner \"%s\" on %s", username, update_torrents)
client.core.set_torrents_owner(update_torrents, account) client.core.set_torrents_owner(update_torrents, username)

View File

@ -2,6 +2,7 @@
# preferences.py # preferences.py
# #
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <ufs@ufsoft.org>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -46,11 +47,14 @@ from deluge.ui.client import client
import deluge.common import deluge.common
import deluge.error import deluge.error
import common import common
import dialogs
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
import deluge.configmanager import deluge.configmanager
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = range(3)
class Preferences(component.Component): class Preferences(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "Preferences") component.Component.__init__(self, "Preferences")
@ -81,6 +85,35 @@ class Preferences(component.Component):
self.liststore.append([i, category]) self.liststore.append([i, category])
i += 1 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 # Setup plugin tab listview
self.plugin_liststore = gtk.ListStore(str, bool) self.plugin_liststore = gtk.ListStore(str, bool)
self.plugin_liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) 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 # Connect to the 'changed' event of TreeViewSelection to get selection
# changes. # changes.
self.treeview.get_selection().connect("changed", self.treeview.get_selection().connect(
self.on_selection_changed) "changed", self.on_selection_changed
)
self.plugin_listview.get_selection().connect("changed", self.plugin_listview.get_selection().connect(
self.on_plugin_selection_changed) "changed", self.on_plugin_selection_changed
)
self.glade.signal_autoconnect({ self.glade.signal_autoconnect({
"on_pref_dialog_delete_event": self.on_pref_dialog_delete_event, "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_find_plugins_clicked": self._on_button_find_plugins_clicked,
"on_button_cache_refresh_clicked": self._on_button_cache_refresh_clicked, "on_button_cache_refresh_clicked": self._on_button_cache_refresh_clicked,
"on_combo_proxy_type_changed": self._on_combo_proxy_type_changed, "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 # These get updated by requests done to the core
@ -191,9 +229,12 @@ class Preferences(component.Component):
component.get("PluginManager").run_on_show_prefs() component.get("PluginManager").run_on_show_prefs()
# Update the preferences dialog to reflect current config settings # Update the preferences dialog to reflect current config settings
self.core_config = {} self.core_config = {}
if client.connected(): if client.connected():
self._get_accounts_tab_data()
def _on_get_config(config): def _on_get_config(config):
self.core_config = config self.core_config = config
client.core.get_available_plugins().addCallback(_on_get_available_plugins) 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. # Show the correct notebook page based on what row is selected.
(model, row) = treeselection.get_selected() (model, row) = treeselection.get_selected()
try: 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)) self.notebook.set_current_page(model.get_value(row, 0))
except TypeError: except TypeError:
pass pass
@ -961,3 +1005,169 @@ class Preferences(component.Component):
def _on_button_associate_magnet_clicked(self, widget): def _on_button_associate_magnet_clicked(self, widget):
common.associate_magnet_links(True) 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)