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