diff --git a/deluge/component.py b/deluge/component.py index e775e2a2d..bc6850a6f 100644 --- a/deluge/component.py +++ b/deluge/component.py @@ -111,6 +111,7 @@ class ComponentRegistry: if self.depend.has_key(name): for depend in self.depend[name]: self.start_component(depend) + # Only start if the component is stopped. if self.components[name].get_state() == \ COMPONENT_STATE.index("Stopped"): diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index f9b1bc07f..f9196baba 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -25,7 +25,7 @@ """The AlertManager handles all the libtorrent alerts.""" -import gobject +from twisted.internet import reactor import deluge.component as component try: @@ -97,7 +97,7 @@ class AlertManager(component.Component): if alert_type in self.handlers.keys(): for handler in self.handlers[alert_type]: if not wait: - gobject.idle_add(handler, alert) + reactor.callLater(0, handler, alert) else: handler(alert) diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py index 111b922bc..02ee386b2 100644 --- a/deluge/core/authmanager.py +++ b/deluge/core/authmanager.py @@ -1,7 +1,7 @@ # # authmanager.py # -# Copyright (C) 2008 Andrew Resch +# Copyright (C) 2009 Andrew Resch # # Deluge is free software. # @@ -29,16 +29,25 @@ import stat import deluge.component as component import deluge.configmanager as configmanager +from deluge.log import LOG as log + +AUTH_LEVEL_NONE = 0 +AUTH_LEVEL_READONLY = 1 +AUTH_LEVEL_NORMAL = 5 +AUTH_LEVEL_ADMIN = 10 + +AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL + class AuthManager(component.Component): def __init__(self): component.Component.__init__(self, "AuthManager") - self.auth = {} + self.__auth = {} def start(self): self.__load_auth_file() def stop(self): - self.auth = {} + self.__auth = {} def shutdown(self): pass @@ -49,21 +58,22 @@ class AuthManager(component.Component): :param username: str, username :param password: str, password - :returns: True or False - :rtype: bool + :returns: int, the auth level for this user or 0 if not able to authenticate + :rtype: int """ - if username not in self.auth: + 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: - return False + if username not in self.__auth: + return 0 - if self.auth[username] == password: - return True + if self.__auth[username][0] == password: + # Return the users auth level + return self.__auth[username][1] - return False + return 0 def __load_auth_file(self): auth_file = configmanager.get_config_dir("auth") @@ -74,7 +84,7 @@ class AuthManager(component.Component): from hashlib import sha1 as sha_hash except ImportError: from sha import new as sha_hash - open(auth_file, "w").write("localclient:" + sha_hash(str(random.random())).hexdigest() + "\n") + open(auth_file, "w").write("localclient:" + sha_hash(str(random.random())).hexdigest() + ":" + str(AUTH_LEVEL_ADMIN)) # Change the permissions on the file so only this user can read/write it os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE) @@ -85,7 +95,18 @@ class AuthManager(component.Component): # This is a comment line continue try: - username, password = line.split(":") - except ValueError: + lsplit = line.split(":") + except Exception, e: + log.error("Your auth file is malformed: %s", e) continue - self.auth[username.strip()] = password.strip() + if len(lsplit) == 2: + username, password = lsplit + log.warning("Your auth entry for %s contains no auth level, using AUTH_LEVEL_DEFAULT(%s)..", username, AUTH_LEVEL_DEFAULT) + level = AUTH_LEVEL_DEFAULT + elif len(lsplit) == 3: + username, password, level = lsplit + else: + log.error("Your auth file is malformed: Incorrect number of fields!") + continue + + self.__auth[username.strip()] = (password.strip(), level) diff --git a/deluge/core/core.py b/deluge/core/core.py index 6dd15f20e..8f3e911a0 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -29,7 +29,8 @@ import os.path import threading import pkg_resources -import gobject +from twisted.internet import reactor +from twisted.internet.task import LoopingCall try: import deluge.libtorrent as lt @@ -49,7 +50,7 @@ from deluge.core.filtermanager import FilterManager from deluge.core.preferencesmanager import PreferencesManager from deluge.core.autoadd import AutoAdd from deluge.core.authmanager import AuthManager -from deluge.core.rpcserver import BasicAuthXMLRPCRequestHandler, export +from deluge.core.rpcserver import export from deluge.log import LOG as log @@ -214,12 +215,12 @@ class Core(component.Component): return False # Exported Methods - @export + @export() def ping(self): """A method to see if the core is running""" return True - @export + @export() def register_client(self, port): """Registers a client with the signal manager so that signals are sent to it.""" @@ -227,17 +228,17 @@ class Core(component.Component): if self.config["new_release_check"]: self.check_new_release() - @export + @export() def deregister_client(self): """De-registers a client with the signal manager.""" self.signalmanager.deregister_client(component.get("RPCServer").client_address) - @export + @export() def add_torrent_file(self, filename, filedump, options): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ - gobject.idle_add(self._add_torrent_file, filename, filedump, options) + reactor.callLater(0, self._add_torrent_file, filename, filedump, options) def _add_torrent_file(self, filename, filedump, options): # Turn the filedump into a torrent_info @@ -259,7 +260,7 @@ class Core(component.Component): # Run the plugin hooks for 'post_torrent_add' self.pluginmanager.run_post_torrent_add(torrent_id) - @export + @export() def get_stats(self): """ document me!!! @@ -279,7 +280,7 @@ class Core(component.Component): return stats - @export + @export() def get_session_status(self, keys): """ Gets the session status values for 'keys' @@ -296,7 +297,7 @@ class Core(component.Component): return status - @export + @export() def add_torrent_url(self, url, options): log.info("Attempting to add url %s", url) @@ -321,7 +322,7 @@ class Core(component.Component): # Add the torrent to session return callback(filename, filedump, options) - @export + @export() def add_torrent_magnets(self, uris, options): for uri in uris: log.debug("Attempting to add by magnet uri: %s", uri) @@ -335,7 +336,7 @@ class Core(component.Component): # Run the plugin hooks for 'post_torrent_add' self.pluginmanager.run_post_torrent_add(torrent_id) - @export + @export() def remove_torrent(self, torrent_ids, remove_data): log.debug("Removing torrent %s from the core.", torrent_ids) for torrent_id in torrent_ids: @@ -343,57 +344,57 @@ class Core(component.Component): # Run the plugin hooks for 'post_torrent_remove' self.pluginmanager.run_post_torrent_remove(torrent_id) - @export + @export() def force_reannounce(self, torrent_ids): log.debug("Forcing reannouncment to: %s", torrent_ids) for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_reannounce() - @export + @export() def pause_torrent(self, torrent_ids): log.debug("Pausing: %s", torrent_ids) for torrent_id in torrent_ids: if not self.torrentmanager[torrent_id].pause(): log.warning("Error pausing torrent %s", torrent_id) - @export + @export() def connect_peer(self, torrent_id, ip, port): log.debug("adding peer %s to %s", ip, torrent_id) if not self.torrentmanager[torrent_id].connect_peer(ip, port): log.warning("Error adding peer %s:%s to %s", ip, port, torrent_id) - @export + @export() def move_storage(self, torrent_ids, dest): log.debug("Moving storage %s to %s", torrent_ids, dest) for torrent_id in torrent_ids: if not self.torrentmanager[torrent_id].move_storage(dest): log.warning("Error moving torrent %s to %s", torrent_id, dest) - @export + @export() def pause_all_torrents(self): """Pause all torrents in the session""" self.session.pause() - @export + @export() def resume_all_torrents(self): """Resume all torrents in the session""" self.session.resume() self.signalmanager.emit("torrent_all_resumed") - @export + @export() def resume_torrent(self, torrent_ids): log.debug("Resuming: %s", torrent_ids) for torrent_id in torrent_ids: self.torrentmanager[torrent_id].resume() - @export + @export() def get_status_keys(self): """ returns all possible keys for the keys argument in get_torrent(s)_status. """ return STATUS_KEYS + self.pluginmanager.status_fields.keys() - @export + @export() def get_torrent_status(self, torrent_id, keys): # Build the status dictionary status = self.torrentmanager[torrent_id].get_status(keys) @@ -404,7 +405,7 @@ class Core(component.Component): status.update(self.pluginmanager.get_status(torrent_id, leftover_fields)) return status - @export + @export() def get_torrents_status(self, filter_dict, keys): """ returns all torrents , optionally filtered by filter_dict. @@ -418,7 +419,7 @@ class Core(component.Component): return status_dict - @export + @export() def get_filter_tree(self , show_zero_hits=True, hide_cat=None): """ returns {field: [(value,count)] } @@ -426,18 +427,18 @@ class Core(component.Component): """ return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat) - @export + @export() def get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager return self.torrentmanager.get_torrent_list() - @export + @export() def get_config(self): """Get all the preferences as a dictionary""" return self.config.config - @export + @export() def get_config_value(self, key): """Get the config value for key""" try: @@ -447,7 +448,7 @@ class Core(component.Component): return value - @export + @export() def get_config_values(self, keys): """Get the config values for the entered keys""" config = {} @@ -458,7 +459,7 @@ class Core(component.Component): pass return config - @export + @export() def set_config(self, config): """Set the config with values from dictionary""" # Load all the values into the configuration @@ -467,156 +468,158 @@ class Core(component.Component): config[key] = config[key].encode("utf8") self.config[key] = config[key] - @export + @export() def get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() - @export + @export() def get_num_connections(self): """Returns the current number of connections""" return self.session.num_connections() - @export + @export() def get_dht_nodes(self): """Returns the number of dht nodes""" return self.session.status().dht_nodes - @export + @export() def get_download_rate(self): """Returns the payload download rate""" return self.session.status().payload_download_rate - @export + @export() def get_upload_rate(self): """Returns the payload upload rate""" return self.session.status().payload_upload_rate - @export + @export() def get_available_plugins(self): """Returns a list of plugins available in the core""" return self.pluginmanager.get_available_plugins() - @export + @export() def get_enabled_plugins(self): """Returns a list of enabled plugins in the core""" return self.pluginmanager.get_enabled_plugins() - @export + @export() def enable_plugin(self, plugin): self.pluginmanager.enable_plugin(plugin) return None - @export + @export() def disable_plugin(self, plugin): self.pluginmanager.disable_plugin(plugin) return None - @export + @export() def force_recheck(self, torrent_ids): """Forces a data recheck on torrent_ids""" for torrent_id in torrent_ids: self.torrentmanager[torrent_id].force_recheck() - @export + @export() def set_torrent_options(self, torrent_ids, options): """Sets the torrent options for torrent_ids""" for torrent_id in torrent_ids: self.torrentmanager[torrent_id].set_options(options) - @export + @export() def set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" return self.torrentmanager[torrent_id].set_trackers(trackers) - @export + @export() def set_torrent_max_connections(self, torrent_id, value): """Sets a torrents max number of connections""" return self.torrentmanager[torrent_id].set_max_connections(value) - @export + @export() def set_torrent_max_upload_slots(self, torrent_id, value): """Sets a torrents max number of upload slots""" return self.torrentmanager[torrent_id].set_max_upload_slots(value) - @export + @export() def set_torrent_max_upload_speed(self, torrent_id, value): """Sets a torrents max upload speed""" return self.torrentmanager[torrent_id].set_max_upload_speed(value) - @export + @export() def set_torrent_max_download_speed(self, torrent_id, value): """Sets a torrents max download speed""" return self.torrentmanager[torrent_id].set_max_download_speed(value) - @export + @export() def set_torrent_file_priorities(self, torrent_id, priorities): """Sets a torrents file priorities""" return self.torrentmanager[torrent_id].set_file_priorities(priorities) - @export + @export() def set_torrent_prioritize_first_last(self, torrent_id, value): """Sets a higher priority to the first and last pieces""" return self.torrentmanager[torrent_id].set_prioritize_first_last(value) - @export + @export() def set_torrent_auto_managed(self, torrent_id, value): """Sets the auto managed flag for queueing purposes""" return self.torrentmanager[torrent_id].set_auto_managed(value) - @export + @export() def set_torrent_stop_at_ratio(self, torrent_id, value): """Sets the torrent to stop at 'stop_ratio'""" return self.torrentmanager[torrent_id].set_stop_at_ratio(value) - @export + @export() def set_torrent_stop_ratio(self, torrent_id, value): """Sets the ratio when to stop a torrent if 'stop_at_ratio' is set""" return self.torrentmanager[torrent_id].set_stop_ratio(value) - @export + @export() def set_torrent_remove_at_ratio(self, torrent_id, value): """Sets the torrent to be removed at 'stop_ratio'""" return self.torrentmanager[torrent_id].set_remove_at_ratio(value) - @export + @export() def set_torrent_move_on_completed(self, torrent_id, value): """Sets the torrent to be moved when completed""" return self.torrentmanager[torrent_id].set_move_on_completed(value) - @export + @export() def set_torrent_move_on_completed_path(self, torrent_id, value): """Sets the path for the torrent to be moved when completed""" return self.torrentmanager[torrent_id].set_move_on_completed_path(value) - @export + @export() def block_ip_range(self, range): """Block an ip range""" self.ip_filter.add_rule(range[0], range[1], 1) # Start a 2 second timer (and remove the previous one if it exists) - if self.__set_ip_filter_timer: - gobject.source_remove(self.__set_ip_filter_timer) - self.__set_ip_filter_timer = gobject.timeout_add(2000, self.session.set_ip_filter, self.ip_filter) + #if self.__set_ip_filter_timer: + # self.__set_ip_filter_timer.stop() - @export + #self.__set_ip_filter_timer = LoopingCall(self.session.set_ip_filter, self.ip_filter) + #self.__set_ip_filter_timer.start(2, False) + + @export() def reset_ip_filter(self): """Clears the ip filter""" self.ip_filter = lt.ip_filter() self.session.set_ip_filter(self.ip_filter) - @export + @export() def get_health(self): """Returns True if we have established incoming connections""" return self.session.status().has_incoming_connections - @export + @export() def get_path_size(self, path): """Returns the size of the file or folder 'path' and -1 if the path is unaccessible (non-existent or insufficient privs)""" return deluge.common.get_path_size(path) - @export + @export() def create_torrent(self, path, tracker, piece_length, comment, target, url_list, private, created_by, httpseeds, add_to_session): @@ -651,7 +654,7 @@ class Core(component.Component): if add_to_session: self.add_torrent_file(os.path.split(target)[1], open(target, "rb").read(), None) - @export + @export() def upload_plugin(self, filename, plugin_data): """This method is used to upload new plugins to the daemon. It is used when connecting to the daemon remotely and installing a new plugin on @@ -663,23 +666,23 @@ class Core(component.Component): f.close() component.get("PluginManager").scan_for_plugins() - @export + @export() def rescan_plugins(self): """Rescans the plugin folders for new plugins""" component.get("PluginManager").scan_for_plugins() - @export + @export() def rename_files(self, torrent_id, filenames): """Renames files in 'torrent_id'. The 'filenames' parameter should be a list of (index, filename) pairs.""" self.torrentmanager[torrent_id].rename_files(filenames) - @export + @export() def rename_folder(self, torrent_id, folder, new_folder): """Renames the 'folder' to 'new_folder' in 'torrent_id'.""" self.torrentmanager[torrent_id].rename_folder(folder, new_folder) - @export + @export() def queue_top(self, torrent_ids): log.debug("Attempting to queue %s to top", torrent_ids) for torrent_id in torrent_ids: @@ -690,7 +693,7 @@ class Core(component.Component): except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @export + @export() def queue_up(self, torrent_ids): log.debug("Attempting to queue %s to up", torrent_ids) #torrent_ids must be sorted before moving. @@ -703,7 +706,7 @@ class Core(component.Component): except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @export + @export() def queue_down(self, torrent_ids): log.debug("Attempting to queue %s to down", torrent_ids) #torrent_ids must be sorted before moving. @@ -716,7 +719,7 @@ class Core(component.Component): except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @export + @export() def queue_bottom(self, torrent_ids): log.debug("Attempting to queue %s to bottom", torrent_ids) for torrent_id in torrent_ids: @@ -727,11 +730,11 @@ class Core(component.Component): except KeyError: log.warning("torrent_id: %s does not exist in the queue", torrent_id) - @export + @export() def glob(self, path): return glob.glob(path) - @export + @export() def test_listen_port(self): """ Checks if active port is open """ import urllib diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index a6b5bdd1b..691c1e5e7 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -24,10 +24,10 @@ import signal -import gobject import gettext import locale import pkg_resources +from twisted.internet import reactor import deluge.component as component import deluge.configmanager @@ -59,13 +59,11 @@ class Daemon(object): from win32api import SetConsoleCtrlHandler from win32con import CTRL_CLOSE_EVENT from win32con import CTRL_SHUTDOWN_EVENT - result = 0 def win_handler(ctrl_type): log.debug("ctrl_type: %s", ctrl_type) if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: self.__shutdown() - result = 1 - return result + return 1 SetConsoleCtrlHandler(win_handler) version = deluge.common.get_version() @@ -83,25 +81,33 @@ class Daemon(object): self.core = Core() self.rpcserver = RPCServer( - options.port if options.port else self.core.config["daemon_port"], - self.core.config["allow_remote"] + port=options.port if options.port else self.core.config["daemon_port"], + allow_remote=self.core.config["allow_remote"] ) - self.rpcserver.register_object(self.core, "core") - self.rpcserver.register_object(self, "daemon") + self.rpcserver.register_object(self.core) + self.rpcserver.register_object(self) - gobject.threads_init() # Make sure we start the PreferencesManager first component.start("PreferencesManager") component.start() - self.loop = gobject.MainLoop() +# reactor.run() try: - self.loop.run() + reactor.run() except KeyboardInterrupt: self.shutdown() - @export - def shutdown(self): + @export() + def shutdown(self, *args, **kwargs): component.shutdown() - self.loop.quit() + reactor.stop() + + @export() + def info(self): + """ + Returns some info from the daemon. + + :returns: str, the version number + """ + return deluge.common.get_version() diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index fb2dcc8d8..86c27f062 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -25,7 +25,8 @@ """PluginManager for Core""" -import gobject +from twisted.internet import reactor +from twisted.internet.task import LoopingCall import deluge.pluginmanagerbase import deluge.component as component @@ -57,13 +58,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, self.enable_plugins() # Set update timer to call update() in plugins every second - self.update_timer = gobject.timeout_add(1000, self.update_plugins) + self.update_timer = LoopingCall(self.update_plugins) + self.update_timer.start(1) def stop(self): # Disable all enabled plugins self.disable_plugins() # Stop the update timer - gobject.source_remove(self.update_timer) + self.update_timer.stop() def shutdown(self): self.stop() @@ -151,4 +153,3 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def reset_ip_filter(self): """Resets the ip filter""" return self.core.export_reset_ip_filter() - diff --git a/deluge/core/preferencesmanager.py b/deluge/core/preferencesmanager.py index 8c495a2e6..f84d6d787 100644 --- a/deluge/core/preferencesmanager.py +++ b/deluge/core/preferencesmanager.py @@ -25,7 +25,8 @@ import os.path import threading -import gobject +from twisted.internet import reactor +from twisted.internet.task import LoopingCall try: import deluge.libtorrent as lt @@ -450,13 +451,14 @@ class PreferencesManager(component.Component): log.debug("Checking for new release..") threading.Thread(target=self.core.get_new_release).start() if self.new_release_timer: - gobject.source_remove(self.new_release_timer) + self.new_release_timer.stop() # Set a timer to check for a new release every 3 days - self.new_release_timer = gobject.timeout_add( - 72 * 60 * 60 * 1000, self._on_new_release_check, "new_release_check", True) + self.new_release_timer = LoopingCall( + self._on_new_release_check, "new_release_check", True) + self.new_release_timer.start(72 * 60 * 60, False) else: if self.new_release_timer: - gobject.source.remove(self.new_release_timer) + self.new_release_timer.stop() def _on_set_proxies(self, key, value): for k, v in value.items(): diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index fd129517c..815e59b8d 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -24,27 +24,208 @@ """RPCServer Module""" -import gobject import sys +import zlib +import os +import traceback -from deluge.SimpleXMLRPCServer import SimpleXMLRPCServer -from deluge.SimpleXMLRPCServer import SimpleXMLRPCRequestHandler -from SocketServer import ThreadingMixIn -from base64 import decodestring, encodestring +from twisted.internet.protocol import Factory, Protocol +from twisted.internet import ssl, reactor +from OpenSSL import crypto, SSL + +import deluge.rencode as rencode from deluge.log import LOG as log + import deluge.component as component import deluge.configmanager +from deluge.core.authmanager import AUTH_LEVEL_NONE, AUTH_LEVEL_DEFAULT -def export(func): +RPC_RESPONSE = 1 +RPC_ERROR = 2 +RPC_SIGNAL = 3 + +def export(auth_level=AUTH_LEVEL_DEFAULT): """ Decorator function to register an object's method as an RPC. The object - will need to be registered with an RPCServer to be effective. + will need to be registered with an `:class:RPCServer` to be effective. :param func: function, the function to export + :param auth_level: int, the auth level required to call this method + """ - func._rpcserver_export = True - return func + def wrap(func, *args, **kwargs): + func._rpcserver_export = True + func._rpcserver_auth_level = auth_level + return func + + return wrap + +class DelugeError(Exception): + pass + +class NotAuthorizedError(DelugeError): + pass + +class ServerContextFactory(object): + def getContext(self): + """ + Create an SSL context. + + This loads the servers cert/private key SSL files for use with the + SSL transport. + """ + ssl_dir = deluge.configmanager.get_config_dir("ssl") + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_certificate_file(os.path.join(ssl_dir, "daemon.cert")) + ctx.use_privatekey_file(os.path.join(ssl_dir, "daemon.pkey")) + return ctx + +class DelugeRPCProtocol(Protocol): + __buffer = None + + def dataReceived(self, data): + """ + This method is called whenever data is received from a client. The + only message that a client sends to the server is a RPC Request message. + If the RPC Request message is valid, then the method is called in a thread + with _dispatch(). + + :param data: str, the data from the client. It should be a zlib compressed + rencoded string. + + """ + if self.__buffer: + # We have some data from the last dataReceived() so lets prepend it + data = self.__buffer + data + self.__buffer = None + + while data: + dobj = zlib.decompressobj() + try: + request = rencode.loads(dobj.decompress(data)) + except Exception, e: + log.debug("Received possible invalid message (%r): %s", data, e) + # This could be cut-off data, so we'll save this in the buffer + # and try to prepend it on the next dataReceived() + self.__buffer = data + return + else: + data = dobj.unused_data + + if type(request) is not tuple: + log.debug("Received invalid message: type is not tuple") + return + + if len(request) < 1: + log.debug("Received invalid message: there are no items") + return + + for call in request: + if len(call) != 4: + log.debug("Received invalid rpc request: number of items in request is %s", len(call)) + continue + + # Format the RPCRequest message for debug printing + s = call[1] + "(" + if call[2]: + s += ", ".join([str(x) for x in call[2]]) + if call[3]: + if call[2]: + s += ", " + s += ", ".join([key + "=" + str(value) for key, value in call[3].items()]) + s += ")" + + log.debug("RPCRequest: %s", s) + reactor.callLater(0, self._dispatch, *call) + + def sendData(self, data): + """ + Sends the data to the client. + + :param data: the object that is to be sent to the client. This should + be one of the RPC message types. + + """ + self.transport.write(zlib.compress(rencode.dumps(data))) + + def connectionMade(self): + """ + This method is called when a new client connects. + """ + peer = self.transport.getPeer() + log.info("Deluge Client connection made from: %s:%s", peer.host, peer.port) + # Set the initial auth level of this session to AUTH_LEVEL_NONE + self.factory.authorized_sessions[self.transport.sessionno] = AUTH_LEVEL_NONE + + def connectionLost(self, reason): + """ + This method is called when the client is disconnected. + + :param reason: str, the reason the client disconnected. + + """ + + # We need to remove this session from the authmanager + del self.factory.authorized_sessions[self.transport.sessionno] + + log.info("Deluge client disconnected: %s", reason.value) + + def _dispatch(self, request_id, method, args, kwargs): + """ + This method is run when a RPC Request is made. It will run the local method + and will send either a RPC Response or RPC Error back to the client. + + :param request_id: int, the request_id from the client (sent in the RPC Request) + :param method: str, the local method to call. It must be registered with + the `:class:RPCServer`. + :param args: list, the arguments to pass to `:param:method` + :param kwargs: dict, the keyword-arguments to pass to `:param:method` + + """ + if method == "daemon.login": + # This is a special case and used in the initial connection process + # We need to authenticate the user here + try: + ret = component.get("AuthManager").authorize(*args, **kwargs) + if ret: + self.factory.authorized_sessions[self.transport.sessionno] = ret + except Exception, e: + # Send error packet here + log.exception(e) + else: + self.sendData((RPC_RESPONSE, request_id, (ret))) + if not ret: + self.transport.loseConnection() + finally: + return + + if method in self.factory.methods: + try: + method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level + auth_level = self.factory.authorized_sessions[self.transport.sessionno] + if auth_level < method_auth_requirement: + # This session is not allowed to call this method + log.debug("Session %s is trying to call a method it is not authorized to call!", self.transport.sessionno) + raise NotAuthorizedError("Auth level too low: %s < %s" % (auth_level, method_auth_requirement)) + + ret = self.factory.methods[method](*args, **kwargs) + except Exception, e: + # Send an error packet here + exceptionType, exceptionValue, exceptionTraceback = sys.exc_info() + + self.sendData(( + RPC_ERROR, + request_id, + (exceptionType.__name__, + exceptionValue.message, + "".join(traceback.format_tb(exceptionTraceback))) + )) + # Don't bother printing out DelugeErrors, because they are just for the client + if not isinstance(e, DelugeError): + log.exception("Exception calling RPC request: %s", e) + else: + self.sendData((RPC_RESPONSE, request_id, ret)) class RPCServer(component.Component): """ @@ -53,10 +234,11 @@ class RPCServer(component.Component): decorator. :param port: int, the port the RPCServer will listen on + :param interface: str, the interface to listen on, this may override the `:param:allow_remote` setting :param allow_remote: bool, set True if the server should allow remote connections """ - def __init__(self, port=58846, allow_remote=False): + def __init__(self, port=58846, interface="", allow_remote=False): component.Component.__init__(self, "RPCServer") if allow_remote: @@ -64,33 +246,36 @@ class RPCServer(component.Component): else: hostname = "localhost" - # Setup the xmlrpc server + if interface: + hostname = interface + + log.info("Starting DelugeRPC server %s:%s", hostname, port) + + self.factory = Factory() + self.factory.protocol = DelugeRPCProtocol + # Holds the registered methods + self.factory.methods = {} + # Holds the session_ids and auth levels + self.factory.authorized_sessions = {} + + # Check for SSL cert/key and create them if necessary + ssl_dir = deluge.configmanager.get_config_dir("ssl") + if not os.path.exists(ssl_dir): + # The ssl folder doesn't exist so we need to create it + os.makedirs(ssl_dir) + self.__generate_ssl_keys() + else: + for f in ("daemon.pkey", "daemon.cert"): + if not os.path.exists(os.path.join(ssl_dir, f)): + self.__generate_ssl_keys() + try: - log.info("Starting XMLRPC server %s:%s", hostname, port) - self.server = XMLRPCServer((hostname, port), - requestHandler=BasicAuthXMLRPCRequestHandler, - logRequests=False, - allow_none=True) + reactor.listenSSL(port, self.factory, ServerContextFactory(), interface=hostname) except Exception, e: log.info("Daemon already running or port not available..") log.error(e) sys.exit(0) - self.server.register_multicall_functions() - self.server.register_introspection_functions() - - self.server.socket.setblocking(False) - - gobject.io_add_watch(self.server.socket.fileno(), gobject.IO_IN | gobject.IO_OUT | gobject.IO_PRI | gobject.IO_ERR | gobject.IO_HUP, self.__on_socket_activity) - - def __on_socket_activity(self, source, condition): - """ - This gets called when there is activity on the socket, ie, data to read - or to write and is called by gobject.io_add_watch(). - """ - self.server.handle_request() - return True - def register_object(self, obj, name=None): """ Registers an object to export it's rpc methods. These methods should @@ -100,43 +285,51 @@ class RPCServer(component.Component): :param name: str, the name to use, if None, it will be the class name of the object """ if not name: - name = obj.__class__.__name__ + name = obj.__class__.__name__.lower() + log.debug("dir: %s", dir(obj)) for d in dir(obj): if d[0] == "_": continue if getattr(getattr(obj, d), '_rpcserver_export', False): log.debug("Registering method: %s", name + "." + d) - self.server.register_function(getattr(obj, d), name + "." + d) + #self.server.register_function(getattr(obj, d), name + "." + d) + self.factory.methods[name + "." + d] = getattr(obj, d) - @property - def client_address(self): - """ - The ip address of the current client. - """ - return self.server.client_address + def get_object_method(self, name): + log.debug(self.factory.methods) + return self.factory.methods[name] -class XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer): - def get_request(self): + def __generate_ssl_keys(self): """ - Get the request and client address from the socket. - We override this so that we can get the ip address of the client. + This method generates a new SSL key/cert. """ - request, client_address = self.socket.accept() - self.client_address = client_address[0] - return (request, client_address) - -class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - def do_POST(self): - if "authorization" in self.headers: - auth = self.headers['authorization'] - auth = auth.replace("Basic ","") - decoded_auth = decodestring(auth) - # Check authentication here - if component.get("AuthManager").authorize(*decoded_auth.split(":")): - # User authorized, call the real do_POST now - return SimpleXMLRPCRequestHandler.do_POST(self) + digest = "md5" + # Generate key pair + pkey = crypto.PKey() + pkey.generate_key(crypto.TYPE_RSA, 1024) - # if cannot authenticate, end the connection - self.send_response(401) - self.end_headers() + # Generate cert request + req = crypto.X509Req() + subj = req.get_subject() + setattr(subj, "CN", "Deluge Daemon") + req.set_pubkey(pkey) + req.sign(pkey, digest) + + # Generate certificate + cert = crypto.X509() + cert.set_serial_number(0) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(60*60*24*365*5) # Five Years + cert.set_issuer(req.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(pkey, digest) + + # Write out files + ssl_dir = deluge.configmanager.get_config_dir("ssl") + open(os.path.join(ssl_dir, "daemon.pkey"), "w").write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) + open(os.path.join(ssl_dir, "daemon.cert"), "w").write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) + # Make the files only readable by this user + for f in ("daemon.pkey", "daemon.cert"): + os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE) diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index 7fbe13f07..2c8cddf65 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -27,7 +27,8 @@ import deluge.xmlrpclib as xmlrpclib import socket import struct -import gobject +from twisted.internet import reactor +from twisted.internet.task import LoopingCall import deluge.component as component from deluge.log import LOG as log @@ -81,8 +82,6 @@ class SignalManager(component.Component): uri = "http://" + str(address) + ":" + str(port) log.debug("Registering %s as a signal reciever..", uri) self.clients[uri] = xmlrpclib.ServerProxy(uri, transport=Transport()) - #self.clients[uri].socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, - # struct.pack('ii', 1, 0)) def emit(self, signal, *data): # Run the handlers @@ -91,7 +90,9 @@ class SignalManager(component.Component): handler(*data) for uri in self.clients: - gobject.idle_add(self._emit, uri, signal, 1, *data) + # reactor.callLater(0, self._emit, uri, signal, 1, *data) + #XXX: Need to fix this for the new signal sending + pass def _emit(self, uri, signal, count, *data): if uri not in self.clients: @@ -102,7 +103,7 @@ class SignalManager(component.Component): except (socket.error, Exception), e: log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count) if count < 30: - gobject.timeout_add(1000, self._emit, uri, signal, count + 1, *data) + reactor.callLater(1, self._emit, uri, signal, count + 1, *data) else: log.info("Removing %s because it couldn't be reached..", uri) del self.clients[uri] diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 429d241a3..3c7c7a30c 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -31,7 +31,8 @@ import os import time import shutil -import gobject +from twisted.internet import reactor +from twisted.internet.task import LoopingCall try: import deluge.libtorrent as lt @@ -178,8 +179,10 @@ class TorrentManager(component.Component): self.load_state() # Save the state every 5 minutes - self.save_state_timer = gobject.timeout_add(300000, self.save_state) - self.save_resume_data_timer = gobject.timeout_add(290000, self.save_resume_data) + self.save_state_timer = LoopingCall(self.save_state) + self.save_state_timer.start(200) + self.save_resume_data_timer = LoopingCall(self.save_resume_data) + self.save_resume_data_timer.start(190) def stop(self): # Save state on shutdown diff --git a/deluge/rencode.py b/deluge/rencode.py new file mode 100644 index 000000000..2b32c001b --- /dev/null +++ b/deluge/rencode.py @@ -0,0 +1,433 @@ + +""" +rencode -- Web safe object pickling/unpickling. + +Public domain, Connelly Barnes 2006-2007. + +The rencode module is a modified version of bencode from the +BitTorrent project. For complex, heterogeneous data structures with +many small elements, r-encodings take up significantly less space than +b-encodings: + + >>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99})) + 13 + >>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99})) + 26 + +The rencode format is not standardized, and may change with different +rencode module versions, so you should check that you are using the +same rencode version throughout your project. +""" + +__version__ = '1.0.1' +__all__ = ['dumps', 'loads'] + +# Original bencode module by Petru Paler, et al. +# +# Modifications by Connelly Barnes: +# +# - Added support for floats (sent as 32-bit or 64-bit in network +# order), bools, None. +# - Allowed dict keys to be of any serializable type. +# - Lists/tuples are always decoded as tuples (thus, tuples can be +# used as dict keys). +# - Embedded extra information in the 'typecodes' to save some space. +# - Added a restriction on integer length, so that malicious hosts +# cannot pass us large integers which take a long time to decode. +# +# Licensed by Bram Cohen under the "MIT license": +# +# "Copyright (C) 2001-2002 Bram Cohen +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# The Software is provided "AS IS", without warranty of any kind, +# express or implied, including but not limited to the warranties of +# merchantability, fitness for a particular purpose and +# noninfringement. In no event shall the authors or copyright holders +# be liable for any claim, damages or other liability, whether in an +# action of contract, tort or otherwise, arising from, out of or in +# connection with the Software or the use or other dealings in the +# Software." +# +# (The rencode module is licensed under the above license as well). +# + +import struct +import string +from threading import Lock + +# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()). +DEFAULT_FLOAT_BITS = 32 + +# Maximum length of integer when written as base 10 string. +MAX_INT_LENGTH = 64 + +# The bencode 'typecodes' such as i, d, etc have been extended and +# relocated on the base-256 character set. +CHR_LIST = chr(59) +CHR_DICT = chr(60) +CHR_INT = chr(61) +CHR_INT1 = chr(62) +CHR_INT2 = chr(63) +CHR_INT4 = chr(64) +CHR_INT8 = chr(65) +CHR_FLOAT32 = chr(66) +CHR_FLOAT64 = chr(44) +CHR_TRUE = chr(67) +CHR_FALSE = chr(68) +CHR_NONE = chr(69) +CHR_TERM = chr(127) + +# Positive integers with value embedded in typecode. +INT_POS_FIXED_START = 0 +INT_POS_FIXED_COUNT = 44 + +# Dictionaries with length embedded in typecode. +DICT_FIXED_START = 102 +DICT_FIXED_COUNT = 25 + +# Negative integers with value embedded in typecode. +INT_NEG_FIXED_START = 70 +INT_NEG_FIXED_COUNT = 32 + +# Strings with length embedded in typecode. +STR_FIXED_START = 128 +STR_FIXED_COUNT = 64 + +# Lists with length embedded in typecode. +LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT +LIST_FIXED_COUNT = 64 + +def decode_int(x, f): + f += 1 + newf = x.index(CHR_TERM, f) + if newf - f >= MAX_INT_LENGTH: + raise ValueError('overflow') + try: + n = int(x[f:newf]) + except (OverflowError, ValueError): + n = long(x[f:newf]) + if x[f] == '-': + if x[f + 1] == '0': + raise ValueError + elif x[f] == '0' and newf != f+1: + raise ValueError + return (n, newf+1) + +def decode_intb(x, f): + f += 1 + return (struct.unpack('!b', x[f:f+1])[0], f+1) + +def decode_inth(x, f): + f += 1 + return (struct.unpack('!h', x[f:f+2])[0], f+2) + +def decode_intl(x, f): + f += 1 + return (struct.unpack('!l', x[f:f+4])[0], f+4) + +def decode_intq(x, f): + f += 1 + return (struct.unpack('!q', x[f:f+8])[0], f+8) + +def decode_float32(x, f): + f += 1 + n = struct.unpack('!f', x[f:f+4])[0] + return (n, f+4) + +def decode_float64(x, f): + f += 1 + n = struct.unpack('!d', x[f:f+8])[0] + return (n, f+8) + +def decode_string(x, f): + colon = x.index(':', f) + try: + n = int(x[f:colon]) + except (OverflowError, ValueError): + n = long(x[f:colon]) + if x[f] == '0' and colon != f+1: + raise ValueError + colon += 1 + s = x[colon:colon+n] + try: + t = s.decode("utf8") + if len(t) != len(s): + s = t + except UnicodeEncodeError: + pass + return (s, colon+n) + +def decode_list(x, f): + r, f = [], f+1 + while x[f] != CHR_TERM: + v, f = decode_func[x[f]](x, f) + r.append(v) + return (tuple(r), f + 1) + +def decode_dict(x, f): + r, f = {}, f+1 + while x[f] != CHR_TERM: + k, f = decode_func[x[f]](x, f) + r[k], f = decode_func[x[f]](x, f) + return (r, f + 1) + +def decode_true(x, f): + return (True, f+1) + +def decode_false(x, f): + return (False, f+1) + +def decode_none(x, f): + return (None, f+1) + +decode_func = {} +decode_func['0'] = decode_string +decode_func['1'] = decode_string +decode_func['2'] = decode_string +decode_func['3'] = decode_string +decode_func['4'] = decode_string +decode_func['5'] = decode_string +decode_func['6'] = decode_string +decode_func['7'] = decode_string +decode_func['8'] = decode_string +decode_func['9'] = decode_string +decode_func[CHR_LIST ] = decode_list +decode_func[CHR_DICT ] = decode_dict +decode_func[CHR_INT ] = decode_int +decode_func[CHR_INT1 ] = decode_intb +decode_func[CHR_INT2 ] = decode_inth +decode_func[CHR_INT4 ] = decode_intl +decode_func[CHR_INT8 ] = decode_intq +decode_func[CHR_FLOAT32] = decode_float32 +decode_func[CHR_FLOAT64] = decode_float64 +decode_func[CHR_TRUE ] = decode_true +decode_func[CHR_FALSE ] = decode_false +decode_func[CHR_NONE ] = decode_none + +def make_fixed_length_string_decoders(): + def make_decoder(slen): + def f(x, f): + s = x[f+1:f+1+slen] + try: + t = s.decode("utf8") + if len(t) != len(s): + s = t + except UnicodeEncodeError: + pass + return (s, f+1+slen) + return f + for i in range(STR_FIXED_COUNT): + decode_func[chr(STR_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_string_decoders() + +def make_fixed_length_list_decoders(): + def make_decoder(slen): + def f(x, f): + r, f = [], f+1 + for i in range(slen): + v, f = decode_func[x[f]](x, f) + r.append(v) + return (tuple(r), f) + return f + for i in range(LIST_FIXED_COUNT): + decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_list_decoders() + +def make_fixed_length_int_decoders(): + def make_decoder(j): + def f(x, f): + return (j, f+1) + return f + for i in range(INT_POS_FIXED_COUNT): + decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i) + for i in range(INT_NEG_FIXED_COUNT): + decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i) + +make_fixed_length_int_decoders() + +def make_fixed_length_dict_decoders(): + def make_decoder(slen): + def f(x, f): + r, f = {}, f+1 + for j in range(slen): + k, f = decode_func[x[f]](x, f) + r[k], f = decode_func[x[f]](x, f) + return (r, f) + return f + for i in range(DICT_FIXED_COUNT): + decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i) + +make_fixed_length_dict_decoders() + +def encode_dict(x,r): + r.append(CHR_DICT) + for k, v in x.items(): + encode_func[type(k)](k, r) + encode_func[type(v)](v, r) + r.append(CHR_TERM) + + +def loads(x): + try: + r, l = decode_func[x[0]](x, 0) + except (IndexError, KeyError): + raise ValueError + if l != len(x): + raise ValueError + return r + +from types import StringType, IntType, LongType, DictType, ListType, TupleType, FloatType, NoneType, UnicodeType + +def encode_int(x, r): + if 0 <= x < INT_POS_FIXED_COUNT: + r.append(chr(INT_POS_FIXED_START+x)) + elif -INT_NEG_FIXED_COUNT <= x < 0: + r.append(chr(INT_NEG_FIXED_START-1-x)) + elif -128 <= x < 128: + r.extend((CHR_INT1, struct.pack('!b', x))) + elif -32768 <= x < 32768: + r.extend((CHR_INT2, struct.pack('!h', x))) + elif -2147483648 <= x < 2147483648: + r.extend((CHR_INT4, struct.pack('!l', x))) + elif -9223372036854775808 <= x < 9223372036854775808: + r.extend((CHR_INT8, struct.pack('!q', x))) + else: + s = str(x) + if len(s) >= MAX_INT_LENGTH: + raise ValueError('overflow') + r.extend((CHR_INT, s, CHR_TERM)) + +def encode_float32(x, r): + r.extend((CHR_FLOAT32, struct.pack('!f', x))) + +def encode_float64(x, r): + r.extend((CHR_FLOAT64, struct.pack('!d', x))) + +def encode_bool(x, r): + r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)]) + +def encode_none(x, r): + r.extend(CHR_NONE) + +def encode_string(x, r): + if len(x) < STR_FIXED_COUNT: + r.extend((chr(STR_FIXED_START + len(x)), x)) + else: + r.extend((str(len(x)), ':', x)) + +def encode_unicode(x, r): + encode_string(x.encode("utf8"), r) + +def encode_list(x, r): + if len(x) < LIST_FIXED_COUNT: + r.append(chr(LIST_FIXED_START + len(x))) + for i in x: + encode_func[type(i)](i, r) + else: + r.append(CHR_LIST) + for i in x: + encode_func[type(i)](i, r) + r.append(CHR_TERM) + +def encode_dict(x,r): + if len(x) < DICT_FIXED_COUNT: + r.append(chr(DICT_FIXED_START + len(x))) + for k, v in x.items(): + encode_func[type(k)](k, r) + encode_func[type(v)](v, r) + else: + r.append(CHR_DICT) + for k, v in x.items(): + encode_func[type(k)](k, r) + encode_func[type(v)](v, r) + r.append(CHR_TERM) + +encode_func = {} +encode_func[IntType] = encode_int +encode_func[LongType] = encode_int +encode_func[StringType] = encode_string +encode_func[ListType] = encode_list +encode_func[TupleType] = encode_list +encode_func[DictType] = encode_dict +encode_func[NoneType] = encode_none +encode_func[UnicodeType] = encode_unicode + +lock = Lock() + +try: + from types import BooleanType + encode_func[BooleanType] = encode_bool +except ImportError: + pass + +def dumps(x, float_bits=DEFAULT_FLOAT_BITS): + """ + Dump data structure to str. + + Here float_bits is either 32 or 64. + """ + lock.acquire() + try: + if float_bits == 32: + encode_func[FloatType] = encode_float32 + elif float_bits == 64: + encode_func[FloatType] = encode_float64 + else: + raise ValueError('Float bits (%d) is not 32 or 64' % float_bits) + r = [] + encode_func[type(x)](x, r) + finally: + lock.release() + return ''.join(r) + +def test(): + f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0] + f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0] + f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0] + L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),) + assert loads(dumps(L)) == L + d = dict(zip(range(-100000,100000),range(-100000,100000))) + d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False}) + L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''}) + assert loads(dumps(L)) == L + L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000) + assert loads(dumps(L)) == L + L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple([tuple(range(n)) for n in range(100)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple(['a'*n for n in range(1000)]) + ('b',) + assert loads(dumps(L)) == L + L = tuple(['a'*n for n in range(1000)]) + (None,True,None) + assert loads(dumps(L)) == L + assert loads(dumps(None)) == None + assert loads(dumps({None:None})) == {None:None} + assert 1e-10 -# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2009 Andrew Resch # # Deluge is free software. # @@ -23,364 +22,543 @@ # Boston, MA 02110-1301, USA. # +from twisted.internet.protocol import Protocol, ClientFactory +from twisted.internet import reactor, ssl, defer +import deluge.rencode as rencode +import zlib -import os.path -import socket -import struct -import httplib -import urlparse - -import gobject - -import deluge.xmlrpclib as xmlrpclib +if deluge.common.windows_check(): + import win32api +else: + import subprocess import deluge.common -import deluge.error from deluge.log import LOG as log -class Transport(xmlrpclib.Transport): - def __init__(self, username=None, password=None, use_datetime=0): - self.__username = username - self.__password = password - self._use_datetime = use_datetime +RPC_RESPONSE = 1 +RPC_ERROR = 2 +RPC_SIGNAL = 3 - def request(self, host, handler, request_body, verbose=0): - # issue XML-RPC request +def format_kwargs(kwargs): + return ", ".join([key + "=" + str(value) for key, value in kwargs.items()]) - h = self.make_connection(host) - if verbose: - h.set_debuglevel(1) +class DelugeRPCError(object): + """ + This object is passed to errback handlers in the event of a RPCError from the + daemon. + """ + def __init__(self, method, args, kwargs, exception_type, exception_msg, traceback): + self.method = method + self.args = args + self.kwargs = kwargs + self.exception_type = exception_type + self.exception_msg = exception_msg + self.traceback = traceback - self.send_request(h, handler, request_body) - self.send_host(h, host) - self.send_user_agent(h) +class DelugeRPCRequest(object): + """ + This object is created whenever there is a RPCRequest to be sent to the + daemon. It is generally only used by the DaemonProxy's call method. + """ - if self.__username is not None and self.__password is not None: - h.putheader("AUTHORIZATION", "Basic %s" % string.replace( - encodestring("%s:%s" % (self.__username, self.__password)), - "\012", "")) + request_id = None + method = None + args = None + kwargs = None - self.send_content(h, request_body) + def __repr__(self): + """ + Returns a string of the RPCRequest in the following form: + method(arg, kwarg=foo, ...) + """ + s = self.method + "(" + if self.args: + s += ", ".join([str(x) for x in self.args]) + if self.kwargs: + if self.args: + s += ", " + s += format_kwargs(self.kwargs) + s += ")" - errcode, errmsg, headers = h.getreply() + return s - if errcode != 200: - raise xmlrpclib.ProtocolError( - host + handler, - errcode, errmsg, - headers - ) + def format_message(self): + """ + Returns a properly formatted RPCRequest based on the properties. Will + raise a TypeError if the properties haven't been set yet. - self.verbose = verbose + :returns: a properly formated RPCRequest + """ + if self.request_id is None or self.method is None or self.args is None or self.kwargs is None: + raise TypeError("You must set the properties of this object before calling format_message!") - try: - sock = h._conn.sock - except AttributeError: - sock = None + return (self.request_id, self.method, self.args, self.kwargs) - return self._parse_response(h.getfile(), sock) +class DelugeRPCProtocol(Protocol): + __rpc_requests = {} + __buffer = None + def connectionMade(self): + # Set the protocol in the daemon so it can send data + self.factory.daemon.protocol = self + # Get the address of the daemon that we've connected to + peer = self.transport.getPeer() + self.factory.daemon.host = peer.host + self.factory.daemon.port = peer.port + self.factory.daemon.connected = True - def make_connection(self, host): - # create a HTTP connection object from a host descriptor - import httplib - host, extra_headers, x509 = self.get_host_info(host) - h = httplib.HTTP(host) - h._conn.connect() - h._conn.sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, - struct.pack('ii', 1, 0)) - return h + log.info("Connected to daemon at %s:%s..", peer.host, peer.port) + self.factory.daemon.connect_deferred.callback((peer.host, peer.port)) -class CoreProxy(gobject.GObject): - __gsignals__ = { - "new_core" : ( - gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), - "no_core" : ( - gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), - } - def __init__(self): - log.debug("CoreProxy init..") - gobject.GObject.__init__(self) - self._uri = None - self.rpc_core = None - self._multi = None - self._callbacks = [] - self._multi_timer = None + def dataReceived(self, data): + """ + This method is called whenever we receive data from the daemon. - def call(self, func, callback, *args): - if self.rpc_core is None or self._multi is None: - if self.rpc_core is None: - raise deluge.error.NoCoreError("The core proxy is invalid.") + :param data: a zlib compressed and rencoded string that should be either + a RPCResponse, RCPError or RPCSignal + """ + if self.__buffer: + # We have some data from the last dataReceived() so lets prepend it + data = self.__buffer + data + self.__buffer = None + + while data: + dobj = zlib.decompressobj() + try: + request = rencode.loads(dobj.decompress(data)) + except Exception, e: + log.debug("Received possible invalid message: %s", e) + # This could be cut-off data, so we'll save this in the buffer + # and try to prepend it on the next dataReceived() + self.__buffer = data + return + else: + data = dobj.unused_data + + if type(request) is not tuple: + log.debug("Received invalid message: type is not tuple") + return + if len(request) < 3: + log.debug("Received invalid message: number of items in response is %s", len(3)) return - _func = getattr(self._multi, func) + message_type = request[0] - if _func is not None: - if (func, args) in self._multi.get_call_list(): - index = self._multi.get_call_list().index((func, args)) - if callback not in self._callbacks[index]: - self._callbacks[index].append(callback) - else: - if len(args) == 0: - _func() - else: - _func(*args) + if message_type == RPC_SIGNAL: + signal = request[1] + # A RPCSignal was received from the daemon so run any handlers + # associated with it. + if signal in self.factory.daemon.signal_handlers: + for handler in self.factory.daemon.signal_handlers[signal]: + handler(*request[2]) + return - self._callbacks.append([callback]) + request_id = request[1] - def do_multicall(self, block=False): - if len(self._callbacks) == 0: - return True + # We get the Deferred object for this request_id to either run the + # callbacks or the errbacks dependent on the response from the daemon. + d = self.factory.daemon.pop_deferred(request_id) - if self._multi is not None and self.rpc_core is not None: - try: - try: - for i, ret in enumerate(self._multi()): - try: - for callback in self._callbacks[i]: - if block == False: - gobject.idle_add(callback, ret) - else: - callback(ret) - except: - pass - except (socket.error, xmlrpclib.ProtocolError), e: - log.error("Socket or XMLRPC error: %s", e) - self.set_core_uri(None) - except (deluge.xmlrpclib.Fault, Exception), e: - #self.set_core_uri(None) , disabled : there are many reasons for an exception ; not just an invalid core. - #todo : publish an exception event, ui's like gtk could popup a dialog for this. - log.warning("Multi-call Exception: %s:%s", e, getattr(e,"message",None)) - finally: - self._callbacks = [] + if message_type == RPC_RESPONSE: + # Run the callbacks registered with this Deferred object + d.callback(request[2]) + elif message_type == RPC_ERROR: + # Create the DelugeRPCError to pass to the errback + r = self.__rpc_requests[request_id] + e = DelugeRPCError(r.method, r.args, r.kwargs, request[2][0], request[2][1], request[2][2]) + # Run the errbacks registered with this Deferred object + d.errback(e) - self._multi = xmlrpclib.MultiCall(self.rpc_core) + del self.__rpc_requests[request_id] - return True + def send_request(self, request): + """ + Sends a RPCRequest to the server. - def set_core_uri(self, uri): - log.info("Setting core uri as %s", uri) + :param request: RPCRequest - if uri == None and self._uri != None: - self._uri = None - self.rpc_core = None - self._multi = None - try: - gobject.source_remove(self._multi_timer) - except: - pass - self.emit("no_core") - return + """ + # Store the DelugeRPCRequest object just in case a RPCError is sent in + # response to this request. We use the extra information when printing + # out the error for debugging purposes. + self.__rpc_requests[request.request_id] = request + log.debug("Sending RPCRequest %s: %s", request.request_id, request) + # Send the request in a tuple because multiple requests can be sent at once + self.transport.write(zlib.compress(rencode.dumps((request.format_message(),)))) - if uri != self._uri and self._uri != None: - self.rpc_core = None - self._multi = None - try: - gobject.source_remove(self._multi_timer) - except: - pass - self.emit("no_core") +class DelugeRPCClientFactory(ClientFactory): + protocol = DelugeRPCProtocol - self._uri = uri - # Get a new core - self.get_rpc_core() + def __init__(self, daemon): + self.daemon = daemon - def get_core_uri(self): - """Returns the URI of the core currently being used.""" - return self._uri + def startedConnecting(self, connector): + log.info("Connecting to daemon at %s:%s..", connector.host, connector.port) - def get_rpc_core(self): - if self.rpc_core is None and self._uri is not None: - log.debug("Creating ServerProxy..") - self.rpc_core = xmlrpclib.ServerProxy(self._uri.replace("localhost", "127.0.0.1"), allow_none=True, transport=Transport()) - self._multi = xmlrpclib.MultiCall(self.rpc_core) - self._multi_timer = gobject.timeout_add(200, self.do_multicall) - # Call any callbacks registered - self.emit("new_core") + def clientConnectionFailed(self, connector, reason): + log.warning("Connection to daemon at %s:%s failed: %s", connector.host, connector.port, reason.value) + self.daemon.connect_deferred.errback(reason) - return self.rpc_core + def clientConnectionLost(self, connector, reason): + log.info("Connection lost to daemon at %s:%s reason: %s", connector.host, connector.port, reason.value) + self.daemon.host = None + self.daemon.port = None + self.daemon.username = None + self.daemon.connected = False + if self.daemon.disconnect_deferred: + self.daemon.disconnect_deferred.callback(reason.value) + self.daemon.generate_event("disconnected") + +class DaemonProxy(object): + __event_handlers = { + "connected": [], + "disconnected": [] + } + + def register_event_handler(self, event, handler): + """ + Registers a handler that will be called when an event happens. + + :params event: str, the event to handle + :params handler: func, the handler function, f() + + :raises KeyError: if event is not valid + """ + self.__event_handlers[event].append(handler) + + def generate_event(self, event): + """ + Calls the event handlers for `:param:event`. + + :param event: str, the event to generate + """ + for handler in self.__event_handlers[event]: + handler() + +class DaemonSSLProxy(DaemonProxy): + def __init__(self): + self.__factory = DelugeRPCClientFactory(self) + self.__request_counter = 0 + self.__deferred = {} + + # This is set when a connection is made to the daemon + self.protocol = None + + # This is set when a connection is made + self.host = None + self.port = None + self.username = None + + self.connected = False + + self.disconnect_deferred = None + + def connect(self, host, port, username, password): + """ + Connects to a daemon at host:port + + :param host: str, the host to connect to + :param port: int, the listening port on the daemon + :param username: str, the username to login as + :param password: str, the password to login with + + :returns: twisted.Deferred + + """ + self.host = host + self.port = port + self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory()) + self.connect_deferred = defer.Deferred() + self.connect_deferred.addCallback(self.__on_connect, username, password) + self.connect_deferred.addErrback(self.__on_connect_fail) + + self.login_deferred = defer.Deferred() + # XXX: Add the login stuff.. + return self.login_deferred + + def disconnect(self): + self.disconnect_deferred = defer.Deferred() + self.__connector.disconnect() + return self.disconnect_deferred + + def call(self, method, *args, **kwargs): + """ + Makes a RPCRequest to the daemon. All methods should be in the form of + 'component.method'. + + :params method: str, the method to call in the form of 'component.method' + :params args: the arguments to call the remote method with + :params kwargs: the keyword arguments to call the remote method with + + :return: a twisted.Deferred object that will be activated when a RPCResponse + or RPCError is received from the daemon + + """ + # Create the DelugeRPCRequest to pass to protocol.send_request() + request = DelugeRPCRequest() + request.request_id = self.__request_counter + request.method = method + request.args = args + request.kwargs = kwargs + # Send the request to the server + self.protocol.send_request(request) + # Create a Deferred object to return and add a default errback to print + # the error. + d = defer.Deferred() + d.addErrback(self.__rpcError) + + # Store the Deferred until we receive a response from the daemon + self.__deferred[self.__request_counter] = d + + # Increment the request counter since we don't want to use the same one + # before a response is received. + self.__request_counter += 1 + + return d + + def pop_deferred(self, request_id): + """ + Pops a Deffered object. This is generally called once we receive the + reply we were waiting on from the server. + + :param request_id: int, the request_id of the Deferred to pop + """ + return self.__deferred.pop(request_id) + + def register_signal_handler(self, signal, handler): + """ + Registers a handler function to be called when `:param:signal` is received + from the daemon. + + :param signal: str, the name of the signal to handle + :param handler: function, the function to be called when `:param:signal` + is emitted from the daemon + + """ + if signal not in self.signal_handlers: + self.signal_handlers[signal] = [] + + self.signal_handlers[signal].append(handler) + + def deregister_signal_handler(self, signal, handler): + """ + Deregisters a signal handler. + + :param signal: str, the name of the signal + :param handler: function, the function registered + + """ + if signal in self.signal_handlers and handler in self.signal_handlers[signal]: + self.signal_handlers[signal].remove(handler) + + def __rpcError(self, error_data): + """ + Prints out a RPCError message to the error log. This includes the daemon + traceback. + + :param error_data: this is passed from the deferred errback with error.value + containing a `:class:DelugeRPCError` object. + """ + # 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) + + def __on_connect(self, result, username, password): + self.__login_deferred = self.call("daemon.login", username, password) + self.__login_deferred.addCallback(self.__on_login, username) + self.__login_deferred.addErrback(self.__on_login_fail) + + def __on_connect_fail(self, reason): + self.login_deferred.callback(False) + + def __on_login(self, result, username): + self.username = username + self.login_deferred.callback(result) + self.generate_event("connected") + + def __on_login_fail(self, result): + self.login_deferred.callback(False) + +class DaemonClassicProxy(DaemonProxy): + def __init__(self): + import daemon + self.__daemon = daemon.Daemon() + self.connected = True + + def call(self, method, *args, **kwargs): + log.debug("call: %s %s %s", method, args, kwargs) + + m = self.__daemon.rpcserver.get_object_method(method) + d = defer.Deferred() + try: + result = m(*args, **kwargs) + except Exception, e: + d.errback(e) + else: + d.callbacks(result) + return d -_core = CoreProxy() class DottedObject(object): - """This is used for dotted name calls to client""" - def __init__(self, client, base): - self.client = client - self.base = base + """ + This is used for dotted name calls to client + """ + def __init__(self, daemon, method): + self.daemon = daemon + self.base = method def __call__(self, *args, **kwargs): - return self.client.get_method("core." + self.base)(*args, **kwargs) + raise Exception("You must make calls in the form of 'component.method'!") def __getattr__(self, name): - return self.client.get_method(self.base + "." + name) + return RemoteMethod(self.daemon, self.base + "." + name) -class BaseClient(object): +class RemoteMethod(DottedObject): """ - wraps all calls to core/coreproxy - base for AClient and SClient + This is used when something like 'client.core.get_something()' is attempted. """ - no_callback_list = ["add_torrent_url", "pause_all_torrents", - "resume_all_torrents", "set_config", "enable_plugin", - "disable_plugin", "set_torrent_trackers", "connect_peer", - "set_torrent_max_connections", "set_torrent_max_upload_slots", - "set_torrent_max_upload_speed", "set_torrent_max_download_speed", - "set_torrent_private_flag", "set_torrent_file_priorities", - "block_ip_range", "remove_torrent", "pause_torrent", "move_storage", - "resume_torrent", "force_reannounce", "force_recheck", - "deregister_client", "register_client", "add_torrent_file", - "set_torrent_prioritize_first_last", "set_torrent_auto_managed", - "set_torrent_stop_ratio", "set_torrent_stop_at_ratio", - "set_torrent_remove_at_ratio", "set_torrent_move_on_completed", - "set_torrent_move_on_completed_path", "add_torrent_magnets", - "create_torrent", "upload_plugin", "rescan_plugins", "rename_files", - "rename_folder"] + def __call__(self, *args, **kwargs): + return self.daemon.call(self.base, *args, **kwargs) + +class Client(object): + """ + This class is used to connect to a daemon process and issue RPC requests. + """ + + __event_handlers = { + "connected": [], + "disconnected": [] + } def __init__(self): - self.core = _core + self._daemon_proxy = None - #xml-rpc introspection - def list_methods(self): - registered = self.core.rpc_core.system.listMethods() - return sorted(registered) - - def methodSignature(self, method_name): - "broken :(" - return self.core.rpc_core.system.methodSignature(method_name) - - def methodHelp(self, method_name): - return self.core.rpc_core.system.methodHelp(method_name) - - #wrappers, getattr - def get_method(self, method_name): - "Override this in subclass." - raise NotImplementedError() - - def __getattr__(self, method_name): - return DottedObject(self, method_name) - - #custom wrapped methods: - def add_torrent_file(self, torrent_files, torrent_options=None): - """Adds torrent files to the core - Expects a list of torrent files - A list of torrent_option dictionaries in the same order of torrent_files + def connect(self, host="127.0.0.1", port=58846, username="", password=""): """ - if torrent_files is None: - log.debug("No torrent files selected..") - return - log.debug("Attempting to add torrent files: %s", torrent_files) - for torrent_file in torrent_files: - # Open the .torrent file for reading because we need to send it's - # contents to the core. - try: - f = open(torrent_file, "rb") - except Exception, e: - log.warning("Unable to open %s: %s", torrent_file, e) - continue + Connects to a daemon process. - # Get the filename because the core doesn't want a path. - (path, filename) = os.path.split(torrent_file) - fdump = xmlrpclib.Binary(f.read()) - f.close() + :param host: str, the hostname of the daemon + :param port: int, the port of the daemon + :param username: str, the username to login with + :param password: str, the password to login with - # Get the options for the torrent - if torrent_options != None: - try: - options = torrent_options[torrent_files.index(torrent_file)] - except: - options = None + :returns: a Deferred object that will be called once the connection + has been established or fails + """ + self._daemon_proxy = DaemonSSLProxy() + self._daemon_proxy.register_event_handler("connected", self._on_connect) + self._daemon_proxy.register_event_handler("disconnected", self._on_disconnect) + d = self._daemon_proxy.connect(host, port, username, password) + return d + + def disconnect(self): + """ + Disconnects from the daemon. + """ + if self._daemon_proxy: + return self._daemon_proxy.disconnect() + + def start_classic_mode(self): + """ + Starts a daemon in the same process as the client. + """ + self._daemon_proxy = DaemonClassicProxy() + + def start_daemon(self, port, config): + """ + Starts a daemon process. + + :param port: int, the port for the daemon to listen on + :param config: str, the path to the current config folder + :returns: True if started, False if not + + """ + try: + if deluge.common.windows_check(): + win32api.WinExec("deluged --port=%s --config=%s" % (port, config)) else: - options = None - self.get_method("core.add_torrent_file")(filename, fdump, options) - - def add_torrent_file_binary(self, filename, fdump, options = None): - """ - Core-wrapper. - Adds 1 torrent file to the core. - Expects fdump as a bytestring (== result of f.read()). - """ - fdump_xmlrpc = xmlrpclib.Binary(fdump) - self.get_method("core.add_torrent_file")(filename, fdump_xmlrpc, options) - - #utility: - def has_callback(self, method_name): - msplit = method_name.split(".") - if msplit[0] == "core": - return not (msplit[1] in self.no_callback_list) + subprocess.call(["deluged", "--port=%s" % port, "--config=%s" % config]) + except Exception, e: + log.error("Unable to start daemon!") + log.exception(e) + return False else: return True def is_localhost(self): - """Returns True if core is a localhost""" - # Get the uri - uri = self.core.get_core_uri() - if uri != None: - # Get the host - u = urlparse.urlsplit(uri) - if u.hostname == "localhost" or u.hostname == "127.0.0.1": - return True - return False + """ + Checks if the current connected host is a localhost or not. - def get_core_uri(self): - """Get the core URI""" - return self.core.get_core_uri() + :returns: bool, True if connected to a localhost - def set_core_uri(self, uri='http://localhost:58846'): - """Sets the core uri""" - if uri: - # Check if we need to get the localclient auth password - u = urlparse.urlsplit(uri) - if (u.hostname == "localhost" or u.hostname == "127.0.0.1") and not u.username: - from deluge.ui.common import get_localhost_auth_uri - uri = get_localhost_auth_uri(uri) - return self.core.set_core_uri(uri) - - def connected(self): - """Returns True if connected to a host, and False if not.""" - if self.get_core_uri() != None: + """ + if self._daemon_proxy and self._daemon_proxy.host in ("127.0.0.1", "localhost"): return True return False - def shutdown(self): - """Shutdown the core daemon""" - try: - self.core.call("shutdown", None) - self.core.do_multicall(block=False) - finally: - self.set_core_uri(None) + def connected(self): + """ + Check to see if connected to a daemon. - #events: - def connect_on_new_core(self, callback): - """Connect a callback whenever a new core is connected to.""" - return self.core.connect("new_core", callback) + :returns: bool, True if connected - def connect_on_no_core(self, callback): - """Connect a callback whenever the core is disconnected from.""" - return self.core.connect("no_core", callback) + """ + return self._daemon_proxy.connected if self._daemon_proxy else False -class SClient(BaseClient): - """ - sync proxy - """ - def get_method(self, method_name): - return getattr(self.core.rpc_core, method_name) + def connection_info(self): + """ + Get some info about the connection or return None if not connected. -class AClient(BaseClient): - """ - async proxy - """ - def get_method(self, method_name): - if not self.has_callback(method_name): - def async_proxy_nocb(*args, **kwargs): - return self.core.call(method_name, None, *args, **kwargs) - return async_proxy_nocb - else: - def async_proxy(*args, **kwargs): - return self.core.call(method_name, *args, **kwargs) - return async_proxy + :returns: a tuple of (host, port, username) or None if not connected + """ + if self.connected(): + return (self._daemon_proxy.host, self._daemon_proxy.port, self._daemon_proxy.username) - def force_call(self, block=True): - """Forces the multicall batch to go now and not wait for the timer. This - call also blocks until all callbacks have been dealt with.""" - self.core.do_multicall(block=block) + return None -sclient = SClient() -aclient = AClient() + def register_event_handler(self, event, handler): + """ + Registers a handler that will be called when an event happens. + + :params event: str, the event to handle + :params handler: func, the handler function, f() + + :raises KeyError: if event is not valid + """ + self.__event_handlers[event].append(handler) + + def __generate_event(self, event): + """ + Calls the event handlers for `:param:event`. + + :param event: str, the event to generate + """ + + for handler in self.__event_handlers[event]: + handler() + + def force_call(self, block=False): + # no-op for now.. we'll see if we need this in the future + pass + + def __getattr__(self, method): + return DottedObject(self._daemon_proxy, method) + + def _on_connect(self): + self.__generate_event("connected") + + def _on_disconnect(self): + self.__generate_event("disconnected") + +# This is the object clients will use +client = Client() diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 5e87ae2d6..261225237 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -30,7 +30,6 @@ import pkg_resources import deluge.common import deluge.ui.gtkui.common as common -from deluge.ui.client import aclient as client class AboutDialog: def __init__(self): diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 03766c176..aebe7f35d 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -31,7 +31,7 @@ import gobject import pkg_resources -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component import deluge.ui.gtkui.listview as listview from deluge.configmanager import ConfigManager @@ -162,12 +162,10 @@ class AddTorrentDialog(component.Component): def _on_config_values(config): self.core_config = config + self.set_default_options() # Send requests to the core for these config values - client.get_config_values(_on_config_values, self.core_keys) - # Force a call to the core because we need this data now - client.force_call() - self.set_default_options() + client.core.get_config_values(self.core_keys).addCallback(_on_config_values) def add_from_files(self, filenames): import os.path @@ -673,9 +671,9 @@ class AddTorrentDialog(component.Component): row = self.torrent_liststore.iter_next(row) if torrent_filenames: - client.add_torrent_file(torrent_filenames, torrent_options) + client.core.add_torrent_file(torrent_filenames, torrent_options) if torrent_magnets: - client.add_torrent_magnets(torrent_magnets, torrent_magnet_options) + client.core.add_torrent_magnets(torrent_magnets, torrent_magnet_options) client.force_call(False) self.hide() diff --git a/deluge/ui/gtkui/common.py b/deluge/ui/gtkui/common.py index 538c1e537..972e1ee3a 100644 --- a/deluge/ui/gtkui/common.py +++ b/deluge/ui/gtkui/common.py @@ -30,7 +30,7 @@ import gtk, gtk.glade import pkg_resources -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component from deluge.log import LOG as log import deluge.common @@ -191,6 +191,6 @@ def add_peer_dialog(): if deluge.common.is_ip(ip): id = component.get("TorrentView").get_selected_torrent() log.debug("adding peer %s to %s", value, id) - client.connect_peer(id, ip, port) + client.core.connect_peer(id, ip, port) peer_dialog.destroy() return True diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 6b5fc8f6c..367760390 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -1,7 +1,7 @@ # # connectionmanager.py # -# Copyright (C) 2007 Andrew Resch +# Copyright (C) 2007-2009 Andrew Resch # # Deluge is free software. # @@ -22,38 +22,40 @@ # Boston, MA 02110-1301, USA. # - -import gtk, gtk.glade +import gtk import pkg_resources -import gobject -import socket -import os -import subprocess -import time -import threading import urlparse +import time +import hashlib import deluge.component as component -import deluge.xmlrpclib as xmlrpclib import deluge.common import deluge.ui.gtkui.common as common -from deluge.ui.common import get_localhost_auth_uri -from deluge.ui.client import aclient as client -from deluge.configmanager import ConfigManager import deluge.configmanager +from deluge.ui.client import client +import deluge.ui.client +from deluge.configmanager import ConfigManager from deluge.log import LOG as log -DEFAULT_URI = "http://127.0.0.1:58846" -DEFAULT_HOST = DEFAULT_URI.split(":")[1][2:] -DEFAULT_PORT = DEFAULT_URI.split(":")[-1] +DEFAULT_HOST = "127.0.0.1" +DEFAULT_PORT = 58846 DEFAULT_CONFIG = { - "hosts": [DEFAULT_URI] + "hosts": [(hashlib.sha1(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, "andrew", "andrew")] } -HOSTLIST_COL_PIXBUF = 0 -HOSTLIST_COL_URI = 1 -HOSTLIST_COL_STATUS = 2 +HOSTLIST_COL_ID = 0 +HOSTLIST_COL_HOST = 1 +HOSTLIST_COL_PORT = 2 +HOSTLIST_COL_STATUS = 3 +HOSTLIST_COL_USER = 4 +HOSTLIST_COL_PASS = 5 +HOSTLIST_COL_VERSION = 6 + + +HOSTLIST_PIXBUFS = [ + # This is populated in ConnectionManager.show +] HOSTLIST_STATUS = [ "Offline", @@ -61,242 +63,260 @@ HOSTLIST_STATUS = [ "Connected" ] -HOSTLIST_PIXBUFS = [ - # This is populated in ConnectionManager.__init__ -] - -if deluge.common.windows_check(): - import win32api - - def cell_render_host(column, cell, model, row, data): - host = model[row][data] - u = urlparse.urlsplit(host) - if not u.hostname: - host = "http://" + host - u = urlparse.urlsplit(host) - if u.username: - text = u.username + "@" + u.hostname + ":" + str(u.port) - else: - text = u.hostname + ":" + str(u.port) - + host, port, username = model.get(row, *data) + text = host + ":" + str(port) + if username: + text = username + "@" + text cell.set_property('text', text) +def cell_render_status(column, cell, model, row, data): + status = model[row][data] + pixbuf = None + if status in HOSTLIST_STATUS: + pixbuf = HOSTLIST_PIXBUFS[HOSTLIST_STATUS.index(status)] + + cell.set_property("pixbuf", pixbuf) + class ConnectionManager(component.Component): def __init__(self): component.Component.__init__(self, "ConnectionManager") + self.gtkui_config = ConfigManager("gtkui.conf") + + if self.gtkui_config["classic_mode"]: + client.start_classic_mode() + return + + self.config = ConfigManager("hostlist.conf.1.2", DEFAULT_CONFIG) + + # Component overrides + def start(self): + pass + + def stop(self): + pass + + def shutdown(self): + pass + + # Public methods + def show(self): + """ + Show the ConnectionManager dialog. + """ # Get the glade file for the connection manager self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/connection_manager.glade")) - self.window = component.get("MainWindow") - self.config = ConfigManager("hostlist.conf.1.1", DEFAULT_CONFIG) - self.gtkui_config = ConfigManager("gtkui.conf") + # Setup the ConnectionManager dialog self.connection_manager = self.glade.get_widget("connection_manager") - # Make the Connection Manager window a transient for the main window. self.connection_manager.set_transient_for(self.window.window) - - # Create status pixbufs - for stock_id in (gtk.STOCK_NO, gtk.STOCK_YES, gtk.STOCK_CONNECT): - HOSTLIST_PIXBUFS.append(self.connection_manager.render_icon(stock_id, gtk.ICON_SIZE_MENU)) - - self.hostlist = self.glade.get_widget("hostlist") self.connection_manager.set_icon(common.get_logo(32)) - self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32)) - # connection status pixbuf, hostname:port, status - self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, int) + self.hostlist = self.glade.get_widget("hostlist") - # Holds the online status of hosts - self.online_status = {} + # Create status pixbufs + if not HOSTLIST_PIXBUFS: + for stock_id in (gtk.STOCK_NO, gtk.STOCK_YES, gtk.STOCK_CONNECT): + HOSTLIST_PIXBUFS.append(self.connection_manager.render_icon(stock_id, gtk.ICON_SIZE_MENU)) - # Fill in hosts from config file - for host in self.config["hosts"]: - row = self.liststore.append() - self.liststore.set_value(row, HOSTLIST_COL_URI, host) + # Create the host list gtkliststore + # id-hash, hostname, port, status, username, password, version + self.liststore = gtk.ListStore(str, str, int, str, str, str, str) # Setup host list treeview self.hostlist.set_model(self.liststore) render = gtk.CellRendererPixbuf() - column = gtk.TreeViewColumn( - "Status", render, pixbuf=HOSTLIST_COL_PIXBUF) + column = gtk.TreeViewColumn("Status", render) + column.set_cell_data_func(render, cell_render_status, 3) self.hostlist.append_column(column) render = gtk.CellRendererText() - column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_URI) - column.set_cell_data_func(render, cell_render_host, 1) + column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_HOST) + column.set_cell_data_func(render, cell_render_host, (1, 2, 4)) + column.set_expand(True) + self.hostlist.append_column(column) + render = gtk.CellRendererText() + column = gtk.TreeViewColumn("Version", render, text=HOSTLIST_COL_VERSION) self.hostlist.append_column(column) - self.glade.signal_autoconnect({ - "on_button_addhost_clicked": self.on_button_addhost_clicked, - "on_button_removehost_clicked": self.on_button_removehost_clicked, - "on_button_startdaemon_clicked": \ - self.on_button_startdaemon_clicked, - "on_button_close_clicked": self.on_button_close_clicked, - "on_button_connect_clicked": self.on_button_connect_clicked, - "on_chk_autoconnect_toggled": self.on_chk_autoconnect_toggled, - "on_chk_autostart_toggled": self.on_chk_autostart_toggled, - "on_chk_donotshow_toggled": self.on_chk_donotshow_toggled - }) + # Load any saved host entries + self.__load_hostlist() + self.__load_options() - self.connection_manager.connect("delete-event", self.on_delete_event) - # Connect to the 'changed' event of TreeViewSelection to get selection - # changes. - self.hostlist.get_selection().connect("changed", - self.on_selection_changed) + # Connect the signals to the handlers + self.glade.signal_autoconnect(self) + self.hostlist.get_selection().connect("changed", self.on_hostlist_selection_changed) - self.hostlist.connect("row-activated", self._on_row_activated) + self.__update_list() - # If classic mode is set, we just start up a localhost daemon and connect to it - if self.gtkui_config["classic_mode"]: - self.start_localhost(DEFAULT_PORT) - # We need to wait for the host to start before connecting - uri = None - while not uri: - # We need to keep trying because the daemon may not have been started yet - # and the 'auth' file may not have been created - uri = get_localhost_auth_uri(DEFAULT_URI) - time.sleep(0.01) + response = self.connection_manager.run() - while not self.test_online_status(uri): - time.sleep(0.01) - client.set_core_uri(uri) - self.hide() - return + # Save the toggle options + self.__save_options() - # This controls the timer, if it's set to false the update timer will stop. - self._do_update = True - self._update_list() + self.connection_manager.destroy() + del self.glade + del self.window + del self.connection_manager + del self.liststore + del self.hostlist - # Auto connect to a host if applicable - if self.gtkui_config["autoconnect"] and \ - self.gtkui_config["autoconnect_host_uri"] != None: - uri = self.gtkui_config["autoconnect_host_uri"] - if self.test_online_status(uri): - # Host is online, so lets connect - client.set_core_uri(uri) - self.hide() - elif self.gtkui_config["autostart_localhost"]: - # Check to see if we are trying to connect to a localhost - u = urlparse.urlsplit(uri) - if u.hostname == "localhost" or u.hostname == "127.0.0.1": - # This is a localhost, so lets try to start it - # First add it to the list - self.add_host("localhost", u.port) - self.start_localhost(u.port) - # Get the localhost uri with authentication details - auth_uri = None - while not auth_uri: - # We need to keep trying because the daemon may not have been started yet - # and the 'auth' file may not have been created - auth_uri = get_localhost_auth_uri(uri) - time.sleep(0.01) + def add_host(self, host, port, username="", password=""): + """ + Adds a host to the list. - # We need to wait for the host to start before connecting - while not self.test_online_status(auth_uri): - time.sleep(0.01) - client.set_core_uri(auth_uri) - self.hide() + :param host: str, the hostname + :param port: int, the port + :param username: str, the username to login as + :param password: str, the password to login with - def start(self): - if self.gtkui_config["autoconnect"]: - # We need to update the autoconnect_host_uri on connection to host - # start() gets called whenever we get a new connection to a host - self.gtkui_config["autoconnect_host_uri"] = client.get_core_uri() + """ + # Check to see if there is already an entry for this host and return + # if thats the case + for entry in self.liststore: + if [entry[HOSTLIST_COL_HOST], entry[HOSTLIST_COL_PORT], entry[HOSTLIST_COL_USER]] == [host, port, username]: + raise Exception("Host already in list!") - def show(self): - # Set the checkbuttons according to config - self.glade.get_widget("chk_autoconnect").set_active( - self.gtkui_config["autoconnect"]) - self.glade.get_widget("chk_autostart").set_active( - self.gtkui_config["autostart_localhost"]) - self.glade.get_widget("chk_donotshow").set_active( - not self.gtkui_config["show_connection_manager_on_start"]) + # Host isn't in the list, so lets add it + row = self.liststore.append() + import time + import hashlib + self.liststore[row][HOSTLIST_COL_ID] = hashlib.sha1(str(time.time()).hexdigest()) + self.liststore[row][HOSTLIST_COL_HOST] = host + self.liststore[row][HOSTLIST_COL_PORT] = port + self.liststore[row][HOSTLIST_COL_USER] = username + self.liststore[row][HOSTLIST_COL_PASS] = password - # Setup timer to update host status - self._update_timer = gobject.timeout_add(1000, self._update_list) - self._update_list() - self._update_list() - self.connection_manager.show_all() + # Save the host list to file + self.__save_hostlist() - def hide(self): - self.connection_manager.hide() - self._do_update = False - try: - gobject.source_remove(self._update_timer) - except AttributeError: - # We are probably trying to hide the window without having it showed - # first. OK to ignore. - pass + # Update the status of the hosts + self.__update_list() - def _update_list(self): + # Private methods + def __save_hostlist(self): + """ + Save the current hostlist to the config file. + """ + # Grab the hosts from the liststore + self.config["hosts"] = [] + for row in self.liststore: + self.config["hosts"].append((row[HOSTLIST_COL_ID], row[HOSTLIST_COL_HOST], row[HOSTLIST_COL_PORT], row[HOSTLIST_COL_USER], row[HOSTLIST_COL_PASS])) + + self.config.save() + + def __load_hostlist(self): + """ + Load saved host entries + """ + for host in self.config["hosts"]: + new_row = self.liststore.append() + self.liststore[new_row][HOSTLIST_COL_ID] = host[0] + self.liststore[new_row][HOSTLIST_COL_HOST] = host[1] + self.liststore[new_row][HOSTLIST_COL_PORT] = host[2] + self.liststore[new_row][HOSTLIST_COL_USER] = host[3] + self.liststore[new_row][HOSTLIST_COL_PASS] = host[4] + + def __get_host_row(self, host_id): + """ + Returns the row in the liststore for `:param:host_id` or None + + """ + for row in self.liststore: + if host_id == row[HOSTLIST_COL_ID]: + return row + return None + + def __update_list(self): """Updates the host status""" - def update_row(model=None, path=None, row=None, columns=None): - uri = model.get_value(row, HOSTLIST_COL_URI) - threading.Thread(target=self.test_online_status, args=(uri,)).start() - try: - online = self.online_status[uri] - except: - online = False + def on_connect(result, c, host_id): + row = self.__get_host_row(host_id) + def on_info(info, c): + if row: + row[HOSTLIST_COL_STATUS] = "Online" + row[HOSTLIST_COL_VERSION] = info + self.__update_buttons() + c.disconnect() - # Update hosts status - if online: - online = HOSTLIST_STATUS.index("Online") - else: - online = HOSTLIST_STATUS.index("Offline") + def on_info_fail(reason): + if row: + row[HOSTLIST_COL_STATUS] = "Offline" + self.__update_buttons() - if urlparse.urlsplit(uri).hostname == "localhost" or urlparse.urlsplit(uri).hostname == "127.0.0.1": - uri = get_localhost_auth_uri(uri) + d = c.daemon.info() + d.addCallback(on_info, c) + d.addErrback(on_info_fail) - if uri == current_uri: - online = HOSTLIST_STATUS.index("Connected") + def on_connect_failed(reason, host_info): + row = self.__get_host_row(host_id) + if row: + row[HOSTLIST_COL_STATUS] = "Offline" + self.__update_buttons() - model.set_value(row, HOSTLIST_COL_STATUS, online) - model.set_value(row, HOSTLIST_COL_PIXBUF, HOSTLIST_PIXBUFS[online]) + for row in self.liststore: + host_id = row[HOSTLIST_COL_ID] + host = row[HOSTLIST_COL_HOST] + port = row[HOSTLIST_COL_PORT] + user = row[HOSTLIST_COL_USER] + password = row[HOSTLIST_COL_PASS] + if client.connected() and (host, port, user) == client.connection_info(): + def on_info(info): + row[HOSTLIST_COL_VERSION] = info + self.__update_buttons() + row[HOSTLIST_COL_STATUS] = "Connected" + client.daemon.info().addCallback(on_info) + continue - current_uri = client.get_core_uri() - self.liststore.foreach(update_row) - # Update the buttons - self.update_buttons() + # Create a new Client instance + c = deluge.ui.client.Client() + d = c.connect(host, port, user, password) + d.addCallback(on_connect, c, host_id) + d.addErrback(on_connect_failed, host_id) - # See if there is any row selected - paths = self.hostlist.get_selection().get_selected_rows()[1] - if len(paths) < 1: - # And there is at least 1 row - if self.liststore.iter_n_children(None) > 0: - # Then select the first row - self.hostlist.get_selection().select_iter(self.liststore.get_iter_first()) - return self._do_update + def __load_options(self): + """ + Set the widgets to show the correct options from the config. + """ + self.glade.get_widget("chk_autoconnect").set_active(self.gtkui_config["autoconnect"]) + self.glade.get_widget("chk_autostart").set_active(self.gtkui_config["autostart_localhost"]) + self.glade.get_widget("chk_donotshow").set_active(not self.gtkui_config["show_connection_manager_on_start"]) - def update_buttons(self): - """Updates the buttons based on selection""" - if self.liststore.iter_n_children(None) < 1: + def __save_options(self): + """ + Set options in gtkui config from the toggle buttons. + """ + self.gtkui_config["autoconnect"] = self.glade.get_widget("chk_autoconnect").get_active() + self.gtkui_config["autostart_localhost"] = self.glade.get_widget("chk_autostart").get_active() + self.gtkui_config["show_connection_manager_on_start"] = not self.glade.get_widget("chk_donotshow").get_active() + + def __update_buttons(self): + """ + Updates the buttons states. + """ + if len(self.liststore) == 0: # There is nothing in the list self.glade.get_widget("button_startdaemon").set_sensitive(True) self.glade.get_widget("button_connect").set_sensitive(False) self.glade.get_widget("button_removehost").set_sensitive(False) self.glade.get_widget("image_startdaemon").set_from_stock( gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - self.glade.get_widget("label_startdaemon").set_text( - "_Start Daemon") - self.glade.get_widget("label_startdaemon").set_use_underline( - True) + self.glade.get_widget("label_startdaemon").set_text("_Start Daemon") - # Get the selected row's URI - paths = self.hostlist.get_selection().get_selected_rows()[1] - # If nothing is selected, just return - if len(paths) < 1: + model, row = self.hostlist.get_selection().get_selected() + if not row: return - row = self.liststore.get_iter(paths[0]) - uri = self.liststore.get_value(row, HOSTLIST_COL_URI) - status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) - # Check to see if a localhost is selected + # Get some values about the selected host + status = model[row][HOSTLIST_COL_STATUS] + host = model[row][HOSTLIST_COL_HOST] + + log.debug("Status: %s", status) + # Check to see if we have a localhost entry selected localhost = False - u = urlparse.urlsplit(uri) - if u.hostname == "localhost" or u.hostname == "127.0.0.1": + if host in ("127.0.0.1", "localhost"): localhost = True # Make sure buttons are sensitive at start @@ -304,26 +324,25 @@ class ConnectionManager(component.Component): self.glade.get_widget("button_connect").set_sensitive(True) self.glade.get_widget("button_removehost").set_sensitive(True) - # See if this is the currently connected URI - if status == HOSTLIST_STATUS.index("Connected"): + # See if this is the currently connected host + if status == "Connected": # Display a disconnect button if we're connected to this host self.glade.get_widget("button_connect").set_label("gtk-disconnect") self.glade.get_widget("button_removehost").set_sensitive(False) else: self.glade.get_widget("button_connect").set_label("gtk-connect") - if status == HOSTLIST_STATUS.index("Offline") and not localhost: + if status == "Offline" and not localhost: self.glade.get_widget("button_connect").set_sensitive(False) # Check to see if the host is online - if status == HOSTLIST_STATUS.index("Connected") \ - or status == HOSTLIST_STATUS.index("Online"): + if status == "Connected" or status == "Online": self.glade.get_widget("image_startdaemon").set_from_stock( gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) self.glade.get_widget("label_startdaemon").set_text( "_Stop Daemon") # Update the start daemon button if the selected host is localhost - if localhost and status == HOSTLIST_STATUS.index("Offline"): + if localhost and status == "Offline": # The localhost is not online self.glade.get_widget("image_startdaemon").set_from_stock( gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) @@ -338,39 +357,30 @@ class ConnectionManager(component.Component): self.glade.get_widget("label_startdaemon").set_use_underline( True) - def save(self): - """Save the current host list to file""" - def append_row(model=None, path=None, row=None, columns=None): - hostlist.append(model.get_value(row, HOSTLIST_COL_URI)) + # Signal handlers + def __on_connected(self, connector, host_id): + if self.gtkui_config["autoconnect"]: + self.gtkui_config["autoconnect_host_id"] = host_id - hostlist = [] - self.liststore.foreach(append_row, hostlist) - self.config["hosts"] = hostlist - self.config.save() + def on_button_connect_clicked(self, widget): + model, row = self.hostlist.get_selection().get_selected() + status = model[row][HOSTLIST_COL_STATUS] + if status == "Connected": + def on_disconnect(reason): + self.__update_list() + client.disconnect().addCallback(on_disconnect) + return - def test_online_status(self, uri): - """Tests the status of URI.. Returns True or False depending on status. - """ - online = True - host = None - try: - u = urlparse.urlsplit(uri) - if u.hostname == "localhost" or u.hostname == "127.0.0.1": - host = xmlrpclib.ServerProxy(get_localhost_auth_uri(uri)) - else: - host = xmlrpclib.ServerProxy(uri) - host.core.ping() - except Exception: - online = False + host_id = model[row][HOSTLIST_COL_ID] + host = model[row][HOSTLIST_COL_HOST] + port = model[row][HOSTLIST_COL_PORT] + user = model[row][HOSTLIST_COL_USER] + password = model[row][HOSTLIST_COL_PASS] + client.connect(host, port, user, password).addCallback(self.__on_connected, host_id) + self.connection_manager.response(gtk.RESPONSE_OK) - del host - self.online_status[uri] = online - return online - - ## Callbacks - def on_delete_event(self, widget, event): - self.hide() - return True + def on_button_close_clicked(self, widget): + self.connection_manager.response(gtk.RESPONSE_CLOSE) def on_button_addhost_clicked(self, widget): log.debug("on_button_addhost_clicked") @@ -385,50 +395,11 @@ class ConnectionManager(component.Component): username = username_entry.get_text() password = password_entry.get_text() hostname = hostname_entry.get_text() - if not urlparse.urlsplit(hostname).hostname: - # We need to add a http:// - hostname = "http://" + hostname - u = urlparse.urlsplit(hostname) - if username and password: - host = u.scheme + "://" + username + ":" + password + "@" + u.hostname - else: - host = hostname # We add the host - self.add_host(host, port_spinbutton.get_value_as_int()) + self.add_host(hostname, port_spinbutton.get_value_as_int(), username, password) - dialog.hide() - - def add_host(self, hostname, port): - """Adds the host to the list""" - if not urlparse.urlsplit(hostname).scheme: - # We need to add http:// to this - hostname = "http://" + hostname - - # Check to make sure the hostname is at least 1 character long - if len(hostname) < 1: - return - - # Get the port and concatenate the hostname string - hostname = hostname + ":" + str(port) - - # Check to see if there is already an entry for this host and return - # if thats the case - self.hosts_liststore = [] - def each_row(model, path, iter, data): - self.hosts_liststore.append( - model.get_value(iter, HOSTLIST_COL_URI)) - self.liststore.foreach(each_row, None) - if hostname in self.hosts_liststore: - return - - # Host isn't in the list, so lets add it - row = self.liststore.append() - self.liststore.set_value(row, HOSTLIST_COL_URI, hostname) - # Save the host list to file - self.save() - # Update the status of the hosts - self._update_list() + dialog.destroy() def on_button_removehost_clicked(self, widget): log.debug("on_button_removehost_clicked") @@ -438,7 +409,7 @@ class ConnectionManager(component.Component): self.liststore.remove(self.liststore.get_iter(path)) # Update the hostlist - self._update_list() + self.__update_list() # Save the host list self.save() @@ -449,120 +420,48 @@ class ConnectionManager(component.Component): # There is nothing in the list, so lets create a localhost entry self.add_host(DEFAULT_HOST, DEFAULT_PORT) # ..and start the daemon. - self.start_localhost(DEFAULT_PORT) + client.start_daemon(DEFAULT_PORT, deluge.configmanager.get_config_dir()) return paths = self.hostlist.get_selection().get_selected_rows()[1] if len(paths) < 1: return - row = self.liststore.get_iter(paths[0]) - status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) - uri = self.liststore.get_value(row, HOSTLIST_COL_URI) - u = urlparse.urlsplit(uri) - if HOSTLIST_STATUS[status] == "Online" or\ - HOSTLIST_STATUS[status] == "Connected": + + status = self.liststore[paths[0]][HOSTLIST_COL_STATUS] + host = self.liststore[paths[0]][HOSTLIST_COL_HOST] + port = self.liststore[paths[0]][HOSTLIST_COL_PORT] + user = self.liststore[paths[0]][HOSTLIST_COL_USER] + password = self.liststore[paths[0]][HOSTLIST_COL_PASS] + + if host not in ("127.0.0.1", "localhost"): + return + + if status in ("Online", "Connected"): # We need to stop this daemon # Call the shutdown method on the daemon - if u.hostname == "127.0.0.1" or u.hostname == "localhost": - uri = get_localhost_auth_uri(uri) - core = xmlrpclib.ServerProxy(uri) - core.shutdown() - # Update display to show change - self._update_list() - elif HOSTLIST_STATUS[status] == "Offline": - self.start_localhost(u.port) + def on_daemon_shutdown(d): + # Update display to show change + self.__update_list() + if client.connected(): + client.daemon.shutdown().addCallback(on_daemon_shutdown) + else: + # Create a new client instance + c = deluge.ui.client.Client() + def on_connect(d, c): + log.debug("on_connect") + c.daemon.shutdown().addCallback(on_daemon_shutdown) - def start_localhost(self, port): - """Starts a localhost daemon""" - port = str(port) - log.info("Starting localhost:%s daemon..", port) - # Spawn a local daemon - if deluge.common.windows_check(): - win32api.WinExec("deluged -p %s" % port) - else: - subprocess.call(["deluged", "--port=%s" % port, - "--config=%s" % deluge.configmanager.get_config_dir()]) + c.connect(host, port, user, password).addCallback(on_connect, c) - def on_button_close_clicked(self, widget): - log.debug("on_button_close_clicked") - self.hide() + elif status == "Offline": + client.start_daemon(port, deluge.configmanager.get_config_dir()) + self.__update_list() - def on_button_connect_clicked(self, widget): - log.debug("on_button_connect_clicked") - component.stop() - paths = self.hostlist.get_selection().get_selected_rows()[1] - row = self.liststore.get_iter(paths[0]) - status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) - uri = self.liststore.get_value(row, HOSTLIST_COL_URI) - # Determine if this is a localhost - localhost = False - u = urlparse.urlsplit(uri) - if u.hostname == "localhost" or u.hostname == "127.0.0.1": - localhost = True + def on_button_refresh_clicked(self, widget): + self.__update_list() + def on_hostlist_row_activated(self, tree, path, view_column): + self.on_button_connect_clicked() - if status == HOSTLIST_STATUS.index("Connected"): - # Stop all the components first. - component.stop() - # If we are connected to this host, then we will disconnect. - client.set_core_uri(None) - self._update_list() - return - - # Test the host to see if it is online or not. We don't use the status - # column information because it can be up to 5 seconds out of sync. - if not self.test_online_status(uri): - log.warning("Host does not appear to be online..") - # If this is an offline localhost.. lets start it and connect - if localhost: - self.start_localhost(u.port) - # We need to wait for the host to start before connecting - auth_uri = None - while not auth_uri: - auth_uri = get_localhost_auth_uri(uri) - time.sleep(0.01) - - while not self.test_online_status(auth_uri): - time.sleep(0.01) - client.set_core_uri(auth_uri) - self._update_list() - self.hide() - - # Update the list to show proper status - self._update_list() - - return - - # Status is OK, so lets change to this host - if localhost: - client.set_core_uri(get_localhost_auth_uri(uri)) - else: - client.set_core_uri(uri) - - self.hide() - - def on_chk_autoconnect_toggled(self, widget): - log.debug("on_chk_autoconnect_toggled") - value = widget.get_active() - self.gtkui_config["autoconnect"] = value - # If we are currently connected to a host, set that as the autoconnect - # host. - if client.get_core_uri() != None: - self.gtkui_config["autoconnect_host_uri"] = client.get_core_uri() - - def on_chk_autostart_toggled(self, widget): - log.debug("on_chk_autostart_toggled") - value = widget.get_active() - self.gtkui_config["autostart_localhost"] = value - - def on_chk_donotshow_toggled(self, widget): - log.debug("on_chk_donotshow_toggled") - value = widget.get_active() - self.gtkui_config["show_connection_manager_on_start"] = not value - - def on_selection_changed(self, treeselection): - log.debug("on_selection_changed") - self.update_buttons() - - def _on_row_activated(self, tree, path, view_column): - self.on_button_connect_clicked(self.glade.get_widget("button_connect")) + def on_hostlist_selection_changed(self, treeselection): + self.__update_buttons() diff --git a/deluge/ui/gtkui/coreconfig.py b/deluge/ui/gtkui/coreconfig.py index 72344076d..361a9faba 100644 --- a/deluge/ui/gtkui/coreconfig.py +++ b/deluge/ui/gtkui/coreconfig.py @@ -24,7 +24,7 @@ import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.log import LOG as log class CoreConfig(component.Component): @@ -36,7 +36,7 @@ class CoreConfig(component.Component): self._on_config_value_changed) def start(self): - client.get_config(self._on_get_config) + client.core.get_config().addCallback(self._on_get_config) def stop(self): self.config = {} @@ -45,11 +45,10 @@ class CoreConfig(component.Component): return self.config[key] def __setitem__(self, key, value): - client.set_config({key: value}) + client.core.set_config({key: value}) def _on_get_config(self, config): self.config = config def _on_config_value_changed(self, key, value): self.config[key] = value - diff --git a/deluge/ui/gtkui/createtorrentdialog.py b/deluge/ui/gtkui/createtorrentdialog.py index 3265813b5..e078a3de0 100644 --- a/deluge/ui/gtkui/createtorrentdialog.py +++ b/deluge/ui/gtkui/createtorrentdialog.py @@ -28,7 +28,7 @@ import pkg_resources import os.path import gobject -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.ui.gtkui.listview as listview import deluge.component as component import deluge.common @@ -182,7 +182,7 @@ class CreateTorrentDialog: self.files_treestore.clear() self.files_treestore.append(None, [result, gtk.STOCK_NETWORK, size]) self.adjust_piece_size() - client.get_path_size(_on_get_path_size, result) + client.core.get_path_size(result).addCallback(_on_get_path_size) client.force_call(True) dialog.destroy() @@ -270,7 +270,7 @@ class CreateTorrentDialog: add_to_session = self.glade.get_widget("chk_add_to_session").get_active() if is_remote: - client.create_torrent( + client.core.create_torrent( path, tracker, piece_length, @@ -321,7 +321,7 @@ class CreateTorrentDialog: httpseeds=httpseeds) self.glade.get_widget("progress_dialog").hide_all() if add_to_session: - client.add_torrent_file([target]) + client.core.add_torrent_file([target]) def _on_create_torrent_progress(self, value, num_pieces): percent = float(value)/float(num_pieces) @@ -380,5 +380,3 @@ class CreateTorrentDialog: log.debug("_on_button_remove_clicked") row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1] self.trackers_liststore.remove(row) - - diff --git a/deluge/ui/gtkui/details_tab.py b/deluge/ui/gtkui/details_tab.py index f5a7b6db5..b07020e18 100644 --- a/deluge/ui/gtkui/details_tab.py +++ b/deluge/ui/gtkui/details_tab.py @@ -25,7 +25,7 @@ import gtk, gtk.glade -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component import deluge.common from deluge.ui.gtkui.torrentdetails import Tab @@ -69,8 +69,7 @@ class DetailsTab(Tab): status_keys = ["name", "total_size", "num_files", "tracker", "save_path", "message", "hash"] - client.get_torrent_status( - self._on_get_torrent_status, selected, status_keys) + client.core.get_torrent_status(selected, status_keys).addCallback(self._on_get_torrent_status) def _on_get_torrent_status(self, status): # Check to see if we got valid data from the core @@ -98,4 +97,3 @@ class DetailsTab(Tab): def clear(self): for widget in self.label_widgets: widget[0].set_text("") - diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index bb109e65d..83f06be5e 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -28,7 +28,7 @@ import pkg_resources import deluge.common import deluge.ui.gtkui.common as common -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component from deluge.log import LOG as log @@ -85,8 +85,7 @@ class EditTrackersDialog: # Get the trackers for this torrent - client.get_torrent_status( - self._on_get_torrent_status, self.torrent_id, ["trackers"]) + client.core.get_torrent_status(self.torrent_id, ["trackers"]).addCallback(self._on_get_torrent_status) client.force_call() def _on_get_torrent_status(self, status): @@ -169,7 +168,7 @@ class EditTrackersDialog: self.trackers.append(tracker) self.liststore.foreach(each, None) # Set the torrens trackers - client.set_torrent_trackers(self.torrent_id, self.trackers) + client.core.set_torrent_trackers(self.torrent_id, self.trackers) self.dialog.destroy() def on_button_cancel_clicked(self, widget): diff --git a/deluge/ui/gtkui/files_tab.py b/deluge/ui/gtkui/files_tab.py index e0ba54cee..4db39b814 100644 --- a/deluge/ui/gtkui/files_tab.py +++ b/deluge/ui/gtkui/files_tab.py @@ -30,7 +30,7 @@ import os.path import cPickle from deluge.ui.gtkui.torrentdetails import Tab -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.configmanager import ConfigManager import deluge.configmanager import deluge.component as component @@ -295,7 +295,7 @@ class FilesTab(Tab): # We already have the files list stored, so just update the view self.update_files() - client.get_torrent_status(self._on_get_torrent_status, self.torrent_id, status_keys) + client.core.get_torrent_status(self.torrent_id, status_keys).addCallback(self._on_get_torrent_status) client.force_call(True) def clear(self): @@ -304,7 +304,7 @@ class FilesTab(Tab): def _on_row_activated(self, tree, path, view_column): if client.is_localhost: - client.get_torrent_status(self._on_open_file, self.torrent_id, ["save_path", "files"]) + client.core.get_torrent_status(self.torrent_id, ["save_path", "files"]).addCallback(self._on_open_file) client.force_call(False) def get_file_path(self, row, path=""): @@ -469,7 +469,7 @@ class FilesTab(Tab): priorities = [p[1] for p in file_priorities] log.debug("priorities: %s", priorities) - client.set_torrent_file_priorities(self.torrent_id, priorities) + client.core.set_torrent_file_priorities(self.torrent_id, priorities) def _on_menuitem_donotdownload_activate(self, menuitem): self._set_file_priorities_on_user_change( @@ -523,7 +523,7 @@ class FilesTab(Tab): log.debug("filepath: %s", filepath) - client.rename_files(self.torrent_id, [(index, filepath)]) + client.core.rename_files(self.torrent_id, [(index, filepath)]) else: # We are renaming a folder folder = self.treestore[path][0] @@ -534,7 +534,7 @@ class FilesTab(Tab): parent_path = self.treestore[itr][0] + parent_path itr = self.treestore.iter_parent(itr) - client.rename_folder(self.torrent_id, parent_path + folder, parent_path + new_text) + client.core.rename_folder(self.torrent_id, parent_path + folder, parent_path + new_text) self._editing_index = None @@ -769,11 +769,11 @@ class FilesTab(Tab): while itr: pp = self.treestore[itr][0] + pp itr = self.treestore.iter_parent(itr) - client.rename_folder(self.torrent_id, pp + model[selected[0]][0], parent_path + model[selected[0]][0]) + client.core.rename_folder(self.torrent_id, pp + model[selected[0]][0], parent_path + model[selected[0]][0]) else: #[(index, filepath), ...] to_rename = [] for s in selected: to_rename.append((model[s][5], parent_path + model[s][0])) log.debug("to_rename: %s", to_rename) - client.rename_files(self.torrent_id, to_rename) + client.core.rename_files(self.torrent_id, to_rename) diff --git a/deluge/ui/gtkui/filtertreeview.py b/deluge/ui/gtkui/filtertreeview.py index 65e0fd56b..80cdef3d2 100644 --- a/deluge/ui/gtkui/filtertreeview.py +++ b/deluge/ui/gtkui/filtertreeview.py @@ -32,7 +32,7 @@ import deluge.component as component import deluge.common from deluge.ui.tracker_icons import TrackerIcons from deluge.log import LOG as log -from deluge.ui.client import aclient +from deluge.ui.client import client from deluge.configmanager import ConfigManager STATE_PIX = { @@ -280,7 +280,7 @@ class FilterTreeView(component.Component): hide_cat = [] if not self.config["sidebar_show_trackers"]: hide_cat = ["tracker_host"] - aclient.get_filter_tree(self.cb_update_filter_tree, self.config["sidebar_show_zero"], hide_cat) + client.core.get_filter_tree(self.config["sidebar_show_zero"], hide_cat).addCallback(self.cb_update_filter_tree) except Exception, e: log.debug(e) diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index c4eed5fac..bc4355610 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,9 +1,12 @@ - + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Add Host + True GTK_WIN_POS_CENTER True GDK_WINDOW_TYPE_HINT_DIALOG @@ -199,6 +202,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 Connection Manager + True GTK_WIN_POS_CENTER_ON_PARENT 350 300 @@ -267,6 +271,7 @@ True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + @@ -315,6 +320,22 @@ False + + + True + True + True + gtk-refresh + True + 0 + + + + False + False + 2 + + True @@ -445,10 +466,9 @@ 3 - - + + True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END @@ -458,7 +478,7 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-close True - 0 + -7 @@ -471,7 +491,6 @@ True True True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-connect True 0 @@ -486,6 +505,19 @@ False + False + 4 + + + + + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + False + False GTK_PACK_END diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 712a0f3e1..d8d3cd73b 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -24,20 +24,20 @@ from deluge.log import LOG as log -import pygtk -try: - pygtk.require('2.0') -except: - log.warning("It is suggested that you upgrade your PyGTK to 2.10 or greater.") -import gtk, gtk.glade + +# Install the twisted reactor +from twisted.internet import gtk2reactor +reactor = gtk2reactor.install() + import gobject import gettext import locale import pkg_resources import signal +import gtk, gtk.glade import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from mainwindow import MainWindow from menubar import MenuBar from toolbar import ToolBar @@ -82,7 +82,7 @@ DEFAULT_PREFS = { "enabled_plugins": [], "show_connection_manager_on_start": True, "autoconnect": False, - "autoconnect_host_uri": None, + "autoconnect_host_id": None, "autostart_localhost": False, "autoadd_queued": False, "autoadd_enable": False, @@ -183,8 +183,8 @@ class GtkUI: self.ipcinterface = IPCInterface(args) # We make sure that the UI components start once we get a core URI - client.connect_on_new_core(self._on_new_core) - client.connect_on_no_core(self._on_no_core) + client.register_event_handler("connected", self._on_new_core) + client.register_event_handler("disconnected", self._on_no_core) # Start the signal receiver self.signal_receiver = Signals() @@ -209,13 +209,13 @@ class GtkUI: # Show the connection manager self.connectionmanager = ConnectionManager() - if self.config["show_connection_manager_on_start"] and not self.config["classic_mode"]: - self.connectionmanager.show() + + reactor.callWhenRunning(self._on_reactor_start) # Start the gtk main loop try: gtk.gdk.threads_enter() - gtk.main() + reactor.run() gtk.gdk.threads_leave() except KeyboardInterrupt: self.shutdown() @@ -229,7 +229,7 @@ class GtkUI: component.shutdown() if self.started_in_classic: try: - client.daemon.shutdown(None) + client.daemon.shutdown() except: pass @@ -241,8 +241,15 @@ class GtkUI: except RuntimeError: pass - def _on_new_core(self, data): + def _on_reactor_start(self): + log.debug("_on_reactor_start") + # XXX: We need to call a simulate() here, but this could be a bug in twisted + reactor.simulate() + if self.config["show_connection_manager_on_start"] and not self.config["classic_mode"]: + self.connectionmanager.show() + + def _on_new_core(self): component.start() - def _on_no_core(self, data): + def _on_no_core(self): component.stop() diff --git a/deluge/ui/gtkui/ipcinterface.py b/deluge/ui/gtkui/ipcinterface.py index 4ed30b704..429370a6d 100644 --- a/deluge/ui/gtkui/ipcinterface.py +++ b/deluge/ui/gtkui/ipcinterface.py @@ -27,7 +27,7 @@ import sys import os.path import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -90,14 +90,14 @@ def process_args(args): component.get("AddTorrentDialog").add_from_url(arg) component.get("AddTorrentDialog").show(config["focus_add_dialog"]) else: - client.add_torrent_url(arg, None) + client.core.add_torrent_url(arg, None) elif deluge.common.is_magnet(arg): log.debug("Attempting to add %s from external source..", arg) if config["interactive_add"]: component.get("AddTorrentDialog").add_from_magnets([arg]) component.get("AddTorrentDialog").show(config["focus_add_dialog"]) else: - client.add_torrent_magnets([arg], []) + client.core.add_torrent_magnets([arg], []) else: # Just a file log.debug("Attempting to add %s from external source..", @@ -106,4 +106,4 @@ def process_args(args): component.get("AddTorrentDialog").add_from_files([os.path.abspath(arg)]) component.get("AddTorrentDialog").show(config["focus_add_dialog"]) else: - client.add_torrent_file([os.path.abspath(arg)]) + client.core.add_torrent_file([os.path.abspath(arg)]) diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 08306ad9a..da932aa90 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -31,7 +31,7 @@ import pkg_resources from urlparse import urlparse import urllib -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component from deluge.configmanager import ConfigManager from deluge.ui.gtkui.ipcinterface import process_args @@ -208,7 +208,7 @@ class MainWindow(component.Component): upload_rate = deluge.common.fspeed(status["upload_rate"]) self.window.set_title("Deluge - %s %s %s %s" % (_("Down:"), download_rate, _("Up:"), upload_rate)) if self.config["show_rate_in_title"]: - client.get_session_status(_on_get_session_status, ["download_rate", "upload_rate"]) + client.core.get_session_status(["download_rate", "upload_rate"]).addCallback(_on_get_session_status) def _on_set_show_rate_in_title(self, key, value): if value: diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index dfc030ee4..5c32c957e 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -30,7 +30,7 @@ import pkg_resources import deluge.error import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.common import deluge.ui.gtkui.common as common from deluge.configmanager import ConfigManager @@ -231,16 +231,13 @@ class MenuBar(component.Component): def on_menuitem_quitdaemon_activate(self, data=None): log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown - client.daemon.shutdown(None) + client.daemon.shutdown() self.window.quit() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") if self.config["classic_mode"]: - try: - client.daemon.shutdown(None) - except deluge.error.NoCoreError: - pass + client.daemon.shutdown() self.window.quit() ## Edit Menu ## @@ -255,17 +252,17 @@ class MenuBar(component.Component): ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") - client.pause_torrent( + client.core.pause_torrent( component.get("TorrentView").get_selected_torrents()) def on_menuitem_resume_activate(self, data=None): log.debug("on_menuitem_resume_activate") - client.resume_torrent( + client.core.resume_torrent( component.get("TorrentView").get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") - client.force_reannounce( + client.core.force_reannounce( component.get("TorrentView").get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): @@ -283,7 +280,7 @@ class MenuBar(component.Component): def on_menuitem_recheck_activate(self, data=None): log.debug("on_menuitem_recheck_activate") - client.force_recheck( + client.core.force_recheck( component.get("TorrentView").get_selected_torrents()) def on_menuitem_open_folder_activate(self, data=None): @@ -291,7 +288,7 @@ class MenuBar(component.Component): def _on_torrent_status(status): deluge.common.open_file(status["save_path"]) for torrent_id in component.get("TorrentView").get_selected_torrents(): - client.get_torrent_status(_on_torrent_status, torrent_id, ["save_path"]) + client.core.get_torrent_status(torrent_id, ["save_path"]).addCallback(_on_torrent_status) def on_menuitem_move_activate(self, data=None): log.debug("on_menuitem_move_activate") @@ -310,11 +307,11 @@ class MenuBar(component.Component): if chooser.run() == gtk.RESPONSE_OK: result = chooser.get_filename() config["choose_directory_dialog_path"] = result - client.move_storage( + client.core.move_storage( component.get("TorrentView").get_selected_torrents(), result) chooser.destroy() else: - client.get_torrent_status(self.show_move_storage_dialog, component.get("TorrentView").get_selected_torrent(), ["save_path"]) + client.core.get_torrent_status(component.get("TorrentView").get_selected_torrent(), ["save_path"]).addCallback(self.show_move_storage_dialog) client.force_call(False) def show_move_storage_dialog(self, status): @@ -330,26 +327,26 @@ class MenuBar(component.Component): if response_id == gtk.RESPONSE_OK: log.debug("Moving torrents to %s", entry.get_text()) path = entry.get_text() - client.move_storage(component.get("TorrentView").get_selected_torrents(), path) + client.core.move_storage(component.get("TorrentView").get_selected_torrents(), path) dialog.hide() dialog.connect("response", _on_response_event) dialog.show() def on_menuitem_queue_top_activate(self, value): log.debug("on_menuitem_queue_top_activate") - client.queue_top(None, component.get("TorrentView").get_selected_torrents()) + client.core.queue_top(None, component.get("TorrentView").get_selected_torrents()) def on_menuitem_queue_up_activate(self, value): log.debug("on_menuitem_queue_up_activate") - client.queue_up(None, component.get("TorrentView").get_selected_torrents()) + client.core.queue_up(None, component.get("TorrentView").get_selected_torrents()) def on_menuitem_queue_down_activate(self, value): log.debug("on_menuitem_queue_down_activate") - client.queue_down(None, component.get("TorrentView").get_selected_torrents()) + client.core.queue_down(None, component.get("TorrentView").get_selected_torrents()) def on_menuitem_queue_bottom_activate(self, value): log.debug("on_menuitem_queue_bottom_activate") - client.queue_bottom(None, component.get("TorrentView").get_selected_torrents()) + client.core.queue_bottom(None, component.get("TorrentView").get_selected_torrents()) ## View Menu ## def on_menuitem_toolbar_toggled(self, value): @@ -385,10 +382,10 @@ class MenuBar(component.Component): def on_menuitem_set_unlimited(self, widget): log.debug("widget.name: %s", widget.name) funcs = { - "menuitem_down_speed": client.set_torrent_max_download_speed, - "menuitem_up_speed": client.set_torrent_max_upload_speed, - "menuitem_max_connections": client.set_torrent_max_connections, - "menuitem_upload_slots": client.set_torrent_max_upload_slots + "menuitem_down_speed": client.core.set_torrent_max_download_speed, + "menuitem_up_speed": client.core.set_torrent_max_upload_speed, + "menuitem_max_connections": client.core.set_torrent_max_connections, + "menuitem_upload_slots": client.core.set_torrent_max_upload_slots } if widget.name in funcs.keys(): for torrent in component.get("TorrentView").get_selected_torrents(): @@ -397,10 +394,10 @@ class MenuBar(component.Component): def on_menuitem_set_other(self, widget): log.debug("widget.name: %s", widget.name) funcs = { - "menuitem_down_speed": client.set_torrent_max_download_speed, - "menuitem_up_speed": client.set_torrent_max_upload_speed, - "menuitem_max_connections": client.set_torrent_max_connections, - "menuitem_upload_slots": client.set_torrent_max_upload_slots + "menuitem_down_speed": client.core.set_torrent_max_download_speed, + "menuitem_up_speed": client.core.set_torrent_max_upload_speed, + "menuitem_max_connections": client.core.set_torrent_max_connections, + "menuitem_upload_slots": client.core.set_torrent_max_upload_slots } # widget: (header, type_str, image_stockid, image_filename, default) other_dialog_info = { @@ -418,11 +415,11 @@ class MenuBar(component.Component): def on_menuitem_set_automanaged_on(self, widget): for torrent in component.get("TorrentView").get_selected_torrents(): - client.set_torrent_auto_managed(torrent, True) + client.core.set_torrent_auto_managed(torrent, True) def on_menuitem_set_automanaged_off(self, widget): for torrent in component.get("TorrentView").get_selected_torrents(): - client.set_torrent_auto_managed(torrent, False) + client.core.set_torrent_auto_managed(torrent, False) def on_menuitem_sidebar_zero_toggled(self, widget): self.config["sidebar_show_zero"] = widget.get_active() diff --git a/deluge/ui/gtkui/notification.py b/deluge/ui/gtkui/notification.py index 50c980bfe..ffc709c1b 100644 --- a/deluge/ui/gtkui/notification.py +++ b/deluge/ui/gtkui/notification.py @@ -28,7 +28,7 @@ import deluge.common import deluge.ui.gtkui.common as common from deluge.log import LOG as log from deluge.configmanager import ConfigManager -from deluge.ui.client import aclient as client +from deluge.ui.client import client class Notification: def __init__(self): @@ -42,7 +42,7 @@ class Notification: self.get_torrent_status(torrent_id) def get_torrent_status(self, torrent_id): - client.get_torrent_status(self._on_get_torrent_status, torrent_id, ["name", "num_files", "total_payload_download"]) + client.core.get_torrent_status(torrent_id, ["name", "num_files", "total_payload_download"]).addCallback(self._on_get_torrent_status) def _on_get_torrent_status(self, status): if status is None: @@ -122,4 +122,3 @@ class Notification: log.warning("sending email notification of finished torrent failed") else: log.info("sending email notification of finished torrent was successful") - diff --git a/deluge/ui/gtkui/options_tab.py b/deluge/ui/gtkui/options_tab.py index bbdca6e3d..a687bdea9 100644 --- a/deluge/ui/gtkui/options_tab.py +++ b/deluge/ui/gtkui/options_tab.py @@ -24,7 +24,7 @@ import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.ui.gtkui.torrentdetails import Tab class OptionsTab(Tab): @@ -87,7 +87,7 @@ class OptionsTab(Tab): if torrent_id != self.prev_torrent_id: self.prev_status = None - client.get_torrent_status(self._on_get_torrent_status, torrent_id, + client.core.get_torrent_status(torrent_id, ["max_download_speed", "max_upload_speed", "max_connections", @@ -99,7 +99,7 @@ class OptionsTab(Tab): "stop_ratio", "remove_at_ratio", "move_on_completed", - "move_on_completed_path"]) + "move_on_completed_path"]).addCallback(self._on_get_torrent_status) self.prev_torrent_id = torrent_id def clear(self): @@ -147,31 +147,31 @@ class OptionsTab(Tab): def _on_button_apply_clicked(self, button): if self.spin_max_download.get_value() != self.prev_status["max_download_speed"]: - client.set_torrent_max_download_speed(self.prev_torrent_id, self.spin_max_download.get_value()) + client.core.set_torrent_max_download_speed(self.prev_torrent_id, self.spin_max_download.get_value()) if self.spin_max_upload.get_value() != self.prev_status["max_upload_speed"]: - client.set_torrent_max_upload_speed(self.prev_torrent_id, self.spin_max_upload.get_value()) + client.core.set_torrent_max_upload_speed(self.prev_torrent_id, self.spin_max_upload.get_value()) if self.spin_max_connections.get_value_as_int() != self.prev_status["max_connections"]: - client.set_torrent_max_connections(self.prev_torrent_id, self.spin_max_connections.get_value_as_int()) + client.core.set_torrent_max_connections(self.prev_torrent_id, self.spin_max_connections.get_value_as_int()) if self.spin_max_upload_slots.get_value_as_int() != self.prev_status["max_upload_slots"]: - client.set_torrent_max_upload_slots(self.prev_torrent_id, self.spin_max_upload_slots.get_value_as_int()) + client.core.set_torrent_max_upload_slots(self.prev_torrent_id, self.spin_max_upload_slots.get_value_as_int()) if self.chk_prioritize_first_last.get_active() != self.prev_status["prioritize_first_last"]: - client.set_torrent_prioritize_first_last(self.prev_torrent_id, self.chk_prioritize_first_last.get_active()) + client.core.set_torrent_prioritize_first_last(self.prev_torrent_id, self.chk_prioritize_first_last.get_active()) if self.chk_auto_managed.get_active() != self.prev_status["is_auto_managed"]: - client.set_torrent_auto_managed(self.prev_torrent_id, self.chk_auto_managed.get_active()) + client.core.set_torrent_auto_managed(self.prev_torrent_id, self.chk_auto_managed.get_active()) if self.chk_stop_at_ratio.get_active() != self.prev_status["stop_at_ratio"]: - client.set_torrent_stop_at_ratio(self.prev_torrent_id, self.chk_stop_at_ratio.get_active()) + client.core.set_torrent_stop_at_ratio(self.prev_torrent_id, self.chk_stop_at_ratio.get_active()) if self.spin_stop_ratio.get_value() != self.prev_status["stop_ratio"]: - client.set_torrent_stop_ratio(self.prev_torrent_id, self.spin_stop_ratio.get_value()) + client.core.set_torrent_stop_ratio(self.prev_torrent_id, self.spin_stop_ratio.get_value()) if self.chk_remove_at_ratio.get_active() != self.prev_status["remove_at_ratio"]: - client.set_torrent_remove_at_ratio(self.prev_torrent_id, self.chk_remove_at_ratio.get_active()) + client.core.set_torrent_remove_at_ratio(self.prev_torrent_id, self.chk_remove_at_ratio.get_active()) if self.chk_move_completed.get_active() != self.prev_status["move_on_completed"]: - client.set_torrent_move_on_completed(self.prev_torrent_id, self.chk_move_completed.get_active()) + client.core.set_torrent_move_on_completed(self.prev_torrent_id, self.chk_move_completed.get_active()) if self.chk_move_completed.get_active(): if client.is_localhost(): path = self.filechooser_move_completed.get_current_folder() else: path = self.entry_move_completed.get_text() - client.set_torrent_move_on_completed_path(self.prev_torrent_id, path) + client.core.set_torrent_move_on_completed_path(self.prev_torrent_id, path) def _on_button_edit_trackers_clicked(self, button): diff --git a/deluge/ui/gtkui/peers_tab.py b/deluge/ui/gtkui/peers_tab.py index d37c0b68c..6a07059ca 100644 --- a/deluge/ui/gtkui/peers_tab.py +++ b/deluge/ui/gtkui/peers_tab.py @@ -30,7 +30,7 @@ import pkg_resources import gobject from itertools import izip -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.configmanager import ConfigManager import deluge.configmanager import deluge.component as component @@ -244,7 +244,7 @@ class PeersTab(Tab): self.peers = {} self.torrent_id = torrent_id - client.get_torrent_status(self._on_get_torrent_status, torrent_id, ["peers"]) + client.core.get_torrent_status(torrent_id, ["peers"]).addCallback(self._on_get_torrent_status) def get_flag_pixbuf(self, country): if country == " ": diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 6f25d9499..cb237ec7e 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -25,7 +25,7 @@ import deluge.component as component import deluge.pluginmanagerbase -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -59,7 +59,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, def start(self): """Start the plugin manager""" # Update the enabled_plugins from the core - client.get_enabled_plugins(self._on_get_enabled_plugins) + client.core.get_enabled_plugins().addCallback(self._on_get_enabled_plugins) def stop(self): # Disable the plugins diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 75a4f0b62..95a356eda 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -30,7 +30,7 @@ import pkg_resources import deluge.component as component from deluge.log import LOG as log -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.common import deluge.error import deluge.ui.gtkui.common as common @@ -152,6 +152,7 @@ class Preferences(component.Component): self.liststore.remove(self.iter_to_remove) def _on_get_config(self, config): + log.debug("on_get_config: %s", config) self.core_config = config def _on_get_available_plugins(self, plugins): @@ -174,10 +175,10 @@ class Preferences(component.Component): # Update the preferences dialog to reflect current config settings self.core_config = {} try: - client.get_config(self._on_get_config) - client.get_available_plugins(self._on_get_available_plugins) - client.get_enabled_plugins(self._on_get_enabled_plugins) - client.get_listen_port(self._on_get_listen_port) + client.core.get_config().addCallback(self._on_get_config) + client.core.get_available_plugins().addCallback(self._on_get_available_plugins) + client.core.get_enabled_plugins().addCallback(self._on_get_enabled_plugins) + client.core.get_listen_port().addCallback(self._on_get_listen_port) # Force these calls and block until we've done them all client.force_call() except deluge.error.NoCoreError: @@ -704,7 +705,7 @@ class Preferences(component.Component): self.gtkui_config[key] = new_gtkui_config[key] # Core - if client.get_core_uri() != None: + if client.connected(): # Only do this if we're connected to a daemon config_to_set = {} for key in new_core_config.keys(): @@ -713,7 +714,7 @@ class Preferences(component.Component): config_to_set[key] = new_core_config[key] # Set each changed config value in the core - client.set_config(config_to_set) + client.core.set_config(config_to_set) client.force_call(True) # Update the configuration self.core_config.update(config_to_set) @@ -801,7 +802,7 @@ class Preferences(component.Component): else: self.glade.get_widget("port_img").set_from_stock(gtk.STOCK_DIALOG_WARNING, 4) self.glade.get_widget("port_img").show() - client.test_listen_port(on_get_test) + client.core.test_listen_port().addCallback(on_get_test) client.force_call() def on_plugin_toggled(self, renderer, path): @@ -811,10 +812,10 @@ class Preferences(component.Component): value = self.plugin_liststore.get_value(row, 1) self.plugin_liststore.set_value(row, 1, not value) if not value: - client.enable_plugin(name) + client.core.enable_plugin(name) component.get("PluginManager").enable_plugin(name) else: - client.disable_plugin(name) + client.core.disable_plugin(name) component.get("PluginManager").disable_plugin(name) def on_plugin_selection_changed(self, treeselection): @@ -867,16 +868,16 @@ class Preferences(component.Component): if not client.is_localhost(): # We need to send this plugin to the daemon - client.upload_plugin( + client.core.upload_plugin( filename, xmlrpclib.Binary(open(filepath, "rb").read())) - client.rescan_plugins() + client.core.rescan_plugins() chooser.destroy() # We need to re-show the preferences dialog to show the new plugins self.show() def _on_button_rescan_plugins_clicked(self, widget): component.get("PluginManager").scan_for_plugins() - client.rescan_plugins() + client.core.rescan_plugins() self.show() diff --git a/deluge/ui/gtkui/queuedtorrents.py b/deluge/ui/gtkui/queuedtorrents.py index 22f8423e3..f4c42d0df 100644 --- a/deluge/ui/gtkui/queuedtorrents.py +++ b/deluge/ui/gtkui/queuedtorrents.py @@ -30,7 +30,7 @@ import gobject import pkg_resources import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -166,19 +166,19 @@ class QueuedTorrents(component.Component): component.get("AddTorrentDialog").add_from_url(torrent_path) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) else: - client.add_torrent_url(torrent_path, None) + client.core.add_torrent_url(torrent_path, None) elif deluge.common.is_magnet(torrent_path): if self.config["interactive_add"]: component.get("AddTorrentDialog").add_from_magnets([torrent_path]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) else: - client.add_magnet_uris([torrent_path], []) + client.core.add_magnet_uris([torrent_path], []) else: if self.config["interactive_add"]: component.get("AddTorrentDialog").add_from_files([torrent_path]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) else: - client.add_torrent_file([torrent_path]) + client.core.add_torrent_file([torrent_path]) self.liststore.foreach(add_torrent, None) del self.queue[:] @@ -187,5 +187,3 @@ class QueuedTorrents(component.Component): def on_chk_autoadd_toggled(self, widget): self.config["autoadd_queued"] = widget.get_active() - - diff --git a/deluge/ui/gtkui/removetorrentdialog.py b/deluge/ui/gtkui/removetorrentdialog.py index 89ec6a4a6..1931491b3 100644 --- a/deluge/ui/gtkui/removetorrentdialog.py +++ b/deluge/ui/gtkui/removetorrentdialog.py @@ -25,7 +25,7 @@ import gtk, gtk.glade import pkg_resources -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component from deluge.log import LOG as log @@ -71,7 +71,7 @@ class RemoveTorrentDialog(object): button_data.set_label(pluralize_torrents(button_data.get_label())) def __remove_torrents(self, remove_data): - client.remove_torrent(self.__torrent_ids, remove_data) + client.core.remove_torrent(self.__torrent_ids, remove_data) # Unselect all to avoid issues with the selection changed event component.get("TorrentView").treeview.get_selection().unselect_all() diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index d33fd83c9..8de93ebf3 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -26,7 +26,7 @@ import gtk import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.ui.signalreceiver import SignalReceiver from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -34,12 +34,13 @@ from deluge.log import LOG as log class Signals(component.Component): def __init__(self): component.Component.__init__(self, "Signals") - self.receiver = SignalReceiver() + # self.receiver = SignalReceiver() self.config = ConfigManager("gtkui.conf") - self.config["signal_port"] = self.receiver.get_port() + #self.config["signal_port"] = self.receiver.get_port() self.config.save() def start(self): + return self.receiver.set_remote(not client.is_localhost()) self.receiver.run() @@ -70,6 +71,7 @@ class Signals(component.Component): self.torrent_finished) def stop(self): + return try: self.receiver.shutdown() except: @@ -77,7 +79,8 @@ class Signals(component.Component): def connect_to_signal(self, signal, callback): """Connects a callback to a signal""" - self.receiver.connect_to_signal(signal, callback) + #self.receiver.connect_to_signal(signal, callback) + pass def torrent_finished(self, torrent_id): log.debug("torrent_finished signal received..") diff --git a/deluge/ui/gtkui/status_tab.py b/deluge/ui/gtkui/status_tab.py index 0b341bae5..9dd8297a5 100644 --- a/deluge/ui/gtkui/status_tab.py +++ b/deluge/ui/gtkui/status_tab.py @@ -26,7 +26,7 @@ import gtk, gtk.glade -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.component as component import deluge.common from deluge.ui.gtkui.torrentdetails import Tab @@ -105,8 +105,8 @@ class StatusTab(Tab): "max_upload_speed", "max_download_speed", "active_time", "seeding_time", "seed_rank", "is_auto_managed", "time_added"] - client.get_torrent_status( - self._on_get_torrent_status, selected, status_keys) + client.core.get_torrent_status( + selected, status_keys).addCallback(self._on_get_torrent_status) def _on_get_torrent_status(self, status): # Check to see if we got valid data from the core diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 616116382..5bcf2dea0 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -26,11 +26,11 @@ import gtk import gobject +from deluge.ui.client import client import deluge.component as component import deluge.common import deluge.ui.gtkui.common as common from deluge.configmanager import ConfigManager -from deluge.ui.client import aclient as client from deluge.log import LOG as log class StatusBarItem: @@ -170,15 +170,14 @@ class StatusBar(component.Component): self.health = False # Get some config values - client.get_config_value( - self._on_max_connections_global, "max_connections_global") - client.get_config_value( - self._on_max_download_speed, "max_download_speed") - client.get_config_value( - self._on_max_upload_speed, "max_upload_speed") - client.get_config_value( - self._on_dht, "dht") - client.get_health(self._on_get_health) + client.core.get_config_value( + "max_connections_global").addCallback(self._on_max_connections_global) + client.core.get_config_value( + "max_download_speed").addCallback(self._on_max_download_speed) + client.core.get_config_value( + "max_upload_speed").addCallback(self._on_max_upload_speed) + client.core.get_config_value("dht").addCallback(self._on_dht) + client.core.get_health().addCallback(self._on_get_health) self.send_status_request() @@ -250,15 +249,18 @@ class StatusBar(component.Component): def send_status_request(self): # Sends an async request for data from the core - client.get_num_connections(self._on_get_num_connections) + client.core.get_num_connections().addCallback(self._on_get_num_connections) if self.dht_status: - client.get_dht_nodes(self._on_get_dht_nodes) - client.get_session_status(self._on_get_session_status, - ["upload_rate", "download_rate", "payload_upload_rate", "payload_download_rate"]) + client.core.get_dht_nodes().addCallback(self._on_get_dht_nodes) + client.core.get_session_status([ + "upload_rate", + "download_rate", + "payload_upload_rate", + "payload_download_rate"]).addCallback(self._on_get_session_status) if not self.health: # Only request health status while False - client.get_health(self._on_get_health) + client.core.get_health().addCallback(self._on_get_health) def config_value_changed(self, key, value): """This is called when we received a config_value_changed signal from @@ -284,7 +286,7 @@ class StatusBar(component.Component): if value: self.hbox.pack_start( self.dht_item.get_eventbox(), expand=False, fill=False) - client.get_dht_nodes(self._on_get_dht_nodes) + client.core.get_dht_nodes().addCallback(self._on_get_dht_nodes) else: self.remove_item(self.dht_item) @@ -377,7 +379,7 @@ class StatusBar(component.Component): # Set the config in the core if value != self.max_download_speed: - client.set_config({"max_download_speed": value}) + client.core.set_config({"max_download_speed": value}) def _on_upload_item_clicked(self, widget, event): menu = common.build_menu_radio_list( @@ -405,7 +407,7 @@ class StatusBar(component.Component): # Set the config in the core if value != self.max_upload_speed: - client.set_config({"max_upload_speed": value}) + client.core.set_config({"max_upload_speed": value}) def _on_connection_item_clicked(self, widget, event): menu = common.build_menu_radio_list( @@ -432,7 +434,7 @@ class StatusBar(component.Component): # Set the config in the core if value != self.max_connections: - client.set_config({"max_connections_global": value}) + client.core.set_config({"max_connections_global": value}) def _on_health_icon_clicked(self, widget, event): component.get("Preferences").show("Network") diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 4465737ad..714734dd3 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -27,7 +27,7 @@ import gtk import pkg_resources import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -113,7 +113,7 @@ class SystemTray(component.Component): self.tray_glade.get_widget("separatormenuitem4").hide() component.get("Signals").connect_to_signal("config_value_changed", self.config_value_changed) - if client.get_core_uri() == None: + if not client.connected(): # Hide menu widgets because we're not connected to a host. for widget in self.hide_widget_list: self.tray_glade.get_widget(widget).hide() @@ -132,10 +132,10 @@ class SystemTray(component.Component): self.build_tray_bwsetsubmenu() # Get some config values - client.get_config_value( - self._on_max_download_speed, "max_download_speed") - client.get_config_value( - self._on_max_upload_speed, "max_upload_speed") + client.core.get_config_value( + "max_download_speed").addCallback(self._on_max_download_speed) + client.core.get_config_value( + "max_upload_speed").addCallback(self._on_max_upload_speed) self.send_status_request() def start(self): @@ -153,8 +153,8 @@ class SystemTray(component.Component): self.tray.set_visible(False) def send_status_request(self): - client.get_download_rate(self._on_get_download_rate) - client.get_upload_rate(self._on_get_upload_rate) + client.core.get_download_rate().addCallback(self._on_get_download_rate) + client.core.get_upload_rate().addCallback(self._on_get_upload_rate) def config_value_changed(self, key, value): """This is called when we received a config_value_changed signal from @@ -291,15 +291,15 @@ class SystemTray(component.Component): def on_menuitem_add_torrent_activate(self, menuitem): log.debug("on_menuitem_add_torrent_activate") from addtorrentdialog import AddTorrentDialog - client.add_torrent_file(AddTorrentDialog().show()) + client.core.add_torrent_file(AddTorrentDialog().show()) def on_menuitem_pause_all_activate(self, menuitem): log.debug("on_menuitem_pause_all_activate") - client.pause_all_torrents() + client.core.pause_all_torrents() def on_menuitem_resume_all_activate(self, menuitem): log.debug("on_menuitem_resume_all_activate") - client.resume_all_torrents() + client.core.resume_all_torrents() def on_menuitem_quit_activate(self, menuitem): log.debug("on_menuitem_quit_activate") @@ -308,7 +308,7 @@ class SystemTray(component.Component): return if self.config["classic_mode"]: - client.daemon.shutdown(None) + client.daemon.shutdown() self.window.quit() @@ -318,7 +318,7 @@ class SystemTray(component.Component): if not self.unlock_tray(): return - client.daemon.shutdown(None) + client.daemon.shutdown() self.window.quit() def tray_setbwdown(self, widget, data=None): @@ -341,7 +341,7 @@ class SystemTray(component.Component): return # Set the config in the core - client.set_config({core_key: value}) + client.core.set_config({core_key: value}) self.build_tray_bwsetsubmenu() diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index d8c91159d..b78af1751 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -31,7 +31,6 @@ import gobject import deluge.component as component from deluge.log import LOG as log from deluge.common import TORRENT_STATE -from deluge.ui.client import aclient as client from deluge.configmanager import ConfigManager class ToolBar(component.Component): diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index ed3f55a98..a4cac9f57 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -31,7 +31,7 @@ import os.path import cPickle import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.configmanager import ConfigManager import deluge.configmanager diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 4ea9cfaed..86e0ee841 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -34,7 +34,7 @@ from urlparse import urlparse import deluge.common import deluge.component as component -from deluge.ui.client import aclient as client +from deluge.ui.client import client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview @@ -197,9 +197,10 @@ class TorrentView(listview.ListView, component.Component): """Start the torrentview""" # We need to get the core session state to know which torrents are in # the session so we can add them to our list. - client.get_session_state(self._on_session_state) + client.core.get_session_state().addCallback(self._on_session_state) def _on_session_state(self, state): + log.debug("on_session_state") self.treeview.freeze_child_notify() model = self.treeview.get_model() for torrent_id in state: @@ -259,8 +260,8 @@ class TorrentView(listview.ListView, component.Component): # Request the statuses for all these torrent_ids, this is async so we # will deal with the return in a signal callback. - client.get_torrents_status( - self._on_get_torrents_status, self.filter, status_keys) + client.core.get_torrents_status( + self.filter, status_keys).addCallback(self._on_get_torrents_status) def update(self): # Send a status request diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 4353e5b59..865d49e3a 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -29,7 +29,7 @@ import random import gobject -from deluge.ui.client import aclient as client +from deluge.ui.client import client import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import deluge.xmlrpclib as xmlrpclib @@ -149,4 +149,3 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): self.signals[signal].append(callback) except KeyError: self.signals[signal] = [callback] - diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 7792542ad..01a8db7c3 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -61,6 +61,7 @@ class UI: log.info("Starting ConsoleUI..") from deluge.ui.console.main import ConsoleUI ui = ConsoleUI(ui_args).run() - except ImportError: + except ImportError, e: + log.exception(e) log.error("Unable to find the requested UI: %s. Please select a different UI with the '-u' option or alternatively use the '-s' option to select a different default UI.", selected_ui) sys.exit(0)