Implement new DelugeRPC method to replace XMLRPC.

This commit breaks _a lot_ of things including the web and console UIs, please do not expect 
anything to work.
This commit is contained in:
Andrew Resch 2009-01-27 08:03:39 +00:00
parent ce1b7b491d
commit 42588656fd
43 changed files with 1814 additions and 1043 deletions

View File

@ -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"):

View File

@ -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)

View File

@ -1,7 +1,7 @@
#
# authmanager.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# 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)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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():

View File

@ -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)

View File

@ -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]

View File

@ -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

433
deluge/rencode.py Normal file
View File

@ -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<abs(loads(dumps(1.1))-1.1)<1e-6
assert 1e-10<abs(loads(dumps(1.1,32))-1.1)<1e-6
assert abs(loads(dumps(1.1,64))-1.1)<1e-12
assert loads(dumps(u"Hello World!!"))
try:
import psyco
psyco.bind(dumps)
psyco.bind(loads)
except ImportError:
pass
if __name__ == '__main__':
test()

View File

@ -1,8 +1,7 @@
#
# client.py
#
# Copyright (C) 2007/2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# 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()

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -1,7 +1,7 @@
#
# connectionmanager.py
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
# 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()

View File

@ -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

View File

@ -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)

View File

@ -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("")

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -1,9 +1,12 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Mon Jan 26 23:25:11 2009 -->
<glade-interface>
<widget class="GtkDialog" id="addhost_dialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Add Host</property>
<property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
@ -199,6 +202,7 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Connection Manager</property>
<property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="default_width">350</property>
<property name="default_height">300</property>
@ -267,6 +271,7 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<signal name="row_activated" handler="on_hostlist_row_activated"/>
</widget>
</child>
</widget>
@ -315,6 +320,22 @@
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button_refresh">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-refresh</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_refresh_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button_startdaemon">
<property name="visible">True</property>
@ -445,10 +466,9 @@
<property name="position">3</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<child>
<widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button_close">
@ -458,7 +478,7 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-close</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
<property name="response_id">-7</property>
<signal name="clicked" handler="on_button_close_clicked"/>
</widget>
<packing>
@ -471,7 +491,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">gtk-connect</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
@ -486,6 +505,19 @@
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="sensitive">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>

View File

@ -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()

View File

@ -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)])

View File

@ -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:

View File

@ -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()

View File

@ -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")

View File

@ -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):

View File

@ -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 == " ":

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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..")

View File

@ -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

View File

@ -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")

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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)