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
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): if self.depend.has_key(name):
for depend in self.depend[name]: for depend in self.depend[name]:
self.start_component(depend) self.start_component(depend)
# Only start if the component is stopped. # Only start if the component is stopped.
if self.components[name].get_state() == \ if self.components[name].get_state() == \
COMPONENT_STATE.index("Stopped"): COMPONENT_STATE.index("Stopped"):

View file

@ -25,7 +25,7 @@
"""The AlertManager handles all the libtorrent alerts.""" """The AlertManager handles all the libtorrent alerts."""
import gobject from twisted.internet import reactor
import deluge.component as component import deluge.component as component
try: try:
@ -97,7 +97,7 @@ class AlertManager(component.Component):
if alert_type in self.handlers.keys(): if alert_type in self.handlers.keys():
for handler in self.handlers[alert_type]: for handler in self.handlers[alert_type]:
if not wait: if not wait:
gobject.idle_add(handler, alert) reactor.callLater(0, handler, alert)
else: else:
handler(alert) handler(alert)

View file

@ -1,7 +1,7 @@
# #
# authmanager.py # authmanager.py
# #
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -29,16 +29,25 @@ import stat
import deluge.component as component import deluge.component as component
import deluge.configmanager as configmanager 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): class AuthManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "AuthManager") component.Component.__init__(self, "AuthManager")
self.auth = {} self.__auth = {}
def start(self): def start(self):
self.__load_auth_file() self.__load_auth_file()
def stop(self): def stop(self):
self.auth = {} self.__auth = {}
def shutdown(self): def shutdown(self):
pass pass
@ -49,21 +58,22 @@ class AuthManager(component.Component):
:param username: str, username :param username: str, username
:param password: str, password :param password: str, password
:returns: True or False :returns: int, the auth level for this user or 0 if not able to authenticate
:rtype: bool :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 # Let's try to re-load the file.. Maybe it's been updated
self.__load_auth_file() self.__load_auth_file()
if username not in self.auth: if username not in self.__auth:
return False return 0
if self.auth[username] == password: if self.__auth[username][0] == password:
return True # Return the users auth level
return self.__auth[username][1]
return False return 0
def __load_auth_file(self): def __load_auth_file(self):
auth_file = configmanager.get_config_dir("auth") auth_file = configmanager.get_config_dir("auth")
@ -74,7 +84,7 @@ class AuthManager(component.Component):
from hashlib import sha1 as sha_hash from hashlib import sha1 as sha_hash
except ImportError: except ImportError:
from sha import new as sha_hash 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 # Change the permissions on the file so only this user can read/write it
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE) os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
@ -85,7 +95,18 @@ class AuthManager(component.Component):
# This is a comment line # This is a comment line
continue continue
try: try:
username, password = line.split(":") lsplit = line.split(":")
except ValueError: except Exception, e:
log.error("Your auth file is malformed: %s", e)
continue 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 threading
import pkg_resources import pkg_resources
import gobject from twisted.internet import reactor
from twisted.internet.task import LoopingCall
try: try:
import deluge.libtorrent as lt import deluge.libtorrent as lt
@ -49,7 +50,7 @@ from deluge.core.filtermanager import FilterManager
from deluge.core.preferencesmanager import PreferencesManager from deluge.core.preferencesmanager import PreferencesManager
from deluge.core.autoadd import AutoAdd from deluge.core.autoadd import AutoAdd
from deluge.core.authmanager import AuthManager 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 from deluge.log import LOG as log
@ -214,12 +215,12 @@ class Core(component.Component):
return False return False
# Exported Methods # Exported Methods
@export @export()
def ping(self): def ping(self):
"""A method to see if the core is running""" """A method to see if the core is running"""
return True return True
@export @export()
def register_client(self, port): def register_client(self, port):
"""Registers a client with the signal manager so that signals are """Registers a client with the signal manager so that signals are
sent to it.""" sent to it."""
@ -227,17 +228,17 @@ class Core(component.Component):
if self.config["new_release_check"]: if self.config["new_release_check"]:
self.check_new_release() self.check_new_release()
@export @export()
def deregister_client(self): def deregister_client(self):
"""De-registers a client with the signal manager.""" """De-registers a client with the signal manager."""
self.signalmanager.deregister_client(component.get("RPCServer").client_address) self.signalmanager.deregister_client(component.get("RPCServer").client_address)
@export @export()
def add_torrent_file(self, filename, filedump, options): def add_torrent_file(self, filename, filedump, options):
"""Adds a torrent file to the libtorrent session """Adds a torrent file to the libtorrent session
This requires the torrents filename and a dump of it's content 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): def _add_torrent_file(self, filename, filedump, options):
# Turn the filedump into a torrent_info # Turn the filedump into a torrent_info
@ -259,7 +260,7 @@ class Core(component.Component):
# Run the plugin hooks for 'post_torrent_add' # Run the plugin hooks for 'post_torrent_add'
self.pluginmanager.run_post_torrent_add(torrent_id) self.pluginmanager.run_post_torrent_add(torrent_id)
@export @export()
def get_stats(self): def get_stats(self):
""" """
document me!!! document me!!!
@ -279,7 +280,7 @@ class Core(component.Component):
return stats return stats
@export @export()
def get_session_status(self, keys): def get_session_status(self, keys):
""" """
Gets the session status values for 'keys' Gets the session status values for 'keys'
@ -296,7 +297,7 @@ class Core(component.Component):
return status return status
@export @export()
def add_torrent_url(self, url, options): def add_torrent_url(self, url, options):
log.info("Attempting to add url %s", url) log.info("Attempting to add url %s", url)
@ -321,7 +322,7 @@ class Core(component.Component):
# Add the torrent to session # Add the torrent to session
return callback(filename, filedump, options) return callback(filename, filedump, options)
@export @export()
def add_torrent_magnets(self, uris, options): def add_torrent_magnets(self, uris, options):
for uri in uris: for uri in uris:
log.debug("Attempting to add by magnet uri: %s", uri) 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' # Run the plugin hooks for 'post_torrent_add'
self.pluginmanager.run_post_torrent_add(torrent_id) self.pluginmanager.run_post_torrent_add(torrent_id)
@export @export()
def remove_torrent(self, torrent_ids, remove_data): def remove_torrent(self, torrent_ids, remove_data):
log.debug("Removing torrent %s from the core.", torrent_ids) log.debug("Removing torrent %s from the core.", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
@ -343,57 +344,57 @@ class Core(component.Component):
# Run the plugin hooks for 'post_torrent_remove' # Run the plugin hooks for 'post_torrent_remove'
self.pluginmanager.run_post_torrent_remove(torrent_id) self.pluginmanager.run_post_torrent_remove(torrent_id)
@export @export()
def force_reannounce(self, torrent_ids): def force_reannounce(self, torrent_ids):
log.debug("Forcing reannouncment to: %s", torrent_ids) log.debug("Forcing reannouncment to: %s", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].force_reannounce() self.torrentmanager[torrent_id].force_reannounce()
@export @export()
def pause_torrent(self, torrent_ids): def pause_torrent(self, torrent_ids):
log.debug("Pausing: %s", torrent_ids) log.debug("Pausing: %s", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
if not self.torrentmanager[torrent_id].pause(): if not self.torrentmanager[torrent_id].pause():
log.warning("Error pausing torrent %s", torrent_id) log.warning("Error pausing torrent %s", torrent_id)
@export @export()
def connect_peer(self, torrent_id, ip, port): def connect_peer(self, torrent_id, ip, port):
log.debug("adding peer %s to %s", ip, torrent_id) log.debug("adding peer %s to %s", ip, torrent_id)
if not self.torrentmanager[torrent_id].connect_peer(ip, port): if not self.torrentmanager[torrent_id].connect_peer(ip, port):
log.warning("Error adding peer %s:%s to %s", ip, port, torrent_id) log.warning("Error adding peer %s:%s to %s", ip, port, torrent_id)
@export @export()
def move_storage(self, torrent_ids, dest): def move_storage(self, torrent_ids, dest):
log.debug("Moving storage %s to %s", torrent_ids, dest) log.debug("Moving storage %s to %s", torrent_ids, dest)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
if not self.torrentmanager[torrent_id].move_storage(dest): if not self.torrentmanager[torrent_id].move_storage(dest):
log.warning("Error moving torrent %s to %s", torrent_id, dest) log.warning("Error moving torrent %s to %s", torrent_id, dest)
@export @export()
def pause_all_torrents(self): def pause_all_torrents(self):
"""Pause all torrents in the session""" """Pause all torrents in the session"""
self.session.pause() self.session.pause()
@export @export()
def resume_all_torrents(self): def resume_all_torrents(self):
"""Resume all torrents in the session""" """Resume all torrents in the session"""
self.session.resume() self.session.resume()
self.signalmanager.emit("torrent_all_resumed") self.signalmanager.emit("torrent_all_resumed")
@export @export()
def resume_torrent(self, torrent_ids): def resume_torrent(self, torrent_ids):
log.debug("Resuming: %s", torrent_ids) log.debug("Resuming: %s", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].resume() self.torrentmanager[torrent_id].resume()
@export @export()
def get_status_keys(self): def get_status_keys(self):
""" """
returns all possible keys for the keys argument in get_torrent(s)_status. returns all possible keys for the keys argument in get_torrent(s)_status.
""" """
return STATUS_KEYS + self.pluginmanager.status_fields.keys() return STATUS_KEYS + self.pluginmanager.status_fields.keys()
@export @export()
def get_torrent_status(self, torrent_id, keys): def get_torrent_status(self, torrent_id, keys):
# Build the status dictionary # Build the status dictionary
status = self.torrentmanager[torrent_id].get_status(keys) 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)) status.update(self.pluginmanager.get_status(torrent_id, leftover_fields))
return status return status
@export @export()
def get_torrents_status(self, filter_dict, keys): def get_torrents_status(self, filter_dict, keys):
""" """
returns all torrents , optionally filtered by filter_dict. returns all torrents , optionally filtered by filter_dict.
@ -418,7 +419,7 @@ class Core(component.Component):
return status_dict return status_dict
@export @export()
def get_filter_tree(self , show_zero_hits=True, hide_cat=None): def get_filter_tree(self , show_zero_hits=True, hide_cat=None):
""" """
returns {field: [(value,count)] } returns {field: [(value,count)] }
@ -426,18 +427,18 @@ class Core(component.Component):
""" """
return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat) return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat)
@export @export()
def get_session_state(self): def get_session_state(self):
"""Returns a list of torrent_ids in the session.""" """Returns a list of torrent_ids in the session."""
# Get the torrent list from the TorrentManager # Get the torrent list from the TorrentManager
return self.torrentmanager.get_torrent_list() return self.torrentmanager.get_torrent_list()
@export @export()
def get_config(self): def get_config(self):
"""Get all the preferences as a dictionary""" """Get all the preferences as a dictionary"""
return self.config.config return self.config.config
@export @export()
def get_config_value(self, key): def get_config_value(self, key):
"""Get the config value for key""" """Get the config value for key"""
try: try:
@ -447,7 +448,7 @@ class Core(component.Component):
return value return value
@export @export()
def get_config_values(self, keys): def get_config_values(self, keys):
"""Get the config values for the entered keys""" """Get the config values for the entered keys"""
config = {} config = {}
@ -458,7 +459,7 @@ class Core(component.Component):
pass pass
return config return config
@export @export()
def set_config(self, config): def set_config(self, config):
"""Set the config with values from dictionary""" """Set the config with values from dictionary"""
# Load all the values into the configuration # Load all the values into the configuration
@ -467,156 +468,158 @@ class Core(component.Component):
config[key] = config[key].encode("utf8") config[key] = config[key].encode("utf8")
self.config[key] = config[key] self.config[key] = config[key]
@export @export()
def get_listen_port(self): def get_listen_port(self):
"""Returns the active listen port""" """Returns the active listen port"""
return self.session.listen_port() return self.session.listen_port()
@export @export()
def get_num_connections(self): def get_num_connections(self):
"""Returns the current number of connections""" """Returns the current number of connections"""
return self.session.num_connections() return self.session.num_connections()
@export @export()
def get_dht_nodes(self): def get_dht_nodes(self):
"""Returns the number of dht nodes""" """Returns the number of dht nodes"""
return self.session.status().dht_nodes return self.session.status().dht_nodes
@export @export()
def get_download_rate(self): def get_download_rate(self):
"""Returns the payload download rate""" """Returns the payload download rate"""
return self.session.status().payload_download_rate return self.session.status().payload_download_rate
@export @export()
def get_upload_rate(self): def get_upload_rate(self):
"""Returns the payload upload rate""" """Returns the payload upload rate"""
return self.session.status().payload_upload_rate return self.session.status().payload_upload_rate
@export @export()
def get_available_plugins(self): def get_available_plugins(self):
"""Returns a list of plugins available in the core""" """Returns a list of plugins available in the core"""
return self.pluginmanager.get_available_plugins() return self.pluginmanager.get_available_plugins()
@export @export()
def get_enabled_plugins(self): def get_enabled_plugins(self):
"""Returns a list of enabled plugins in the core""" """Returns a list of enabled plugins in the core"""
return self.pluginmanager.get_enabled_plugins() return self.pluginmanager.get_enabled_plugins()
@export @export()
def enable_plugin(self, plugin): def enable_plugin(self, plugin):
self.pluginmanager.enable_plugin(plugin) self.pluginmanager.enable_plugin(plugin)
return None return None
@export @export()
def disable_plugin(self, plugin): def disable_plugin(self, plugin):
self.pluginmanager.disable_plugin(plugin) self.pluginmanager.disable_plugin(plugin)
return None return None
@export @export()
def force_recheck(self, torrent_ids): def force_recheck(self, torrent_ids):
"""Forces a data recheck on torrent_ids""" """Forces a data recheck on torrent_ids"""
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].force_recheck() self.torrentmanager[torrent_id].force_recheck()
@export @export()
def set_torrent_options(self, torrent_ids, options): def set_torrent_options(self, torrent_ids, options):
"""Sets the torrent options for torrent_ids""" """Sets the torrent options for torrent_ids"""
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].set_options(options) self.torrentmanager[torrent_id].set_options(options)
@export @export()
def set_torrent_trackers(self, torrent_id, trackers): def set_torrent_trackers(self, torrent_id, trackers):
"""Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" """Sets a torrents tracker list. trackers will be [{"url", "tier"}]"""
return self.torrentmanager[torrent_id].set_trackers(trackers) return self.torrentmanager[torrent_id].set_trackers(trackers)
@export @export()
def set_torrent_max_connections(self, torrent_id, value): def set_torrent_max_connections(self, torrent_id, value):
"""Sets a torrents max number of connections""" """Sets a torrents max number of connections"""
return self.torrentmanager[torrent_id].set_max_connections(value) return self.torrentmanager[torrent_id].set_max_connections(value)
@export @export()
def set_torrent_max_upload_slots(self, torrent_id, value): def set_torrent_max_upload_slots(self, torrent_id, value):
"""Sets a torrents max number of upload slots""" """Sets a torrents max number of upload slots"""
return self.torrentmanager[torrent_id].set_max_upload_slots(value) return self.torrentmanager[torrent_id].set_max_upload_slots(value)
@export @export()
def set_torrent_max_upload_speed(self, torrent_id, value): def set_torrent_max_upload_speed(self, torrent_id, value):
"""Sets a torrents max upload speed""" """Sets a torrents max upload speed"""
return self.torrentmanager[torrent_id].set_max_upload_speed(value) return self.torrentmanager[torrent_id].set_max_upload_speed(value)
@export @export()
def set_torrent_max_download_speed(self, torrent_id, value): def set_torrent_max_download_speed(self, torrent_id, value):
"""Sets a torrents max download speed""" """Sets a torrents max download speed"""
return self.torrentmanager[torrent_id].set_max_download_speed(value) return self.torrentmanager[torrent_id].set_max_download_speed(value)
@export @export()
def set_torrent_file_priorities(self, torrent_id, priorities): def set_torrent_file_priorities(self, torrent_id, priorities):
"""Sets a torrents file priorities""" """Sets a torrents file priorities"""
return self.torrentmanager[torrent_id].set_file_priorities(priorities) return self.torrentmanager[torrent_id].set_file_priorities(priorities)
@export @export()
def set_torrent_prioritize_first_last(self, torrent_id, value): def set_torrent_prioritize_first_last(self, torrent_id, value):
"""Sets a higher priority to the first and last pieces""" """Sets a higher priority to the first and last pieces"""
return self.torrentmanager[torrent_id].set_prioritize_first_last(value) return self.torrentmanager[torrent_id].set_prioritize_first_last(value)
@export @export()
def set_torrent_auto_managed(self, torrent_id, value): def set_torrent_auto_managed(self, torrent_id, value):
"""Sets the auto managed flag for queueing purposes""" """Sets the auto managed flag for queueing purposes"""
return self.torrentmanager[torrent_id].set_auto_managed(value) return self.torrentmanager[torrent_id].set_auto_managed(value)
@export @export()
def set_torrent_stop_at_ratio(self, torrent_id, value): def set_torrent_stop_at_ratio(self, torrent_id, value):
"""Sets the torrent to stop at 'stop_ratio'""" """Sets the torrent to stop at 'stop_ratio'"""
return self.torrentmanager[torrent_id].set_stop_at_ratio(value) return self.torrentmanager[torrent_id].set_stop_at_ratio(value)
@export @export()
def set_torrent_stop_ratio(self, torrent_id, value): def set_torrent_stop_ratio(self, torrent_id, value):
"""Sets the ratio when to stop a torrent if 'stop_at_ratio' is set""" """Sets the ratio when to stop a torrent if 'stop_at_ratio' is set"""
return self.torrentmanager[torrent_id].set_stop_ratio(value) return self.torrentmanager[torrent_id].set_stop_ratio(value)
@export @export()
def set_torrent_remove_at_ratio(self, torrent_id, value): def set_torrent_remove_at_ratio(self, torrent_id, value):
"""Sets the torrent to be removed at 'stop_ratio'""" """Sets the torrent to be removed at 'stop_ratio'"""
return self.torrentmanager[torrent_id].set_remove_at_ratio(value) return self.torrentmanager[torrent_id].set_remove_at_ratio(value)
@export @export()
def set_torrent_move_on_completed(self, torrent_id, value): def set_torrent_move_on_completed(self, torrent_id, value):
"""Sets the torrent to be moved when completed""" """Sets the torrent to be moved when completed"""
return self.torrentmanager[torrent_id].set_move_on_completed(value) return self.torrentmanager[torrent_id].set_move_on_completed(value)
@export @export()
def set_torrent_move_on_completed_path(self, torrent_id, value): def set_torrent_move_on_completed_path(self, torrent_id, value):
"""Sets the path for the torrent to be moved when completed""" """Sets the path for the torrent to be moved when completed"""
return self.torrentmanager[torrent_id].set_move_on_completed_path(value) return self.torrentmanager[torrent_id].set_move_on_completed_path(value)
@export @export()
def block_ip_range(self, range): def block_ip_range(self, range):
"""Block an ip range""" """Block an ip range"""
self.ip_filter.add_rule(range[0], range[1], 1) self.ip_filter.add_rule(range[0], range[1], 1)
# Start a 2 second timer (and remove the previous one if it exists) # Start a 2 second timer (and remove the previous one if it exists)
if self.__set_ip_filter_timer: #if self.__set_ip_filter_timer:
gobject.source_remove(self.__set_ip_filter_timer) # self.__set_ip_filter_timer.stop()
self.__set_ip_filter_timer = gobject.timeout_add(2000, self.session.set_ip_filter, self.ip_filter)
@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): def reset_ip_filter(self):
"""Clears the ip filter""" """Clears the ip filter"""
self.ip_filter = lt.ip_filter() self.ip_filter = lt.ip_filter()
self.session.set_ip_filter(self.ip_filter) self.session.set_ip_filter(self.ip_filter)
@export @export()
def get_health(self): def get_health(self):
"""Returns True if we have established incoming connections""" """Returns True if we have established incoming connections"""
return self.session.status().has_incoming_connections return self.session.status().has_incoming_connections
@export @export()
def get_path_size(self, path): def get_path_size(self, path):
"""Returns the size of the file or folder 'path' and -1 if the path is """Returns the size of the file or folder 'path' and -1 if the path is
unaccessible (non-existent or insufficient privs)""" unaccessible (non-existent or insufficient privs)"""
return deluge.common.get_path_size(path) return deluge.common.get_path_size(path)
@export @export()
def create_torrent(self, path, tracker, piece_length, comment, target, def create_torrent(self, path, tracker, piece_length, comment, target,
url_list, private, created_by, httpseeds, add_to_session): url_list, private, created_by, httpseeds, add_to_session):
@ -651,7 +654,7 @@ class Core(component.Component):
if add_to_session: if add_to_session:
self.add_torrent_file(os.path.split(target)[1], open(target, "rb").read(), None) self.add_torrent_file(os.path.split(target)[1], open(target, "rb").read(), None)
@export @export()
def upload_plugin(self, filename, plugin_data): def upload_plugin(self, filename, plugin_data):
"""This method is used to upload new plugins to the daemon. It is used """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 when connecting to the daemon remotely and installing a new plugin on
@ -663,23 +666,23 @@ class Core(component.Component):
f.close() f.close()
component.get("PluginManager").scan_for_plugins() component.get("PluginManager").scan_for_plugins()
@export @export()
def rescan_plugins(self): def rescan_plugins(self):
"""Rescans the plugin folders for new plugins""" """Rescans the plugin folders for new plugins"""
component.get("PluginManager").scan_for_plugins() component.get("PluginManager").scan_for_plugins()
@export @export()
def rename_files(self, torrent_id, filenames): def rename_files(self, torrent_id, filenames):
"""Renames files in 'torrent_id'. The 'filenames' parameter should be a """Renames files in 'torrent_id'. The 'filenames' parameter should be a
list of (index, filename) pairs.""" list of (index, filename) pairs."""
self.torrentmanager[torrent_id].rename_files(filenames) self.torrentmanager[torrent_id].rename_files(filenames)
@export @export()
def rename_folder(self, torrent_id, folder, new_folder): def rename_folder(self, torrent_id, folder, new_folder):
"""Renames the 'folder' to 'new_folder' in 'torrent_id'.""" """Renames the 'folder' to 'new_folder' in 'torrent_id'."""
self.torrentmanager[torrent_id].rename_folder(folder, new_folder) self.torrentmanager[torrent_id].rename_folder(folder, new_folder)
@export @export()
def queue_top(self, torrent_ids): def queue_top(self, torrent_ids):
log.debug("Attempting to queue %s to top", torrent_ids) log.debug("Attempting to queue %s to top", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
@ -690,7 +693,7 @@ class Core(component.Component):
except KeyError: except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id) log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export @export()
def queue_up(self, torrent_ids): def queue_up(self, torrent_ids):
log.debug("Attempting to queue %s to up", torrent_ids) log.debug("Attempting to queue %s to up", torrent_ids)
#torrent_ids must be sorted before moving. #torrent_ids must be sorted before moving.
@ -703,7 +706,7 @@ class Core(component.Component):
except KeyError: except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id) log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export @export()
def queue_down(self, torrent_ids): def queue_down(self, torrent_ids):
log.debug("Attempting to queue %s to down", torrent_ids) log.debug("Attempting to queue %s to down", torrent_ids)
#torrent_ids must be sorted before moving. #torrent_ids must be sorted before moving.
@ -716,7 +719,7 @@ class Core(component.Component):
except KeyError: except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id) log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export @export()
def queue_bottom(self, torrent_ids): def queue_bottom(self, torrent_ids):
log.debug("Attempting to queue %s to bottom", torrent_ids) log.debug("Attempting to queue %s to bottom", torrent_ids)
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
@ -727,11 +730,11 @@ class Core(component.Component):
except KeyError: except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id) log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@export @export()
def glob(self, path): def glob(self, path):
return glob.glob(path) return glob.glob(path)
@export @export()
def test_listen_port(self): def test_listen_port(self):
""" Checks if active port is open """ """ Checks if active port is open """
import urllib import urllib

View file

@ -24,10 +24,10 @@
import signal import signal
import gobject
import gettext import gettext
import locale import locale
import pkg_resources import pkg_resources
from twisted.internet import reactor
import deluge.component as component import deluge.component as component
import deluge.configmanager import deluge.configmanager
@ -59,13 +59,11 @@ class Daemon(object):
from win32api import SetConsoleCtrlHandler from win32api import SetConsoleCtrlHandler
from win32con import CTRL_CLOSE_EVENT from win32con import CTRL_CLOSE_EVENT
from win32con import CTRL_SHUTDOWN_EVENT from win32con import CTRL_SHUTDOWN_EVENT
result = 0
def win_handler(ctrl_type): def win_handler(ctrl_type):
log.debug("ctrl_type: %s", ctrl_type) log.debug("ctrl_type: %s", ctrl_type)
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT: if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
self.__shutdown() self.__shutdown()
result = 1 return 1
return result
SetConsoleCtrlHandler(win_handler) SetConsoleCtrlHandler(win_handler)
version = deluge.common.get_version() version = deluge.common.get_version()
@ -83,25 +81,33 @@ class Daemon(object):
self.core = Core() self.core = Core()
self.rpcserver = RPCServer( self.rpcserver = RPCServer(
options.port if options.port else self.core.config["daemon_port"], port=options.port if options.port else self.core.config["daemon_port"],
self.core.config["allow_remote"] allow_remote=self.core.config["allow_remote"]
) )
self.rpcserver.register_object(self.core, "core") self.rpcserver.register_object(self.core)
self.rpcserver.register_object(self, "daemon") self.rpcserver.register_object(self)
gobject.threads_init()
# Make sure we start the PreferencesManager first # Make sure we start the PreferencesManager first
component.start("PreferencesManager") component.start("PreferencesManager")
component.start() component.start()
self.loop = gobject.MainLoop() # reactor.run()
try: try:
self.loop.run() reactor.run()
except KeyboardInterrupt: except KeyboardInterrupt:
self.shutdown() self.shutdown()
@export @export()
def shutdown(self): def shutdown(self, *args, **kwargs):
component.shutdown() 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""" """PluginManager for Core"""
import gobject from twisted.internet import reactor
from twisted.internet.task import LoopingCall
import deluge.pluginmanagerbase import deluge.pluginmanagerbase
import deluge.component as component import deluge.component as component
@ -57,13 +58,14 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
self.enable_plugins() self.enable_plugins()
# Set update timer to call update() in plugins every second # 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): def stop(self):
# Disable all enabled plugins # Disable all enabled plugins
self.disable_plugins() self.disable_plugins()
# Stop the update timer # Stop the update timer
gobject.source_remove(self.update_timer) self.update_timer.stop()
def shutdown(self): def shutdown(self):
self.stop() self.stop()
@ -151,4 +153,3 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def reset_ip_filter(self): def reset_ip_filter(self):
"""Resets the ip filter""" """Resets the ip filter"""
return self.core.export_reset_ip_filter() return self.core.export_reset_ip_filter()

View file

@ -25,7 +25,8 @@
import os.path import os.path
import threading import threading
import gobject from twisted.internet import reactor
from twisted.internet.task import LoopingCall
try: try:
import deluge.libtorrent as lt import deluge.libtorrent as lt
@ -450,13 +451,14 @@ class PreferencesManager(component.Component):
log.debug("Checking for new release..") log.debug("Checking for new release..")
threading.Thread(target=self.core.get_new_release).start() threading.Thread(target=self.core.get_new_release).start()
if self.new_release_timer: 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 # Set a timer to check for a new release every 3 days
self.new_release_timer = gobject.timeout_add( self.new_release_timer = LoopingCall(
72 * 60 * 60 * 1000, self._on_new_release_check, "new_release_check", True) self._on_new_release_check, "new_release_check", True)
self.new_release_timer.start(72 * 60 * 60, False)
else: else:
if self.new_release_timer: if self.new_release_timer:
gobject.source.remove(self.new_release_timer) self.new_release_timer.stop()
def _on_set_proxies(self, key, value): def _on_set_proxies(self, key, value):
for k, v in value.items(): for k, v in value.items():

View file

@ -24,28 +24,209 @@
"""RPCServer Module""" """RPCServer Module"""
import gobject
import sys import sys
import zlib
import os
import traceback
from deluge.SimpleXMLRPCServer import SimpleXMLRPCServer from twisted.internet.protocol import Factory, Protocol
from deluge.SimpleXMLRPCServer import SimpleXMLRPCRequestHandler from twisted.internet import ssl, reactor
from SocketServer import ThreadingMixIn
from base64 import decodestring, encodestring
from OpenSSL import crypto, SSL
import deluge.rencode as rencode
from deluge.log import LOG as log from deluge.log import LOG as log
import deluge.component as component import deluge.component as component
import deluge.configmanager 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 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 func: function, the function to export
:param auth_level: int, the auth level required to call this method
""" """
def wrap(func, *args, **kwargs):
func._rpcserver_export = True func._rpcserver_export = True
func._rpcserver_auth_level = auth_level
return func 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): class RPCServer(component.Component):
""" """
This class is used to handle rpc requests from the client. Objects are This class is used to handle rpc requests from the client. Objects are
@ -53,10 +234,11 @@ class RPCServer(component.Component):
decorator. decorator.
:param port: int, the port the RPCServer will listen on :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 :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") component.Component.__init__(self, "RPCServer")
if allow_remote: if allow_remote:
@ -64,33 +246,36 @@ class RPCServer(component.Component):
else: else:
hostname = "localhost" 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: try:
log.info("Starting XMLRPC server %s:%s", hostname, port) reactor.listenSSL(port, self.factory, ServerContextFactory(), interface=hostname)
self.server = XMLRPCServer((hostname, port),
requestHandler=BasicAuthXMLRPCRequestHandler,
logRequests=False,
allow_none=True)
except Exception, e: except Exception, e:
log.info("Daemon already running or port not available..") log.info("Daemon already running or port not available..")
log.error(e) log.error(e)
sys.exit(0) 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): def register_object(self, obj, name=None):
""" """
Registers an object to export it's rpc methods. These methods should 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 :param name: str, the name to use, if None, it will be the class name of the object
""" """
if not name: if not name:
name = obj.__class__.__name__ name = obj.__class__.__name__.lower()
log.debug("dir: %s", dir(obj))
for d in dir(obj): for d in dir(obj):
if d[0] == "_": if d[0] == "_":
continue continue
if getattr(getattr(obj, d), '_rpcserver_export', False): if getattr(getattr(obj, d), '_rpcserver_export', False):
log.debug("Registering method: %s", name + "." + d) 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 get_object_method(self, name):
def client_address(self): log.debug(self.factory.methods)
""" return self.factory.methods[name]
The ip address of the current client.
"""
return self.server.client_address
class XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer): def __generate_ssl_keys(self):
def get_request(self):
""" """
Get the request and client address from the socket. This method generates a new SSL key/cert.
We override this so that we can get the ip address of the client.
""" """
request, client_address = self.socket.accept() digest = "md5"
self.client_address = client_address[0] # Generate key pair
return (request, client_address) pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 1024)
class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): # Generate cert request
def do_POST(self): req = crypto.X509Req()
if "authorization" in self.headers: subj = req.get_subject()
auth = self.headers['authorization'] setattr(subj, "CN", "Deluge Daemon")
auth = auth.replace("Basic ","") req.set_pubkey(pkey)
decoded_auth = decodestring(auth) req.sign(pkey, digest)
# Check authentication here
if component.get("AuthManager").authorize(*decoded_auth.split(":")):
# User authorized, call the real do_POST now
return SimpleXMLRPCRequestHandler.do_POST(self)
# if cannot authenticate, end the connection # Generate certificate
self.send_response(401) cert = crypto.X509()
self.end_headers() 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 socket
import struct import struct
import gobject from twisted.internet import reactor
from twisted.internet.task import LoopingCall
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
@ -81,8 +82,6 @@ class SignalManager(component.Component):
uri = "http://" + str(address) + ":" + str(port) uri = "http://" + str(address) + ":" + str(port)
log.debug("Registering %s as a signal reciever..", uri) log.debug("Registering %s as a signal reciever..", uri)
self.clients[uri] = xmlrpclib.ServerProxy(uri, transport=Transport()) 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): def emit(self, signal, *data):
# Run the handlers # Run the handlers
@ -91,7 +90,9 @@ class SignalManager(component.Component):
handler(*data) handler(*data)
for uri in self.clients: 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): def _emit(self, uri, signal, count, *data):
if uri not in self.clients: if uri not in self.clients:
@ -102,7 +103,7 @@ class SignalManager(component.Component):
except (socket.error, Exception), e: except (socket.error, Exception), e:
log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count) log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count)
if count < 30: 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: else:
log.info("Removing %s because it couldn't be reached..", uri) log.info("Removing %s because it couldn't be reached..", uri)
del self.clients[uri] del self.clients[uri]

View file

@ -31,7 +31,8 @@ import os
import time import time
import shutil import shutil
import gobject from twisted.internet import reactor
from twisted.internet.task import LoopingCall
try: try:
import deluge.libtorrent as lt import deluge.libtorrent as lt
@ -178,8 +179,10 @@ class TorrentManager(component.Component):
self.load_state() self.load_state()
# Save the state every 5 minutes # Save the state every 5 minutes
self.save_state_timer = gobject.timeout_add(300000, self.save_state) self.save_state_timer = LoopingCall(self.save_state)
self.save_resume_data_timer = gobject.timeout_add(290000, self.save_resume_data) 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): def stop(self):
# Save state on shutdown # 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 # client.py
# #
# Copyright (C) 2007/2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -23,364 +22,543 @@
# Boston, MA 02110-1301, USA. # 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 if deluge.common.windows_check():
import socket import win32api
import struct else:
import httplib import subprocess
import urlparse
import gobject
import deluge.xmlrpclib as xmlrpclib
import deluge.common import deluge.common
import deluge.error
from deluge.log import LOG as log from deluge.log import LOG as log
class Transport(xmlrpclib.Transport): RPC_RESPONSE = 1
def __init__(self, username=None, password=None, use_datetime=0): RPC_ERROR = 2
self.__username = username RPC_SIGNAL = 3
self.__password = password
self._use_datetime = use_datetime
def request(self, host, handler, request_body, verbose=0): def format_kwargs(kwargs):
# issue XML-RPC request return ", ".join([key + "=" + str(value) for key, value in kwargs.items()])
h = self.make_connection(host) class DelugeRPCError(object):
if verbose: """
h.set_debuglevel(1) 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) class DelugeRPCRequest(object):
self.send_host(h, host) """
self.send_user_agent(h) 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: request_id = None
h.putheader("AUTHORIZATION", "Basic %s" % string.replace( method = None
encodestring("%s:%s" % (self.__username, self.__password)), args = None
"\012", "")) 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: def format_message(self):
raise xmlrpclib.ProtocolError( """
host + handler, Returns a properly formatted RPCRequest based on the properties. Will
errcode, errmsg, raise a TypeError if the properties haven't been set yet.
headers
)
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!")
return (self.request_id, self.method, self.args, self.kwargs)
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
log.info("Connected to daemon at %s:%s..", peer.host, peer.port)
self.factory.daemon.connect_deferred.callback((peer.host, peer.port))
def dataReceived(self, data):
"""
This method is called whenever we receive data from the daemon.
: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: try:
sock = h._conn.sock request = rencode.loads(dobj.decompress(data))
except AttributeError: except Exception, e:
sock = None 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
return self._parse_response(h.getfile(), sock) 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
def make_connection(self, host): message_type = request[0]
# 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
class CoreProxy(gobject.GObject): if message_type == RPC_SIGNAL:
__gsignals__ = { signal = request[1]
"new_core" : ( # A RPCSignal was received from the daemon so run any handlers
gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), # associated with it.
"no_core" : ( if signal in self.factory.daemon.signal_handlers:
gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []), for handler in self.factory.daemon.signal_handlers[signal]:
handler(*request[2])
return
request_id = request[1]
# 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 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)
del self.__rpc_requests[request_id]
def send_request(self, request):
"""
Sends a RPCRequest to the server.
:param request: RPCRequest
"""
# 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(),))))
class DelugeRPCClientFactory(ClientFactory):
protocol = DelugeRPCProtocol
def __init__(self, daemon):
self.daemon = daemon
def startedConnecting(self, connector):
log.info("Connecting to daemon at %s:%s..", connector.host, connector.port)
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)
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): def __init__(self):
log.debug("CoreProxy init..") self.__factory = DelugeRPCClientFactory(self)
gobject.GObject.__init__(self) self.__request_counter = 0
self._uri = None self.__deferred = {}
self.rpc_core = None
self._multi = None
self._callbacks = []
self._multi_timer = None
def call(self, func, callback, *args): # This is set when a connection is made to the daemon
if self.rpc_core is None or self._multi is None: self.protocol = None
if self.rpc_core is None:
raise deluge.error.NoCoreError("The core proxy is invalid.")
return
_func = getattr(self._multi, func) # This is set when a connection is made
self.host = None
self.port = None
self.username = None
if _func is not None: self.connected = False
if (func, args) in self._multi.get_call_list():
index = self._multi.get_call_list().index((func, args)) self.disconnect_deferred = None
if callback not in self._callbacks[index]:
self._callbacks[index].append(callback) 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: else:
if len(args) == 0: d.callbacks(result)
_func() return d
else:
_func(*args)
self._callbacks.append([callback])
def do_multicall(self, block=False):
if len(self._callbacks) == 0:
return True
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 = []
self._multi = xmlrpclib.MultiCall(self.rpc_core)
return True
def set_core_uri(self, uri):
log.info("Setting core uri as %s", uri)
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
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")
self._uri = uri
# Get a new core
self.get_rpc_core()
def get_core_uri(self):
"""Returns the URI of the core currently being used."""
return self._uri
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")
return self.rpc_core
_core = CoreProxy()
class DottedObject(object): class DottedObject(object):
"""This is used for dotted name calls to client""" """
def __init__(self, client, base): This is used for dotted name calls to client
self.client = client """
self.base = base def __init__(self, daemon, method):
self.daemon = daemon
self.base = method
def __call__(self, *args, **kwargs): 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): 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 This is used when something like 'client.core.get_something()' is attempted.
base for AClient and SClient
""" """
no_callback_list = ["add_torrent_url", "pause_all_torrents", def __call__(self, *args, **kwargs):
"resume_all_torrents", "set_config", "enable_plugin", return self.daemon.call(self.base, *args, **kwargs)
"disable_plugin", "set_torrent_trackers", "connect_peer",
"set_torrent_max_connections", "set_torrent_max_upload_slots", class Client(object):
"set_torrent_max_upload_speed", "set_torrent_max_download_speed", """
"set_torrent_private_flag", "set_torrent_file_priorities", This class is used to connect to a daemon process and issue RPC requests.
"block_ip_range", "remove_torrent", "pause_torrent", "move_storage", """
"resume_torrent", "force_reannounce", "force_recheck",
"deregister_client", "register_client", "add_torrent_file", __event_handlers = {
"set_torrent_prioritize_first_last", "set_torrent_auto_managed", "connected": [],
"set_torrent_stop_ratio", "set_torrent_stop_at_ratio", "disconnected": []
"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 __init__(self): def __init__(self):
self.core = _core self._daemon_proxy = None
#xml-rpc introspection def connect(self, host="127.0.0.1", port=58846, username="", password=""):
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
""" """
if torrent_files is None: Connects to a daemon process.
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
# Get the filename because the core doesn't want a path. :param host: str, the hostname of the daemon
(path, filename) = os.path.split(torrent_file) :param port: int, the port of the daemon
fdump = xmlrpclib.Binary(f.read()) :param username: str, the username to login with
f.close() :param password: str, the password to login with
# Get the options for the torrent :returns: a Deferred object that will be called once the connection
if torrent_options != None: 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: try:
options = torrent_options[torrent_files.index(torrent_file)] if deluge.common.windows_check():
except: win32api.WinExec("deluged --port=%s --config=%s" % (port, config))
options = None
else: else:
options = None subprocess.call(["deluged", "--port=%s" % port, "--config=%s" % config])
self.get_method("core.add_torrent_file")(filename, fdump, options) except Exception, e:
log.error("Unable to start daemon!")
def add_torrent_file_binary(self, filename, fdump, options = None): log.exception(e)
""" return False
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)
else: else:
return True return True
def is_localhost(self): def is_localhost(self):
"""Returns True if core is a localhost""" """
# Get the uri Checks if the current connected host is a localhost or not.
uri = self.core.get_core_uri()
if uri != None: :returns: bool, True if connected to a localhost
# Get the host
u = urlparse.urlsplit(uri) """
if u.hostname == "localhost" or u.hostname == "127.0.0.1": if self._daemon_proxy and self._daemon_proxy.host in ("127.0.0.1", "localhost"):
return True return True
return False return False
def get_core_uri(self):
"""Get the core URI"""
return self.core.get_core_uri()
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): def connected(self):
"""Returns True if connected to a host, and False if not."""
if self.get_core_uri() != None:
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)
#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)
def connect_on_no_core(self, callback):
"""Connect a callback whenever the core is disconnected from."""
return self.core.connect("no_core", callback)
class SClient(BaseClient):
""" """
sync proxy Check to see if connected to a daemon.
"""
def get_method(self, method_name):
return getattr(self.core.rpc_core, method_name)
class AClient(BaseClient): :returns: bool, True if connected
"""
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
def force_call(self, block=True): """
"""Forces the multicall batch to go now and not wait for the timer. This return self._daemon_proxy.connected if self._daemon_proxy else False
call also blocks until all callbacks have been dealt with."""
self.core.do_multicall(block=block)
sclient = SClient() def connection_info(self):
aclient = AClient() """
Get some info about the connection or return None if not connected.
: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)
return None
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.common
import deluge.ui.gtkui.common as common import deluge.ui.gtkui.common as common
from deluge.ui.client import aclient as client
class AboutDialog: class AboutDialog:
def __init__(self): def __init__(self):

View file

@ -31,7 +31,7 @@ import gobject
import pkg_resources import pkg_resources
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.component as component import deluge.component as component
import deluge.ui.gtkui.listview as listview import deluge.ui.gtkui.listview as listview
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
@ -162,12 +162,10 @@ class AddTorrentDialog(component.Component):
def _on_config_values(config): def _on_config_values(config):
self.core_config = config self.core_config = config
self.set_default_options()
# Send requests to the core for these config values # Send requests to the core for these config values
client.get_config_values(_on_config_values, self.core_keys) client.core.get_config_values(self.core_keys).addCallback(_on_config_values)
# Force a call to the core because we need this data now
client.force_call()
self.set_default_options()
def add_from_files(self, filenames): def add_from_files(self, filenames):
import os.path import os.path
@ -673,9 +671,9 @@ class AddTorrentDialog(component.Component):
row = self.torrent_liststore.iter_next(row) row = self.torrent_liststore.iter_next(row)
if torrent_filenames: if torrent_filenames:
client.add_torrent_file(torrent_filenames, torrent_options) client.core.add_torrent_file(torrent_filenames, torrent_options)
if torrent_magnets: 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) client.force_call(False)
self.hide() self.hide()

View file

@ -30,7 +30,7 @@ import gtk, gtk.glade
import pkg_resources import pkg_resources
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
import deluge.common import deluge.common
@ -191,6 +191,6 @@ def add_peer_dialog():
if deluge.common.is_ip(ip): if deluge.common.is_ip(ip):
id = component.get("TorrentView").get_selected_torrent() id = component.get("TorrentView").get_selected_torrent()
log.debug("adding peer %s to %s", value, id) 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() peer_dialog.destroy()
return True return True

View file

@ -1,7 +1,7 @@
# #
# connectionmanager.py # connectionmanager.py
# #
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # Deluge is free software.
# #
@ -22,38 +22,40 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
import gtk
import gtk, gtk.glade
import pkg_resources import pkg_resources
import gobject
import socket
import os
import subprocess
import time
import threading
import urlparse import urlparse
import time
import hashlib
import deluge.component as component import deluge.component as component
import deluge.xmlrpclib as xmlrpclib
import deluge.common import deluge.common
import deluge.ui.gtkui.common as 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 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 from deluge.log import LOG as log
DEFAULT_URI = "http://127.0.0.1:58846" DEFAULT_HOST = "127.0.0.1"
DEFAULT_HOST = DEFAULT_URI.split(":")[1][2:] DEFAULT_PORT = 58846
DEFAULT_PORT = DEFAULT_URI.split(":")[-1]
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"hosts": [DEFAULT_URI] "hosts": [(hashlib.sha1(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, "andrew", "andrew")]
} }
HOSTLIST_COL_PIXBUF = 0 HOSTLIST_COL_ID = 0
HOSTLIST_COL_URI = 1 HOSTLIST_COL_HOST = 1
HOSTLIST_COL_STATUS = 2 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 = [ HOSTLIST_STATUS = [
"Offline", "Offline",
@ -61,242 +63,260 @@ HOSTLIST_STATUS = [
"Connected" "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): def cell_render_host(column, cell, model, row, data):
host = model[row][data] host, port, username = model.get(row, *data)
u = urlparse.urlsplit(host) text = host + ":" + str(port)
if not u.hostname: if username:
host = "http://" + host text = username + "@" + text
u = urlparse.urlsplit(host)
if u.username:
text = u.username + "@" + u.hostname + ":" + str(u.port)
else:
text = u.hostname + ":" + str(u.port)
cell.set_property('text', 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): class ConnectionManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "ConnectionManager") 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 # Get the glade file for the connection manager
self.glade = gtk.glade.XML( self.glade = gtk.glade.XML(
pkg_resources.resource_filename("deluge.ui.gtkui", pkg_resources.resource_filename("deluge.ui.gtkui",
"glade/connection_manager.glade")) "glade/connection_manager.glade"))
self.window = component.get("MainWindow") 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") 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) self.connection_manager.set_transient_for(self.window.window)
self.connection_manager.set_icon(common.get_logo(32))
self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32))
self.hostlist = self.glade.get_widget("hostlist")
# Create status pixbufs # Create status pixbufs
if not HOSTLIST_PIXBUFS:
for stock_id in (gtk.STOCK_NO, gtk.STOCK_YES, gtk.STOCK_CONNECT): 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)) HOSTLIST_PIXBUFS.append(self.connection_manager.render_icon(stock_id, gtk.ICON_SIZE_MENU))
self.hostlist = self.glade.get_widget("hostlist") # Create the host list gtkliststore
self.connection_manager.set_icon(common.get_logo(32)) # id-hash, hostname, port, status, username, password, version
self.liststore = gtk.ListStore(str, str, int, str, str, str, str)
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)
# Holds the online status of hosts
self.online_status = {}
# 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)
# Setup host list treeview # Setup host list treeview
self.hostlist.set_model(self.liststore) self.hostlist.set_model(self.liststore)
render = gtk.CellRendererPixbuf() render = gtk.CellRendererPixbuf()
column = gtk.TreeViewColumn( column = gtk.TreeViewColumn("Status", render)
"Status", render, pixbuf=HOSTLIST_COL_PIXBUF) column.set_cell_data_func(render, cell_render_status, 3)
self.hostlist.append_column(column) self.hostlist.append_column(column)
render = gtk.CellRendererText() render = gtk.CellRendererText()
column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_URI) column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_HOST)
column.set_cell_data_func(render, cell_render_host, 1) 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.hostlist.append_column(column)
self.glade.signal_autoconnect({ # Load any saved host entries
"on_button_addhost_clicked": self.on_button_addhost_clicked, self.__load_hostlist()
"on_button_removehost_clicked": self.on_button_removehost_clicked, self.__load_options()
"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
})
self.connection_manager.connect("delete-event", self.on_delete_event) # Connect the signals to the handlers
# Connect to the 'changed' event of TreeViewSelection to get selection self.glade.signal_autoconnect(self)
# changes. self.hostlist.get_selection().connect("changed", self.on_hostlist_selection_changed)
self.hostlist.get_selection().connect("changed",
self.on_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 response = self.connection_manager.run()
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)
while not self.test_online_status(uri): # Save the toggle options
time.sleep(0.01) self.__save_options()
client.set_core_uri(uri)
self.hide()
return
# This controls the timer, if it's set to false the update timer will stop. self.connection_manager.destroy()
self._do_update = True del self.glade
self._update_list() del self.window
del self.connection_manager
del self.liststore
del self.hostlist
# Auto connect to a host if applicable def add_host(self, host, port, username="", password=""):
if self.gtkui_config["autoconnect"] and \ """
self.gtkui_config["autoconnect_host_uri"] != None: Adds a host to the list.
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)
# We need to wait for the host to start before connecting :param host: str, the hostname
while not self.test_online_status(auth_uri): :param port: int, the port
time.sleep(0.01) :param username: str, the username to login as
client.set_core_uri(auth_uri) :param password: str, the password to login with
self.hide()
def start(self): """
if self.gtkui_config["autoconnect"]: # Check to see if there is already an entry for this host and return
# We need to update the autoconnect_host_uri on connection to host # if thats the case
# start() gets called whenever we get a new connection to a host for entry in self.liststore:
self.gtkui_config["autoconnect_host_uri"] = client.get_core_uri() 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): # Host isn't in the list, so lets add it
# Set the checkbuttons according to config row = self.liststore.append()
self.glade.get_widget("chk_autoconnect").set_active( import time
self.gtkui_config["autoconnect"]) import hashlib
self.glade.get_widget("chk_autostart").set_active( self.liststore[row][HOSTLIST_COL_ID] = hashlib.sha1(str(time.time()).hexdigest())
self.gtkui_config["autostart_localhost"]) self.liststore[row][HOSTLIST_COL_HOST] = host
self.glade.get_widget("chk_donotshow").set_active( self.liststore[row][HOSTLIST_COL_PORT] = port
not self.gtkui_config["show_connection_manager_on_start"]) self.liststore[row][HOSTLIST_COL_USER] = username
self.liststore[row][HOSTLIST_COL_PASS] = password
# Setup timer to update host status # Save the host list to file
self._update_timer = gobject.timeout_add(1000, self._update_list) self.__save_hostlist()
self._update_list()
self._update_list()
self.connection_manager.show_all()
def hide(self): # Update the status of the hosts
self.connection_manager.hide() self.__update_list()
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
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""" """Updates the host status"""
def update_row(model=None, path=None, row=None, columns=None): def on_connect(result, c, host_id):
uri = model.get_value(row, HOSTLIST_COL_URI) row = self.__get_host_row(host_id)
threading.Thread(target=self.test_online_status, args=(uri,)).start() def on_info(info, c):
try: if row:
online = self.online_status[uri] row[HOSTLIST_COL_STATUS] = "Online"
except: row[HOSTLIST_COL_VERSION] = info
online = False self.__update_buttons()
c.disconnect()
# Update hosts status def on_info_fail(reason):
if online: if row:
online = HOSTLIST_STATUS.index("Online") row[HOSTLIST_COL_STATUS] = "Offline"
else: self.__update_buttons()
online = HOSTLIST_STATUS.index("Offline")
if urlparse.urlsplit(uri).hostname == "localhost" or urlparse.urlsplit(uri).hostname == "127.0.0.1": d = c.daemon.info()
uri = get_localhost_auth_uri(uri) d.addCallback(on_info, c)
d.addErrback(on_info_fail)
if uri == current_uri: def on_connect_failed(reason, host_info):
online = HOSTLIST_STATUS.index("Connected") 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) for row in self.liststore:
model.set_value(row, HOSTLIST_COL_PIXBUF, HOSTLIST_PIXBUFS[online]) 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() # Create a new Client instance
self.liststore.foreach(update_row) c = deluge.ui.client.Client()
# Update the buttons d = c.connect(host, port, user, password)
self.update_buttons() d.addCallback(on_connect, c, host_id)
d.addErrback(on_connect_failed, host_id)
# See if there is any row selected def __load_options(self):
paths = self.hostlist.get_selection().get_selected_rows()[1] """
if len(paths) < 1: Set the widgets to show the correct options from the config.
# And there is at least 1 row """
if self.liststore.iter_n_children(None) > 0: self.glade.get_widget("chk_autoconnect").set_active(self.gtkui_config["autoconnect"])
# Then select the first row self.glade.get_widget("chk_autostart").set_active(self.gtkui_config["autostart_localhost"])
self.hostlist.get_selection().select_iter(self.liststore.get_iter_first()) self.glade.get_widget("chk_donotshow").set_active(not self.gtkui_config["show_connection_manager_on_start"])
return self._do_update
def update_buttons(self): def __save_options(self):
"""Updates the buttons based on selection""" """
if self.liststore.iter_n_children(None) < 1: 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 # There is nothing in the list
self.glade.get_widget("button_startdaemon").set_sensitive(True) self.glade.get_widget("button_startdaemon").set_sensitive(True)
self.glade.get_widget("button_connect").set_sensitive(False) self.glade.get_widget("button_connect").set_sensitive(False)
self.glade.get_widget("button_removehost").set_sensitive(False) self.glade.get_widget("button_removehost").set_sensitive(False)
self.glade.get_widget("image_startdaemon").set_from_stock( self.glade.get_widget("image_startdaemon").set_from_stock(
gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
self.glade.get_widget("label_startdaemon").set_text( self.glade.get_widget("label_startdaemon").set_text("_Start Daemon")
"_Start Daemon")
self.glade.get_widget("label_startdaemon").set_use_underline(
True)
# Get the selected row's URI model, row = self.hostlist.get_selection().get_selected()
paths = self.hostlist.get_selection().get_selected_rows()[1] if not row:
# If nothing is selected, just return
if len(paths) < 1:
return 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 localhost = False
u = urlparse.urlsplit(uri) if host in ("127.0.0.1", "localhost"):
if u.hostname == "localhost" or u.hostname == "127.0.0.1":
localhost = True localhost = True
# Make sure buttons are sensitive at start # 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_connect").set_sensitive(True)
self.glade.get_widget("button_removehost").set_sensitive(True) self.glade.get_widget("button_removehost").set_sensitive(True)
# See if this is the currently connected URI # See if this is the currently connected host
if status == HOSTLIST_STATUS.index("Connected"): if status == "Connected":
# Display a disconnect button if we're connected to this host # 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_connect").set_label("gtk-disconnect")
self.glade.get_widget("button_removehost").set_sensitive(False) self.glade.get_widget("button_removehost").set_sensitive(False)
else: else:
self.glade.get_widget("button_connect").set_label("gtk-connect") 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) self.glade.get_widget("button_connect").set_sensitive(False)
# Check to see if the host is online # Check to see if the host is online
if status == HOSTLIST_STATUS.index("Connected") \ if status == "Connected" or status == "Online":
or status == HOSTLIST_STATUS.index("Online"):
self.glade.get_widget("image_startdaemon").set_from_stock( self.glade.get_widget("image_startdaemon").set_from_stock(
gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
self.glade.get_widget("label_startdaemon").set_text( self.glade.get_widget("label_startdaemon").set_text(
"_Stop Daemon") "_Stop Daemon")
# Update the start daemon button if the selected host is localhost # 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 # The localhost is not online
self.glade.get_widget("image_startdaemon").set_from_stock( self.glade.get_widget("image_startdaemon").set_from_stock(
gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
@ -338,39 +357,30 @@ class ConnectionManager(component.Component):
self.glade.get_widget("label_startdaemon").set_use_underline( self.glade.get_widget("label_startdaemon").set_use_underline(
True) True)
def save(self): # Signal handlers
"""Save the current host list to file""" def __on_connected(self, connector, host_id):
def append_row(model=None, path=None, row=None, columns=None): if self.gtkui_config["autoconnect"]:
hostlist.append(model.get_value(row, HOSTLIST_COL_URI)) self.gtkui_config["autoconnect_host_id"] = host_id
hostlist = [] def on_button_connect_clicked(self, widget):
self.liststore.foreach(append_row, hostlist) model, row = self.hostlist.get_selection().get_selected()
self.config["hosts"] = hostlist status = model[row][HOSTLIST_COL_STATUS]
self.config.save() if status == "Connected":
def on_disconnect(reason):
self.__update_list()
client.disconnect().addCallback(on_disconnect)
return
def test_online_status(self, uri): host_id = model[row][HOSTLIST_COL_ID]
"""Tests the status of URI.. Returns True or False depending on status. host = model[row][HOSTLIST_COL_HOST]
""" port = model[row][HOSTLIST_COL_PORT]
online = True user = model[row][HOSTLIST_COL_USER]
host = None password = model[row][HOSTLIST_COL_PASS]
try: client.connect(host, port, user, password).addCallback(self.__on_connected, host_id)
u = urlparse.urlsplit(uri) self.connection_manager.response(gtk.RESPONSE_OK)
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
del host def on_button_close_clicked(self, widget):
self.online_status[uri] = online self.connection_manager.response(gtk.RESPONSE_CLOSE)
return online
## Callbacks
def on_delete_event(self, widget, event):
self.hide()
return True
def on_button_addhost_clicked(self, widget): def on_button_addhost_clicked(self, widget):
log.debug("on_button_addhost_clicked") log.debug("on_button_addhost_clicked")
@ -385,50 +395,11 @@ class ConnectionManager(component.Component):
username = username_entry.get_text() username = username_entry.get_text()
password = password_entry.get_text() password = password_entry.get_text()
hostname = hostname_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 # 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() dialog.destroy()
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()
def on_button_removehost_clicked(self, widget): def on_button_removehost_clicked(self, widget):
log.debug("on_button_removehost_clicked") log.debug("on_button_removehost_clicked")
@ -438,7 +409,7 @@ class ConnectionManager(component.Component):
self.liststore.remove(self.liststore.get_iter(path)) self.liststore.remove(self.liststore.get_iter(path))
# Update the hostlist # Update the hostlist
self._update_list() self.__update_list()
# Save the host list # Save the host list
self.save() self.save()
@ -449,120 +420,48 @@ class ConnectionManager(component.Component):
# There is nothing in the list, so lets create a localhost entry # There is nothing in the list, so lets create a localhost entry
self.add_host(DEFAULT_HOST, DEFAULT_PORT) self.add_host(DEFAULT_HOST, DEFAULT_PORT)
# ..and start the daemon. # ..and start the daemon.
self.start_localhost(DEFAULT_PORT) client.start_daemon(DEFAULT_PORT, deluge.configmanager.get_config_dir())
return return
paths = self.hostlist.get_selection().get_selected_rows()[1] paths = self.hostlist.get_selection().get_selected_rows()[1]
if len(paths) < 1: if len(paths) < 1:
return return
row = self.liststore.get_iter(paths[0])
status = self.liststore.get_value(row, HOSTLIST_COL_STATUS) status = self.liststore[paths[0]][HOSTLIST_COL_STATUS]
uri = self.liststore.get_value(row, HOSTLIST_COL_URI) host = self.liststore[paths[0]][HOSTLIST_COL_HOST]
u = urlparse.urlsplit(uri) port = self.liststore[paths[0]][HOSTLIST_COL_PORT]
if HOSTLIST_STATUS[status] == "Online" or\ user = self.liststore[paths[0]][HOSTLIST_COL_USER]
HOSTLIST_STATUS[status] == "Connected": 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 # We need to stop this daemon
# Call the shutdown method on the daemon # Call the shutdown method on the daemon
if u.hostname == "127.0.0.1" or u.hostname == "localhost": def on_daemon_shutdown(d):
uri = get_localhost_auth_uri(uri)
core = xmlrpclib.ServerProxy(uri)
core.shutdown()
# Update display to show change # Update display to show change
self._update_list() self.__update_list()
elif HOSTLIST_STATUS[status] == "Offline": if client.connected():
self.start_localhost(u.port) client.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: else:
subprocess.call(["deluged", "--port=%s" % port, # Create a new client instance
"--config=%s" % deluge.configmanager.get_config_dir()]) c = deluge.ui.client.Client()
def on_connect(d, c):
log.debug("on_connect")
c.daemon.shutdown().addCallback(on_daemon_shutdown)
def on_button_close_clicked(self, widget): c.connect(host, port, user, password).addCallback(on_connect, c)
log.debug("on_button_close_clicked")
self.hide()
def on_button_connect_clicked(self, widget): elif status == "Offline":
log.debug("on_button_connect_clicked") client.start_daemon(port, deluge.configmanager.get_config_dir())
component.stop() self.__update_list()
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()
if status == HOSTLIST_STATUS.index("Connected"): def on_hostlist_row_activated(self, tree, path, view_column):
# Stop all the components first. self.on_button_connect_clicked()
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 def on_hostlist_selection_changed(self, treeselection):
# column information because it can be up to 5 seconds out of sync. self.__update_buttons()
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"))

View file

@ -24,7 +24,7 @@
import deluge.component as component 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 from deluge.log import LOG as log
class CoreConfig(component.Component): class CoreConfig(component.Component):
@ -36,7 +36,7 @@ class CoreConfig(component.Component):
self._on_config_value_changed) self._on_config_value_changed)
def start(self): def start(self):
client.get_config(self._on_get_config) client.core.get_config().addCallback(self._on_get_config)
def stop(self): def stop(self):
self.config = {} self.config = {}
@ -45,11 +45,10 @@ class CoreConfig(component.Component):
return self.config[key] return self.config[key]
def __setitem__(self, key, value): def __setitem__(self, key, value):
client.set_config({key: value}) client.core.set_config({key: value})
def _on_get_config(self, config): def _on_get_config(self, config):
self.config = config self.config = config
def _on_config_value_changed(self, key, value): def _on_config_value_changed(self, key, value):
self.config[key] = value self.config[key] = value

View file

@ -28,7 +28,7 @@ import pkg_resources
import os.path import os.path
import gobject 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.ui.gtkui.listview as listview
import deluge.component as component import deluge.component as component
import deluge.common import deluge.common
@ -182,7 +182,7 @@ class CreateTorrentDialog:
self.files_treestore.clear() self.files_treestore.clear()
self.files_treestore.append(None, [result, gtk.STOCK_NETWORK, size]) self.files_treestore.append(None, [result, gtk.STOCK_NETWORK, size])
self.adjust_piece_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) client.force_call(True)
dialog.destroy() dialog.destroy()
@ -270,7 +270,7 @@ class CreateTorrentDialog:
add_to_session = self.glade.get_widget("chk_add_to_session").get_active() add_to_session = self.glade.get_widget("chk_add_to_session").get_active()
if is_remote: if is_remote:
client.create_torrent( client.core.create_torrent(
path, path,
tracker, tracker,
piece_length, piece_length,
@ -321,7 +321,7 @@ class CreateTorrentDialog:
httpseeds=httpseeds) httpseeds=httpseeds)
self.glade.get_widget("progress_dialog").hide_all() self.glade.get_widget("progress_dialog").hide_all()
if add_to_session: if add_to_session:
client.add_torrent_file([target]) client.core.add_torrent_file([target])
def _on_create_torrent_progress(self, value, num_pieces): def _on_create_torrent_progress(self, value, num_pieces):
percent = float(value)/float(num_pieces) percent = float(value)/float(num_pieces)
@ -380,5 +380,3 @@ class CreateTorrentDialog:
log.debug("_on_button_remove_clicked") log.debug("_on_button_remove_clicked")
row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1] row = self.glade.get_widget("tracker_treeview").get_selection().get_selected()[1]
self.trackers_liststore.remove(row) self.trackers_liststore.remove(row)

View file

@ -25,7 +25,7 @@
import gtk, gtk.glade 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.component as component
import deluge.common import deluge.common
from deluge.ui.gtkui.torrentdetails import Tab from deluge.ui.gtkui.torrentdetails import Tab
@ -69,8 +69,7 @@ class DetailsTab(Tab):
status_keys = ["name", "total_size", "num_files", status_keys = ["name", "total_size", "num_files",
"tracker", "save_path", "message", "hash"] "tracker", "save_path", "message", "hash"]
client.get_torrent_status( client.core.get_torrent_status(selected, status_keys).addCallback(self._on_get_torrent_status)
self._on_get_torrent_status, selected, status_keys)
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
# Check to see if we got valid data from the core # Check to see if we got valid data from the core
@ -98,4 +97,3 @@ class DetailsTab(Tab):
def clear(self): def clear(self):
for widget in self.label_widgets: for widget in self.label_widgets:
widget[0].set_text("") widget[0].set_text("")

View file

@ -28,7 +28,7 @@ import pkg_resources
import deluge.common import deluge.common
import deluge.ui.gtkui.common as 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 import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
@ -85,8 +85,7 @@ class EditTrackersDialog:
# Get the trackers for this torrent # Get the trackers for this torrent
client.get_torrent_status( client.core.get_torrent_status(self.torrent_id, ["trackers"]).addCallback(self._on_get_torrent_status)
self._on_get_torrent_status, self.torrent_id, ["trackers"])
client.force_call() client.force_call()
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
@ -169,7 +168,7 @@ class EditTrackersDialog:
self.trackers.append(tracker) self.trackers.append(tracker)
self.liststore.foreach(each, None) self.liststore.foreach(each, None)
# Set the torrens trackers # 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() self.dialog.destroy()
def on_button_cancel_clicked(self, widget): def on_button_cancel_clicked(self, widget):

View file

@ -30,7 +30,7 @@ import os.path
import cPickle import cPickle
from deluge.ui.gtkui.torrentdetails import Tab 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 from deluge.configmanager import ConfigManager
import deluge.configmanager import deluge.configmanager
import deluge.component as component import deluge.component as component
@ -295,7 +295,7 @@ class FilesTab(Tab):
# We already have the files list stored, so just update the view # We already have the files list stored, so just update the view
self.update_files() 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) client.force_call(True)
def clear(self): def clear(self):
@ -304,7 +304,7 @@ class FilesTab(Tab):
def _on_row_activated(self, tree, path, view_column): def _on_row_activated(self, tree, path, view_column):
if client.is_localhost: 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) client.force_call(False)
def get_file_path(self, row, path=""): def get_file_path(self, row, path=""):
@ -469,7 +469,7 @@ class FilesTab(Tab):
priorities = [p[1] for p in file_priorities] priorities = [p[1] for p in file_priorities]
log.debug("priorities: %s", 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): def _on_menuitem_donotdownload_activate(self, menuitem):
self._set_file_priorities_on_user_change( self._set_file_priorities_on_user_change(
@ -523,7 +523,7 @@ class FilesTab(Tab):
log.debug("filepath: %s", filepath) log.debug("filepath: %s", filepath)
client.rename_files(self.torrent_id, [(index, filepath)]) client.core.rename_files(self.torrent_id, [(index, filepath)])
else: else:
# We are renaming a folder # We are renaming a folder
folder = self.treestore[path][0] folder = self.treestore[path][0]
@ -534,7 +534,7 @@ class FilesTab(Tab):
parent_path = self.treestore[itr][0] + parent_path parent_path = self.treestore[itr][0] + parent_path
itr = self.treestore.iter_parent(itr) 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 self._editing_index = None
@ -769,11 +769,11 @@ class FilesTab(Tab):
while itr: while itr:
pp = self.treestore[itr][0] + pp pp = self.treestore[itr][0] + pp
itr = self.treestore.iter_parent(itr) 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: else:
#[(index, filepath), ...] #[(index, filepath), ...]
to_rename = [] to_rename = []
for s in selected: for s in selected:
to_rename.append((model[s][5], parent_path + model[s][0])) to_rename.append((model[s][5], parent_path + model[s][0]))
log.debug("to_rename: %s", to_rename) 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 import deluge.common
from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.tracker_icons import TrackerIcons
from deluge.log import LOG as log from deluge.log import LOG as log
from deluge.ui.client import aclient from deluge.ui.client import client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
STATE_PIX = { STATE_PIX = {
@ -280,7 +280,7 @@ class FilterTreeView(component.Component):
hide_cat = [] hide_cat = []
if not self.config["sidebar_show_trackers"]: if not self.config["sidebar_show_trackers"]:
hide_cat = ["tracker_host"] 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: except Exception, e:
log.debug(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> <glade-interface>
<widget class="GtkDialog" id="addhost_dialog"> <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="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="border_width">5</property>
<property name="title" translatable="yes">Add Host</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="window_position">GTK_WIN_POS_CENTER</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</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="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="border_width">5</property>
<property name="title" translatable="yes">Connection Manager</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="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="default_width">350</property> <property name="default_width">350</property>
<property name="default_height">300</property> <property name="default_height">300</property>
@ -267,6 +271,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<signal name="row_activated" handler="on_hostlist_row_activated"/>
</widget> </widget>
</child> </child>
</widget> </widget>
@ -315,6 +320,22 @@
<property name="fill">False</property> <property name="fill">False</property>
</packing> </packing>
</child> </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> <child>
<widget class="GtkButton" id="button_startdaemon"> <widget class="GtkButton" id="button_startdaemon">
<property name="visible">True</property> <property name="visible">True</property>
@ -445,10 +466,9 @@
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child internal-child="action_area"> <child>
<widget class="GtkHButtonBox" id="dialog-action_area2"> <widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property> <property name="layout_style">GTK_BUTTONBOX_END</property>
<child> <child>
<widget class="GtkButton" id="button_close"> <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="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="label">gtk-close</property>
<property name="use_stock">True</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"/> <signal name="clicked" handler="on_button_close_clicked"/>
</widget> </widget>
<packing> <packing>
@ -471,7 +491,6 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">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="label" translatable="yes">gtk-connect</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property> <property name="response_id">0</property>
@ -486,6 +505,19 @@
</widget> </widget>
<packing> <packing>
<property name="expand">False</property> <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> <property name="pack_type">GTK_PACK_END</property>
</packing> </packing>
</child> </child>

View file

@ -24,20 +24,20 @@
from deluge.log import LOG as log from deluge.log import LOG as log
import pygtk
try: # Install the twisted reactor
pygtk.require('2.0') from twisted.internet import gtk2reactor
except: reactor = gtk2reactor.install()
log.warning("It is suggested that you upgrade your PyGTK to 2.10 or greater.")
import gtk, gtk.glade
import gobject import gobject
import gettext import gettext
import locale import locale
import pkg_resources import pkg_resources
import signal import signal
import gtk, gtk.glade
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
from mainwindow import MainWindow from mainwindow import MainWindow
from menubar import MenuBar from menubar import MenuBar
from toolbar import ToolBar from toolbar import ToolBar
@ -82,7 +82,7 @@ DEFAULT_PREFS = {
"enabled_plugins": [], "enabled_plugins": [],
"show_connection_manager_on_start": True, "show_connection_manager_on_start": True,
"autoconnect": False, "autoconnect": False,
"autoconnect_host_uri": None, "autoconnect_host_id": None,
"autostart_localhost": False, "autostart_localhost": False,
"autoadd_queued": False, "autoadd_queued": False,
"autoadd_enable": False, "autoadd_enable": False,
@ -183,8 +183,8 @@ class GtkUI:
self.ipcinterface = IPCInterface(args) self.ipcinterface = IPCInterface(args)
# We make sure that the UI components start once we get a core URI # We make sure that the UI components start once we get a core URI
client.connect_on_new_core(self._on_new_core) client.register_event_handler("connected", self._on_new_core)
client.connect_on_no_core(self._on_no_core) client.register_event_handler("disconnected", self._on_no_core)
# Start the signal receiver # Start the signal receiver
self.signal_receiver = Signals() self.signal_receiver = Signals()
@ -209,13 +209,13 @@ class GtkUI:
# Show the connection manager # Show the connection manager
self.connectionmanager = ConnectionManager() 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 # Start the gtk main loop
try: try:
gtk.gdk.threads_enter() gtk.gdk.threads_enter()
gtk.main() reactor.run()
gtk.gdk.threads_leave() gtk.gdk.threads_leave()
except KeyboardInterrupt: except KeyboardInterrupt:
self.shutdown() self.shutdown()
@ -229,7 +229,7 @@ class GtkUI:
component.shutdown() component.shutdown()
if self.started_in_classic: if self.started_in_classic:
try: try:
client.daemon.shutdown(None) client.daemon.shutdown()
except: except:
pass pass
@ -241,8 +241,15 @@ class GtkUI:
except RuntimeError: except RuntimeError:
pass 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() component.start()
def _on_no_core(self, data): def _on_no_core(self):
component.stop() component.stop()

View file

@ -27,7 +27,7 @@ import sys
import os.path import os.path
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.common import deluge.common
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log 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").add_from_url(arg)
component.get("AddTorrentDialog").show(config["focus_add_dialog"]) component.get("AddTorrentDialog").show(config["focus_add_dialog"])
else: else:
client.add_torrent_url(arg, None) client.core.add_torrent_url(arg, None)
elif deluge.common.is_magnet(arg): elif deluge.common.is_magnet(arg):
log.debug("Attempting to add %s from external source..", arg) log.debug("Attempting to add %s from external source..", arg)
if config["interactive_add"]: if config["interactive_add"]:
component.get("AddTorrentDialog").add_from_magnets([arg]) component.get("AddTorrentDialog").add_from_magnets([arg])
component.get("AddTorrentDialog").show(config["focus_add_dialog"]) component.get("AddTorrentDialog").show(config["focus_add_dialog"])
else: else:
client.add_torrent_magnets([arg], []) client.core.add_torrent_magnets([arg], [])
else: else:
# Just a file # Just a file
log.debug("Attempting to add %s from external source..", 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").add_from_files([os.path.abspath(arg)])
component.get("AddTorrentDialog").show(config["focus_add_dialog"]) component.get("AddTorrentDialog").show(config["focus_add_dialog"])
else: 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 from urlparse import urlparse
import urllib import urllib
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.component as component import deluge.component as component
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.gtkui.ipcinterface import process_args from deluge.ui.gtkui.ipcinterface import process_args
@ -208,7 +208,7 @@ class MainWindow(component.Component):
upload_rate = deluge.common.fspeed(status["upload_rate"]) upload_rate = deluge.common.fspeed(status["upload_rate"])
self.window.set_title("Deluge - %s %s %s %s" % (_("Down:"), download_rate, _("Up:"), upload_rate)) self.window.set_title("Deluge - %s %s %s %s" % (_("Down:"), download_rate, _("Up:"), upload_rate))
if self.config["show_rate_in_title"]: 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): def _on_set_show_rate_in_title(self, key, value):
if value: if value:

View file

@ -30,7 +30,7 @@ import pkg_resources
import deluge.error import deluge.error
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.common import deluge.common
import deluge.ui.gtkui.common as common import deluge.ui.gtkui.common as common
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
@ -231,16 +231,13 @@ class MenuBar(component.Component):
def on_menuitem_quitdaemon_activate(self, data=None): def on_menuitem_quitdaemon_activate(self, data=None):
log.debug("on_menuitem_quitdaemon_activate") log.debug("on_menuitem_quitdaemon_activate")
# Tell the core to shutdown # Tell the core to shutdown
client.daemon.shutdown(None) client.daemon.shutdown()
self.window.quit() self.window.quit()
def on_menuitem_quit_activate(self, data=None): def on_menuitem_quit_activate(self, data=None):
log.debug("on_menuitem_quit_activate") log.debug("on_menuitem_quit_activate")
if self.config["classic_mode"]: if self.config["classic_mode"]:
try: client.daemon.shutdown()
client.daemon.shutdown(None)
except deluge.error.NoCoreError:
pass
self.window.quit() self.window.quit()
## Edit Menu ## ## Edit Menu ##
@ -255,17 +252,17 @@ class MenuBar(component.Component):
## Torrent Menu ## ## Torrent Menu ##
def on_menuitem_pause_activate(self, data=None): def on_menuitem_pause_activate(self, data=None):
log.debug("on_menuitem_pause_activate") log.debug("on_menuitem_pause_activate")
client.pause_torrent( client.core.pause_torrent(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_resume_activate(self, data=None): def on_menuitem_resume_activate(self, data=None):
log.debug("on_menuitem_resume_activate") log.debug("on_menuitem_resume_activate")
client.resume_torrent( client.core.resume_torrent(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_updatetracker_activate(self, data=None): def on_menuitem_updatetracker_activate(self, data=None):
log.debug("on_menuitem_updatetracker_activate") log.debug("on_menuitem_updatetracker_activate")
client.force_reannounce( client.core.force_reannounce(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_edittrackers_activate(self, data=None): def on_menuitem_edittrackers_activate(self, data=None):
@ -283,7 +280,7 @@ class MenuBar(component.Component):
def on_menuitem_recheck_activate(self, data=None): def on_menuitem_recheck_activate(self, data=None):
log.debug("on_menuitem_recheck_activate") log.debug("on_menuitem_recheck_activate")
client.force_recheck( client.core.force_recheck(
component.get("TorrentView").get_selected_torrents()) component.get("TorrentView").get_selected_torrents())
def on_menuitem_open_folder_activate(self, data=None): def on_menuitem_open_folder_activate(self, data=None):
@ -291,7 +288,7 @@ class MenuBar(component.Component):
def _on_torrent_status(status): def _on_torrent_status(status):
deluge.common.open_file(status["save_path"]) deluge.common.open_file(status["save_path"])
for torrent_id in component.get("TorrentView").get_selected_torrents(): 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): def on_menuitem_move_activate(self, data=None):
log.debug("on_menuitem_move_activate") log.debug("on_menuitem_move_activate")
@ -310,11 +307,11 @@ class MenuBar(component.Component):
if chooser.run() == gtk.RESPONSE_OK: if chooser.run() == gtk.RESPONSE_OK:
result = chooser.get_filename() result = chooser.get_filename()
config["choose_directory_dialog_path"] = result config["choose_directory_dialog_path"] = result
client.move_storage( client.core.move_storage(
component.get("TorrentView").get_selected_torrents(), result) component.get("TorrentView").get_selected_torrents(), result)
chooser.destroy() chooser.destroy()
else: 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) client.force_call(False)
def show_move_storage_dialog(self, status): def show_move_storage_dialog(self, status):
@ -330,26 +327,26 @@ class MenuBar(component.Component):
if response_id == gtk.RESPONSE_OK: if response_id == gtk.RESPONSE_OK:
log.debug("Moving torrents to %s", entry.get_text()) log.debug("Moving torrents to %s", entry.get_text())
path = 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.hide()
dialog.connect("response", _on_response_event) dialog.connect("response", _on_response_event)
dialog.show() dialog.show()
def on_menuitem_queue_top_activate(self, value): def on_menuitem_queue_top_activate(self, value):
log.debug("on_menuitem_queue_top_activate") 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): def on_menuitem_queue_up_activate(self, value):
log.debug("on_menuitem_queue_up_activate") 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): def on_menuitem_queue_down_activate(self, value):
log.debug("on_menuitem_queue_down_activate") 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): def on_menuitem_queue_bottom_activate(self, value):
log.debug("on_menuitem_queue_bottom_activate") 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 ## ## View Menu ##
def on_menuitem_toolbar_toggled(self, value): def on_menuitem_toolbar_toggled(self, value):
@ -385,10 +382,10 @@ class MenuBar(component.Component):
def on_menuitem_set_unlimited(self, widget): def on_menuitem_set_unlimited(self, widget):
log.debug("widget.name: %s", widget.name) log.debug("widget.name: %s", widget.name)
funcs = { funcs = {
"menuitem_down_speed": client.set_torrent_max_download_speed, "menuitem_down_speed": client.core.set_torrent_max_download_speed,
"menuitem_up_speed": client.set_torrent_max_upload_speed, "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
"menuitem_max_connections": client.set_torrent_max_connections, "menuitem_max_connections": client.core.set_torrent_max_connections,
"menuitem_upload_slots": client.set_torrent_max_upload_slots "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
} }
if widget.name in funcs.keys(): if widget.name in funcs.keys():
for torrent in component.get("TorrentView").get_selected_torrents(): for torrent in component.get("TorrentView").get_selected_torrents():
@ -397,10 +394,10 @@ class MenuBar(component.Component):
def on_menuitem_set_other(self, widget): def on_menuitem_set_other(self, widget):
log.debug("widget.name: %s", widget.name) log.debug("widget.name: %s", widget.name)
funcs = { funcs = {
"menuitem_down_speed": client.set_torrent_max_download_speed, "menuitem_down_speed": client.core.set_torrent_max_download_speed,
"menuitem_up_speed": client.set_torrent_max_upload_speed, "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
"menuitem_max_connections": client.set_torrent_max_connections, "menuitem_max_connections": client.core.set_torrent_max_connections,
"menuitem_upload_slots": client.set_torrent_max_upload_slots "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
} }
# widget: (header, type_str, image_stockid, image_filename, default) # widget: (header, type_str, image_stockid, image_filename, default)
other_dialog_info = { other_dialog_info = {
@ -418,11 +415,11 @@ class MenuBar(component.Component):
def on_menuitem_set_automanaged_on(self, widget): def on_menuitem_set_automanaged_on(self, widget):
for torrent in component.get("TorrentView").get_selected_torrents(): 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): def on_menuitem_set_automanaged_off(self, widget):
for torrent in component.get("TorrentView").get_selected_torrents(): 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): def on_menuitem_sidebar_zero_toggled(self, widget):
self.config["sidebar_show_zero"] = widget.get_active() self.config["sidebar_show_zero"] = widget.get_active()

View file

@ -28,7 +28,7 @@ import deluge.common
import deluge.ui.gtkui.common as common import deluge.ui.gtkui.common as common
from deluge.log import LOG as log from deluge.log import LOG as log
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.client import aclient as client from deluge.ui.client import client
class Notification: class Notification:
def __init__(self): def __init__(self):
@ -42,7 +42,7 @@ class Notification:
self.get_torrent_status(torrent_id) self.get_torrent_status(torrent_id)
def get_torrent_status(self, 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): def _on_get_torrent_status(self, status):
if status is None: if status is None:
@ -122,4 +122,3 @@ class Notification:
log.warning("sending email notification of finished torrent failed") log.warning("sending email notification of finished torrent failed")
else: else:
log.info("sending email notification of finished torrent was successful") log.info("sending email notification of finished torrent was successful")

View file

@ -24,7 +24,7 @@
import deluge.component as component 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 from deluge.ui.gtkui.torrentdetails import Tab
class OptionsTab(Tab): class OptionsTab(Tab):
@ -87,7 +87,7 @@ class OptionsTab(Tab):
if torrent_id != self.prev_torrent_id: if torrent_id != self.prev_torrent_id:
self.prev_status = None 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_download_speed",
"max_upload_speed", "max_upload_speed",
"max_connections", "max_connections",
@ -99,7 +99,7 @@ class OptionsTab(Tab):
"stop_ratio", "stop_ratio",
"remove_at_ratio", "remove_at_ratio",
"move_on_completed", "move_on_completed",
"move_on_completed_path"]) "move_on_completed_path"]).addCallback(self._on_get_torrent_status)
self.prev_torrent_id = torrent_id self.prev_torrent_id = torrent_id
def clear(self): def clear(self):
@ -147,31 +147,31 @@ class OptionsTab(Tab):
def _on_button_apply_clicked(self, button): def _on_button_apply_clicked(self, button):
if self.spin_max_download.get_value() != self.prev_status["max_download_speed"]: 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"]: 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"]: 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"]: 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"]: 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"]: 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"]: 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"]: 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"]: 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"]: 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 self.chk_move_completed.get_active():
if client.is_localhost(): if client.is_localhost():
path = self.filechooser_move_completed.get_current_folder() path = self.filechooser_move_completed.get_current_folder()
else: else:
path = self.entry_move_completed.get_text() 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): def _on_button_edit_trackers_clicked(self, button):

View file

@ -30,7 +30,7 @@ import pkg_resources
import gobject import gobject
from itertools import izip from itertools import izip
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
import deluge.configmanager import deluge.configmanager
import deluge.component as component import deluge.component as component
@ -244,7 +244,7 @@ class PeersTab(Tab):
self.peers = {} self.peers = {}
self.torrent_id = torrent_id 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): def get_flag_pixbuf(self, country):
if country == " ": if country == " ":

View file

@ -25,7 +25,7 @@
import deluge.component as component import deluge.component as component
import deluge.pluginmanagerbase import deluge.pluginmanagerbase
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log from deluge.log import LOG as log
@ -59,7 +59,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
def start(self): def start(self):
"""Start the plugin manager""" """Start the plugin manager"""
# Update the enabled_plugins from the core # 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): def stop(self):
# Disable the plugins # Disable the plugins

View file

@ -30,7 +30,7 @@ import pkg_resources
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log 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.common
import deluge.error import deluge.error
import deluge.ui.gtkui.common as common import deluge.ui.gtkui.common as common
@ -152,6 +152,7 @@ class Preferences(component.Component):
self.liststore.remove(self.iter_to_remove) self.liststore.remove(self.iter_to_remove)
def _on_get_config(self, config): def _on_get_config(self, config):
log.debug("on_get_config: %s", config)
self.core_config = config self.core_config = config
def _on_get_available_plugins(self, plugins): def _on_get_available_plugins(self, plugins):
@ -174,10 +175,10 @@ class Preferences(component.Component):
# Update the preferences dialog to reflect current config settings # Update the preferences dialog to reflect current config settings
self.core_config = {} self.core_config = {}
try: try:
client.get_config(self._on_get_config) client.core.get_config().addCallback(self._on_get_config)
client.get_available_plugins(self._on_get_available_plugins) client.core.get_available_plugins().addCallback(self._on_get_available_plugins)
client.get_enabled_plugins(self._on_get_enabled_plugins) client.core.get_enabled_plugins().addCallback(self._on_get_enabled_plugins)
client.get_listen_port(self._on_get_listen_port) client.core.get_listen_port().addCallback(self._on_get_listen_port)
# Force these calls and block until we've done them all # Force these calls and block until we've done them all
client.force_call() client.force_call()
except deluge.error.NoCoreError: except deluge.error.NoCoreError:
@ -704,7 +705,7 @@ class Preferences(component.Component):
self.gtkui_config[key] = new_gtkui_config[key] self.gtkui_config[key] = new_gtkui_config[key]
# Core # Core
if client.get_core_uri() != None: if client.connected():
# Only do this if we're connected to a daemon # Only do this if we're connected to a daemon
config_to_set = {} config_to_set = {}
for key in new_core_config.keys(): for key in new_core_config.keys():
@ -713,7 +714,7 @@ class Preferences(component.Component):
config_to_set[key] = new_core_config[key] config_to_set[key] = new_core_config[key]
# Set each changed config value in the core # 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) client.force_call(True)
# Update the configuration # Update the configuration
self.core_config.update(config_to_set) self.core_config.update(config_to_set)
@ -801,7 +802,7 @@ class Preferences(component.Component):
else: else:
self.glade.get_widget("port_img").set_from_stock(gtk.STOCK_DIALOG_WARNING, 4) self.glade.get_widget("port_img").set_from_stock(gtk.STOCK_DIALOG_WARNING, 4)
self.glade.get_widget("port_img").show() 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() client.force_call()
def on_plugin_toggled(self, renderer, path): def on_plugin_toggled(self, renderer, path):
@ -811,10 +812,10 @@ class Preferences(component.Component):
value = self.plugin_liststore.get_value(row, 1) value = self.plugin_liststore.get_value(row, 1)
self.plugin_liststore.set_value(row, 1, not value) self.plugin_liststore.set_value(row, 1, not value)
if not value: if not value:
client.enable_plugin(name) client.core.enable_plugin(name)
component.get("PluginManager").enable_plugin(name) component.get("PluginManager").enable_plugin(name)
else: else:
client.disable_plugin(name) client.core.disable_plugin(name)
component.get("PluginManager").disable_plugin(name) component.get("PluginManager").disable_plugin(name)
def on_plugin_selection_changed(self, treeselection): def on_plugin_selection_changed(self, treeselection):
@ -867,16 +868,16 @@ class Preferences(component.Component):
if not client.is_localhost(): if not client.is_localhost():
# We need to send this plugin to the daemon # We need to send this plugin to the daemon
client.upload_plugin( client.core.upload_plugin(
filename, filename,
xmlrpclib.Binary(open(filepath, "rb").read())) xmlrpclib.Binary(open(filepath, "rb").read()))
client.rescan_plugins() client.core.rescan_plugins()
chooser.destroy() chooser.destroy()
# We need to re-show the preferences dialog to show the new plugins # We need to re-show the preferences dialog to show the new plugins
self.show() self.show()
def _on_button_rescan_plugins_clicked(self, widget): def _on_button_rescan_plugins_clicked(self, widget):
component.get("PluginManager").scan_for_plugins() component.get("PluginManager").scan_for_plugins()
client.rescan_plugins() client.core.rescan_plugins()
self.show() self.show()

View file

@ -30,7 +30,7 @@ import gobject
import pkg_resources import pkg_resources
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.common import deluge.common
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log 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").add_from_url(torrent_path)
component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"])
else: else:
client.add_torrent_url(torrent_path, None) client.core.add_torrent_url(torrent_path, None)
elif deluge.common.is_magnet(torrent_path): elif deluge.common.is_magnet(torrent_path):
if self.config["interactive_add"]: if self.config["interactive_add"]:
component.get("AddTorrentDialog").add_from_magnets([torrent_path]) component.get("AddTorrentDialog").add_from_magnets([torrent_path])
component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"])
else: else:
client.add_magnet_uris([torrent_path], []) client.core.add_magnet_uris([torrent_path], [])
else: else:
if self.config["interactive_add"]: if self.config["interactive_add"]:
component.get("AddTorrentDialog").add_from_files([torrent_path]) component.get("AddTorrentDialog").add_from_files([torrent_path])
component.get("AddTorrentDialog").show(self.config["focus_add_dialog"]) component.get("AddTorrentDialog").show(self.config["focus_add_dialog"])
else: else:
client.add_torrent_file([torrent_path]) client.core.add_torrent_file([torrent_path])
self.liststore.foreach(add_torrent, None) self.liststore.foreach(add_torrent, None)
del self.queue[:] del self.queue[:]
@ -187,5 +187,3 @@ class QueuedTorrents(component.Component):
def on_chk_autoadd_toggled(self, widget): def on_chk_autoadd_toggled(self, widget):
self.config["autoadd_queued"] = widget.get_active() self.config["autoadd_queued"] = widget.get_active()

View file

@ -25,7 +25,7 @@
import gtk, gtk.glade import gtk, gtk.glade
import pkg_resources import pkg_resources
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
@ -71,7 +71,7 @@ class RemoveTorrentDialog(object):
button_data.set_label(pluralize_torrents(button_data.get_label())) button_data.set_label(pluralize_torrents(button_data.get_label()))
def __remove_torrents(self, remove_data): 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 # Unselect all to avoid issues with the selection changed event
component.get("TorrentView").treeview.get_selection().unselect_all() component.get("TorrentView").treeview.get_selection().unselect_all()

View file

@ -26,7 +26,7 @@
import gtk import gtk
import deluge.component as component 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.ui.signalreceiver import SignalReceiver
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log from deluge.log import LOG as log
@ -34,12 +34,13 @@ from deluge.log import LOG as log
class Signals(component.Component): class Signals(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "Signals") component.Component.__init__(self, "Signals")
self.receiver = SignalReceiver() # self.receiver = SignalReceiver()
self.config = ConfigManager("gtkui.conf") self.config = ConfigManager("gtkui.conf")
self.config["signal_port"] = self.receiver.get_port() #self.config["signal_port"] = self.receiver.get_port()
self.config.save() self.config.save()
def start(self): def start(self):
return
self.receiver.set_remote(not client.is_localhost()) self.receiver.set_remote(not client.is_localhost())
self.receiver.run() self.receiver.run()
@ -70,6 +71,7 @@ class Signals(component.Component):
self.torrent_finished) self.torrent_finished)
def stop(self): def stop(self):
return
try: try:
self.receiver.shutdown() self.receiver.shutdown()
except: except:
@ -77,7 +79,8 @@ class Signals(component.Component):
def connect_to_signal(self, signal, callback): def connect_to_signal(self, signal, callback):
"""Connects a callback to a signal""" """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): def torrent_finished(self, torrent_id):
log.debug("torrent_finished signal received..") log.debug("torrent_finished signal received..")

View file

@ -26,7 +26,7 @@
import gtk, gtk.glade 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.component as component
import deluge.common import deluge.common
from deluge.ui.gtkui.torrentdetails import Tab from deluge.ui.gtkui.torrentdetails import Tab
@ -105,8 +105,8 @@ class StatusTab(Tab):
"max_upload_speed", "max_download_speed", "active_time", "max_upload_speed", "max_download_speed", "active_time",
"seeding_time", "seed_rank", "is_auto_managed", "time_added"] "seeding_time", "seed_rank", "is_auto_managed", "time_added"]
client.get_torrent_status( client.core.get_torrent_status(
self._on_get_torrent_status, selected, status_keys) selected, status_keys).addCallback(self._on_get_torrent_status)
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
# Check to see if we got valid data from the core # Check to see if we got valid data from the core

View file

@ -26,11 +26,11 @@
import gtk import gtk
import gobject import gobject
from deluge.ui.client import client
import deluge.component as component import deluge.component as component
import deluge.common import deluge.common
import deluge.ui.gtkui.common as common import deluge.ui.gtkui.common as common
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.client import aclient as client
from deluge.log import LOG as log from deluge.log import LOG as log
class StatusBarItem: class StatusBarItem:
@ -170,15 +170,14 @@ class StatusBar(component.Component):
self.health = False self.health = False
# Get some config values # Get some config values
client.get_config_value( client.core.get_config_value(
self._on_max_connections_global, "max_connections_global") "max_connections_global").addCallback(self._on_max_connections_global)
client.get_config_value( client.core.get_config_value(
self._on_max_download_speed, "max_download_speed") "max_download_speed").addCallback(self._on_max_download_speed)
client.get_config_value( client.core.get_config_value(
self._on_max_upload_speed, "max_upload_speed") "max_upload_speed").addCallback(self._on_max_upload_speed)
client.get_config_value( client.core.get_config_value("dht").addCallback(self._on_dht)
self._on_dht, "dht") client.core.get_health().addCallback(self._on_get_health)
client.get_health(self._on_get_health)
self.send_status_request() self.send_status_request()
@ -250,15 +249,18 @@ class StatusBar(component.Component):
def send_status_request(self): def send_status_request(self):
# Sends an async request for data from the core # 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: if self.dht_status:
client.get_dht_nodes(self._on_get_dht_nodes) client.core.get_dht_nodes().addCallback(self._on_get_dht_nodes)
client.get_session_status(self._on_get_session_status, client.core.get_session_status([
["upload_rate", "download_rate", "payload_upload_rate", "payload_download_rate"]) "upload_rate",
"download_rate",
"payload_upload_rate",
"payload_download_rate"]).addCallback(self._on_get_session_status)
if not self.health: if not self.health:
# Only request health status while False # 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): def config_value_changed(self, key, value):
"""This is called when we received a config_value_changed signal from """This is called when we received a config_value_changed signal from
@ -284,7 +286,7 @@ class StatusBar(component.Component):
if value: if value:
self.hbox.pack_start( self.hbox.pack_start(
self.dht_item.get_eventbox(), expand=False, fill=False) 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: else:
self.remove_item(self.dht_item) self.remove_item(self.dht_item)
@ -377,7 +379,7 @@ class StatusBar(component.Component):
# Set the config in the core # Set the config in the core
if value != self.max_download_speed: 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): def _on_upload_item_clicked(self, widget, event):
menu = common.build_menu_radio_list( menu = common.build_menu_radio_list(
@ -405,7 +407,7 @@ class StatusBar(component.Component):
# Set the config in the core # Set the config in the core
if value != self.max_upload_speed: 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): def _on_connection_item_clicked(self, widget, event):
menu = common.build_menu_radio_list( menu = common.build_menu_radio_list(
@ -432,7 +434,7 @@ class StatusBar(component.Component):
# Set the config in the core # Set the config in the core
if value != self.max_connections: 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): def _on_health_icon_clicked(self, widget, event):
component.get("Preferences").show("Network") component.get("Preferences").show("Network")

View file

@ -27,7 +27,7 @@ import gtk
import pkg_resources import pkg_resources
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.common import deluge.common
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.log import LOG as log from deluge.log import LOG as log
@ -113,7 +113,7 @@ class SystemTray(component.Component):
self.tray_glade.get_widget("separatormenuitem4").hide() self.tray_glade.get_widget("separatormenuitem4").hide()
component.get("Signals").connect_to_signal("config_value_changed", self.config_value_changed) 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. # Hide menu widgets because we're not connected to a host.
for widget in self.hide_widget_list: for widget in self.hide_widget_list:
self.tray_glade.get_widget(widget).hide() self.tray_glade.get_widget(widget).hide()
@ -132,10 +132,10 @@ class SystemTray(component.Component):
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
# Get some config values # Get some config values
client.get_config_value( client.core.get_config_value(
self._on_max_download_speed, "max_download_speed") "max_download_speed").addCallback(self._on_max_download_speed)
client.get_config_value( client.core.get_config_value(
self._on_max_upload_speed, "max_upload_speed") "max_upload_speed").addCallback(self._on_max_upload_speed)
self.send_status_request() self.send_status_request()
def start(self): def start(self):
@ -153,8 +153,8 @@ class SystemTray(component.Component):
self.tray.set_visible(False) self.tray.set_visible(False)
def send_status_request(self): def send_status_request(self):
client.get_download_rate(self._on_get_download_rate) client.core.get_download_rate().addCallback(self._on_get_download_rate)
client.get_upload_rate(self._on_get_upload_rate) client.core.get_upload_rate().addCallback(self._on_get_upload_rate)
def config_value_changed(self, key, value): def config_value_changed(self, key, value):
"""This is called when we received a config_value_changed signal from """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): def on_menuitem_add_torrent_activate(self, menuitem):
log.debug("on_menuitem_add_torrent_activate") log.debug("on_menuitem_add_torrent_activate")
from addtorrentdialog import AddTorrentDialog 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): def on_menuitem_pause_all_activate(self, menuitem):
log.debug("on_menuitem_pause_all_activate") log.debug("on_menuitem_pause_all_activate")
client.pause_all_torrents() client.core.pause_all_torrents()
def on_menuitem_resume_all_activate(self, menuitem): def on_menuitem_resume_all_activate(self, menuitem):
log.debug("on_menuitem_resume_all_activate") log.debug("on_menuitem_resume_all_activate")
client.resume_all_torrents() client.core.resume_all_torrents()
def on_menuitem_quit_activate(self, menuitem): def on_menuitem_quit_activate(self, menuitem):
log.debug("on_menuitem_quit_activate") log.debug("on_menuitem_quit_activate")
@ -308,7 +308,7 @@ class SystemTray(component.Component):
return return
if self.config["classic_mode"]: if self.config["classic_mode"]:
client.daemon.shutdown(None) client.daemon.shutdown()
self.window.quit() self.window.quit()
@ -318,7 +318,7 @@ class SystemTray(component.Component):
if not self.unlock_tray(): if not self.unlock_tray():
return return
client.daemon.shutdown(None) client.daemon.shutdown()
self.window.quit() self.window.quit()
def tray_setbwdown(self, widget, data=None): def tray_setbwdown(self, widget, data=None):
@ -341,7 +341,7 @@ class SystemTray(component.Component):
return return
# Set the config in the core # Set the config in the core
client.set_config({core_key: value}) client.core.set_config({core_key: value})
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()

View file

@ -31,7 +31,6 @@ import gobject
import deluge.component as component import deluge.component as component
from deluge.log import LOG as log from deluge.log import LOG as log
from deluge.common import TORRENT_STATE from deluge.common import TORRENT_STATE
from deluge.ui.client import aclient as client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
class ToolBar(component.Component): class ToolBar(component.Component):

View file

@ -31,7 +31,7 @@ import os.path
import cPickle import cPickle
import deluge.component as component import deluge.component as component
from deluge.ui.client import aclient as client from deluge.ui.client import client
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
import deluge.configmanager import deluge.configmanager

View file

@ -34,7 +34,7 @@ from urlparse import urlparse
import deluge.common import deluge.common
import deluge.component as component 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 from deluge.log import LOG as log
import deluge.ui.gtkui.listview as listview import deluge.ui.gtkui.listview as listview
@ -197,9 +197,10 @@ class TorrentView(listview.ListView, component.Component):
"""Start the torrentview""" """Start the torrentview"""
# We need to get the core session state to know which torrents are in # We need to get the core session state to know which torrents are in
# the session so we can add them to our list. # 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): def _on_session_state(self, state):
log.debug("on_session_state")
self.treeview.freeze_child_notify() self.treeview.freeze_child_notify()
model = self.treeview.get_model() model = self.treeview.get_model()
for torrent_id in state: 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 # Request the statuses for all these torrent_ids, this is async so we
# will deal with the return in a signal callback. # will deal with the return in a signal callback.
client.get_torrents_status( client.core.get_torrents_status(
self._on_get_torrents_status, self.filter, status_keys) self.filter, status_keys).addCallback(self._on_get_torrents_status)
def update(self): def update(self):
# Send a status request # Send a status request

View file

@ -29,7 +29,7 @@ import random
import gobject import gobject
from deluge.ui.client import aclient as client from deluge.ui.client import client
import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
import deluge.xmlrpclib as xmlrpclib import deluge.xmlrpclib as xmlrpclib
@ -149,4 +149,3 @@ class SignalReceiver(ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer):
self.signals[signal].append(callback) self.signals[signal].append(callback)
except KeyError: except KeyError:
self.signals[signal] = [callback] self.signals[signal] = [callback]

View file

@ -61,6 +61,7 @@ class UI:
log.info("Starting ConsoleUI..") log.info("Starting ConsoleUI..")
from deluge.ui.console.main import ConsoleUI from deluge.ui.console.main import ConsoleUI
ui = ConsoleUI(ui_args).run() 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) 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) sys.exit(0)