From b4cb4e7bcc15d292ee70e1aad5d32f11cbde8921 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Fri, 12 Oct 2007 23:37:42 +0000 Subject: [PATCH 01/13] Import the 0.6 xmlrpc branch.. Work in progess. --- deluge/core/core.py | 218 ++++++++++++------------------ deluge/core/daemon.py | 18 +-- deluge/ui/functions.py | 103 +++++++------- deluge/ui/gtkui/gtkui.py | 4 +- deluge/ui/gtkui/statusbar.py | 9 +- deluge/ui/gtkui/systemtray.py | 11 +- deluge/ui/gtkui/torrentdetails.py | 6 +- deluge/ui/gtkui/torrentview.py | 5 +- 8 files changed, 153 insertions(+), 221 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index fcd152db4..c830ed496 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -31,15 +31,17 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -import dbus.service -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) import gettext import locale import pkg_resources +import sys +import pickle +import SimpleXMLRPCServer +from SocketServer import ThreadingMixIn +import xmlrpclib import gobject +import threading import deluge.libtorrent as lt from deluge.configmanager import ConfigManager @@ -73,9 +75,27 @@ DEFAULT_PREFS = { "max_upload_slots_per_torrent": -1, "enabled_plugins": ["Queue"] } + +class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): + def __init__(self): + log.debug("Core init..") + threading.Thread.__init__(self) -class Core(dbus.service.Object): - def __init__(self, path="/org/deluge_torrent/Core"): + # Setup the xmlrpc server + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, ("localhost", 6666), logRequests=False, allow_none=True) + except: + log.info("Daemon already running or port not available..") + sys.exit(0) + + # Register all export_* functions + for func in dir(self): + if func.startswith("export_"): + self.register_function(getattr(self, "%s" % func), func[7:]) + + self.register_introspection_functions() + # Initialize gettext locale.setlocale(locale.LC_MESSAGES, '') locale.bindtextdomain("deluge", @@ -89,13 +109,9 @@ class Core(dbus.service.Object): gettext.install("deluge", pkg_resources.resource_filename( "deluge", "i18n")) - log.debug("Core init..") - # Setup DBUS - bus_name = dbus.service.BusName("org.deluge_torrent.Deluge", - bus=dbus.SessionBus()) - dbus.service.Object.__init__(self, bus_name, path) - + def run(self): + """Starts the core""" # Get config self.config = ConfigManager("core.conf", DEFAULT_PREFS) @@ -119,29 +135,29 @@ class Core(dbus.service.Object): # Register set functions in the Config self.config.register_set_function("listen_ports", - self.on_set_listen_ports) + self._on_set_listen_ports) self.config.register_set_function("random_port", - self.on_set_random_port) - self.config.register_set_function("dht", self.on_set_dht) - self.config.register_set_function("upnp", self.on_set_upnp) - self.config.register_set_function("natpmp", self.on_set_natpmp) - self.config.register_set_function("utpex", self.on_set_utpex) + self._on_set_random_port) + self.config.register_set_function("dht", self._on_set_dht) + self.config.register_set_function("upnp", self._on_set_upnp) + self.config.register_set_function("natpmp", self._on_set_natpmp) + self.config.register_set_function("utpex", self._on_set_utpex) self.config.register_set_function("enc_in_policy", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_out_policy", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_level", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("enc_prefer_rc4", - self.on_set_encryption) + self._on_set_encryption) self.config.register_set_function("max_connections_global", - self.on_set_max_connections_global) + self._on_set_max_connections_global) self.config.register_set_function("max_upload_speed", - self.on_set_max_upload_speed) + self._on_set_max_upload_speed) self.config.register_set_function("max_download_speed", - self.on_set_max_download_speed) + self._on_set_max_download_speed) self.config.register_set_function("max_upload_slots_global", - self.on_set_max_upload_slots_global) + self._on_set_max_upload_slots_global) # Start the AlertManager self.alerts = AlertManager(self.session) @@ -154,16 +170,18 @@ class Core(dbus.service.Object): # Register alert handlers self.alerts.register_handler("torrent_paused_alert", - self.on_alert_torrent_paused) - - log.debug("Starting main loop..") + self._on_alert_torrent_paused) + + t = threading.Thread(target=self.serve_forever) + t.start() + gobject.threads_init() + self.loop = gobject.MainLoop() self.loop.run() - + def _shutdown(self): """This is called by a thread from shutdown()""" log.info("Shutting down core..") - self.loop.quit() self.plugins.shutdown() self.torrents.shutdown() # Make sure the config file has been saved @@ -171,25 +189,22 @@ class Core(dbus.service.Object): del self.config del deluge.configmanager del self.session + self.loop.quit() # Exported Methods - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", out_signature="") - def shutdown(self): + def export_shutdown(self): """Shutdown the core""" # Make shutdown an async call gobject.idle_add(self._shutdown) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="ssay", out_signature="b") - def add_torrent_file(self, filename, save_path, filedump): + def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content """ if save_path == "": save_path = None - torrent_id = self.torrents.add(filename, filedump=filedump, + torrent_id = self.torrents.add(filename, filedump=filedump.data, save_path=save_path) # Run the plugin hooks for 'post_torrent_add' @@ -203,9 +218,7 @@ class Core(dbus.service.Object): # Return False because the torrent was not added successfully return False - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="ss", out_signature="b") - def add_torrent_url(self, url, save_path): + def export_add_torrent_url(self, url, save_path): log.info("Attempting to add url %s", url) # Get the actual filename of the torrent from the url provided. @@ -224,11 +237,9 @@ class Core(dbus.service.Object): return False # Add the torrent to session - return self.add_torrent_file(filename, save_path, filedump) + return self.export_add_torrent_file(filename, save_path, filedump) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def remove_torrent(self, torrent_id): + def export_remove_torrent(self, torrent_id): log.debug("Removing torrent %s from the core.", torrent_id) if self.torrents.remove(torrent_id): # Run the plugin hooks for 'post_torrent_remove' @@ -236,43 +247,32 @@ class Core(dbus.service.Object): # Emit the torrent_removed signal self.torrent_removed(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def force_reannounce(self, torrent_id): + def export_force_reannounce(self, torrent_id): log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) self.torrents.force_reannounce(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def pause_torrent(self, torrent_id): + def export_pause_torrent(self, torrent_id): log.debug("Pausing torrent %s", torrent_id) if not self.torrents.pause(torrent_id): log.warning("Error pausing torrent %s", torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def pause_all_torrents(self): + def export_pause_all_torrents(self): """Pause all torrents in the session""" if not self.torrents.pause_all(): log.warning("Error pausing all torrents..") - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def resume_all_torrents(self): + def export_resume_all_torrents(self): """Resume all torrents in the session""" if self.torrents.resume_all(): # Emit the 'torrent_all_resumed' signal self.torrent_all_resumed() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", out_signature="") - def resume_torrent(self, torrent_id): + def export_resume_torrent(self, torrent_id): log.debug("Resuming torrent %s", torrent_id) if self.torrents.resume(torrent_id): self.torrent_resumed(torrent_id) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="sas", - out_signature="a{sv}") - def get_torrent_status(self, torrent_id, keys): + def export_get_torrent_status(self, torrent_id, keys): # Convert the array of strings to a python list of strings keys = deluge.common.pythonize(keys) # Build the status dictionary @@ -286,33 +286,23 @@ class Core(dbus.service.Object): leftover_fields = list(set(keys) - set(status.keys())) if len(leftover_fields) > 0: status.update(self.plugins.get_status(torrent_id, leftover_fields)) - return status + return xmlrpclib.Binary(pickle.dumps(status)) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="as") - def get_session_state(self): + def export_get_session_state(self): """Returns a list of torrent_ids in the session.""" # Get the torrent list from the TorrentManager return self.torrents.get_torrent_list() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge") - def save_state(self): + def export_save_state(self): """Save the current session state to file.""" # Have the TorrentManager save it's state self.torrents.save_state() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="", - out_signature="a{sv}") - def get_config(self): + def export_get_config(self): """Get all the preferences as a dictionary""" return self.config.get_config() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s", - out_signature="v") - def get_config_value(self, key): + def export_get_config_value(self, key): """Get the config value for key""" try: value = self.config[key] @@ -320,105 +310,77 @@ class Core(dbus.service.Object): return None return value - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="a{sv}") - def set_config(self, config): + + def export_set_config(self, config): """Set the config with values from dictionary""" config = deluge.common.pythonize(config) # Load all the values into the configuration for key in config.keys(): self.config[key] = config[key] - - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="i") - def get_listen_port(self): + + def export_get_listen_port(self): """Returns the active listen port""" return self.session.listen_port() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="i") - def get_num_connections(self): + def export_get_num_connections(self): """Returns the current number of connections""" return self.session.num_connections() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="d") - def get_download_rate(self): + def export_get_download_rate(self): """Returns the payload download rate""" return self.session.status().payload_download_rate - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="d") - def get_upload_rate(self): + def export_get_upload_rate(self): """Returns the payload upload rate""" return self.session.status().payload_upload_rate - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="as") - def get_available_plugins(self): + def export_get_available_plugins(self): """Returns a list of plugins available in the core""" return self.plugins.get_available_plugins() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - out_signature="as") - def get_enabled_plugins(self): + def export_get_enabled_plugins(self): """Returns a list of enabled plugins in the core""" return self.plugins.get_enabled_plugins() - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s") - def enable_plugin(self, plugin): + def export_enable_plugin(self, plugin): self.plugins.enable_plugin(plugin) - @dbus.service.method(dbus_interface="org.deluge_torrent.Deluge", - in_signature="s") - def disable_plugin(self, plugin): + def export_disable_plugin(self, plugin): self.plugins.disable_plugin(plugin) # Signals - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_removed(self, torrent_id): """Emitted when a torrent has been removed from the core""" log.debug("torrent_remove signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge", - signature="s") def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") def torrent_all_paused(self): """Emitted when all torrents have been paused""" log.debug("torrent_all_paused signal emitted") - @dbus.service.signal(dbus_interface="org.deluge_torrent.Deluge") def torrent_all_resumed(self): """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") # Config set functions - def on_set_listen_ports(self, key, value): + def _on_set_listen_ports(self, key, value): # Only set the listen ports if random_port is not true if self.config["random_port"] is not True: log.debug("listen port range set to %s-%s", value[0], value[1]) self.session.listen_on(value[0], value[1]) - def on_set_random_port(self, key, value): + def _on_set_random_port(self, key, value): log.debug("random port value set to %s", value) # We need to check if the value has been changed to true and false # and then handle accordingly. @@ -436,33 +398,33 @@ class Core(dbus.service.Object): listen_ports[1]) self.session.listen_on(listen_ports[0], listen_ports[1]) - def on_set_dht(self, key, value): + def _on_set_dht(self, key, value): log.debug("dht value set to %s", value) if value: self.session.start_dht(None) else: self.session.stop_dht() - def on_set_upnp(self, key, value): + def _on_set_upnp(self, key, value): log.debug("upnp value set to %s", value) if value: self.session.start_upnp() else: self.session.stop_upnp() - def on_set_natpmp(self, key, value): + def _on_set_natpmp(self, key, value): log.debug("natpmp value set to %s", value) if value: self.session.start_natpmp() else: self.session.stop_natpmp() - def on_set_utpex(self, key, value): + def _on_set_utpex(self, key, value): log.debug("utpex value set to %s", value) if value: self.session.add_extension(lt.create_ut_pex_plugin) - def on_set_encryption(self, key, value): + def _on_set_encryption(self, key, value): log.debug("encryption value %s set to %s..", key, value) pe_settings = lt.pe_settings() pe_settings.out_enc_policy = \ @@ -479,26 +441,26 @@ class Core(dbus.service.Object): set.allowed_enc_level, set.prefer_rc4) - def on_set_max_connections_global(self, key, value): + def _on_set_max_connections_global(self, key, value): log.debug("max_connections_global set to %s..", value) self.session.set_max_connections(value) - def on_set_max_upload_speed(self, key, value): + def _on_set_max_upload_speed(self, key, value): log.debug("max_upload_speed set to %s..", value) # We need to convert Kb/s to B/s self.session.set_upload_rate_limit(int(value * 1024)) - def on_set_max_download_speed(self, key, value): + def _on_set_max_download_speed(self, key, value): log.debug("max_download_speed set to %s..", value) # We need to convert Kb/s to B/s self.session.set_download_rate_limit(int(value * 1024)) - def on_set_max_upload_slots_global(self, key, value): + def _on_set_max_upload_slots_global(self, key, value): log.debug("max_upload_slots_global set to %s..", value) self.session.set_max_uploads(value) ## Alert handlers ## - def on_alert_torrent_paused(self, alert): + def _on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index e4bc29603..024cebe54 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -31,23 +31,13 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import dbus -from dbus.mainloop.glib import DBusGMainLoop - from deluge.core.core import Core from deluge.log import LOG as log class Daemon: def __init__(self): - # Check to see if the daemon is already running and if not, start it - bus = dbus.SessionBus() - obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") - iface = dbus.Interface(obj, "org.freedesktop.DBus") - if iface.NameHasOwner("org.deluge_torrent.Deluge"): - # Daemon is running so lets tell the user - log.info("Daemon is already running..") - else: - # Daemon is not running so lets start up the core - log.debug("Daemon is not running..") - self.core = Core() + # Start the core as a thread and join it until it's done + self.core = Core() + self.core.start() + self.core.join() diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index e085d7024..7a0e614a8 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -32,10 +32,12 @@ # statement from all source files in the program, then also delete it here. import os.path +import pickle -import dbus -from dbus.mainloop.glib import DBusGMainLoop -DBusGMainLoop(set_as_default=True) +import xmlrpclib +#import dbus +#from dbus.mainloop.glib import DBusGMainLoop +#DBusGMainLoop(set_as_default=True) import pygtk pygtk.require('2.0') @@ -44,16 +46,22 @@ import gtk, gtk.glade import deluge.common from deluge.log import LOG as log +class CoreProxy: + def __init__(self): + self._core = None + + def get_core(self): + if self._core is None: + log.debug("Creating ServerProxy..") + self._core = xmlrpclib.ServerProxy("http://localhost:6666") + + return self._core + +_core = CoreProxy() + def get_core(): """Get the core object and return it""" - log.debug("Getting core proxy object from DBUS..") - # Get the proxy object from DBUS - bus = dbus.SessionBus() - proxy = bus.get_object("org.deluge_torrent.Deluge", - "/org/deluge_torrent/Core") - core = dbus.Interface(proxy, "org.deluge_torrent.Deluge") - log.debug("Got core proxy object..") - return core + return _core.get_core() def get_core_plugin(plugin): """Get the core plugin object and return it""" @@ -66,9 +74,7 @@ def get_core_plugin(plugin): def shutdown(): """Shutdown the core daemon""" - core = get_core() - core.shutdown() - return + get_core().shutdown() def add_torrent_file(torrent_files): """Adds torrent files to the core @@ -78,25 +84,25 @@ def add_torrent_file(torrent_files): log.debug("No torrent files selected..") return log.debug("Attempting to add torrent files: %s", torrent_files) - core = get_core() for torrent_file in torrent_files: # Open the .torrent file for reading because we need to send it's # contents to the core. f = open(torrent_file, "rb") # Get the filename because the core doesn't want a path. (path, filename) = os.path.split(torrent_file) - result = core.add_torrent_file(filename, str(), f.read()) + fdump = xmlrpclib.Binary(f.read()) f.close() + result = get_core().add_torrent_file(filename, str(), fdump) + if result is False: # The torrent was not added successfully. log.warning("Torrent %s was not added successfully.", filename) def add_torrent_url(torrent_url): """Adds torrents to the core via url""" - core = get_core() from deluge.common import is_url if is_url(torrent_url): - result = core.add_torrent_url(torrent_url, str()) + result = get_core().add_torrent_url(torrent_url, str()) if result is False: # The torrent url was not added successfully. log.warning("Torrent %s was not added successfully.", torrent_url) @@ -106,69 +112,52 @@ def add_torrent_url(torrent_url): def remove_torrent(torrent_ids): """Removes torrent_ids from the core.. Expects a list of torrent_ids""" log.debug("Attempting to removing torrents: %s", torrent_ids) - core = get_core() for torrent_id in torrent_ids: - core.remove_torrent(torrent_id) + get_core().remove_torrent(torrent_id) def pause_torrent(torrent_ids): """Pauses torrent_ids""" - core = get_core() for torrent_id in torrent_ids: - core.pause_torrent(torrent_id) + get_core().pause_torrent(torrent_id) def resume_torrent(torrent_ids): """Resume torrent_ids""" - core = get_core() for torrent_id in torrent_ids: - core.resume_torrent(torrent_id) + get_core().resume_torrent(torrent_id) def force_reannounce(torrent_ids): """Reannounce to trackers""" - core = get_core() for torrent_id in torrent_ids: - core.force_reannounce(torrent_id) + get_core().force_reannounce(torrent_id) -def get_torrent_status(core, torrent_id, keys): +def get_torrent_status(torrent_id, keys): """Builds the status dictionary and returns it""" - return deluge.common.pythonize(core.get_torrent_status(torrent_id, keys)) + status = get_core().get_torrent_status(torrent_id, keys) + return pickle.loads(status.data) -def get_session_state(core=None): - # Get the core if not supplied - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_session_state()) +def get_session_state(): + return get_core().get_session_state() -def get_config(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_config()) -def get_config_value(key, core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_config_value(key)) +def get_config(): + return get_core().get_config() + +def get_config_value(key): + return get_core().get_config_value(key) -def set_config(config, core=None): +def set_config(config): if config == {}: return - if core is None: - core = get_core() - core.set_config(config) + get_core().set_config(config) -def get_listen_port(core=None): - if core is None: - core = get_core() - return int(core.get_listen_port()) +def get_listen_port(): + return int(get_core().get_listen_port()) -def get_available_plugins(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_available_plugins()) +def get_available_plugins(): + return get_core().get_available_plugins() -def get_enabled_plugins(core=None): - if core is None: - core = get_core() - return deluge.common.pythonize(core.get_enabled_plugins()) +def get_enabled_plugins(): + return get_core().get_enabled_plugins() def open_url_in_browser(url): """Opens link in the desktop's default browser""" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 5d4ab0e06..6d0b0f6a7 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,7 +93,7 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - self.signal_receiver = Signals(self) + #self.signal_receiver = Signals(self) # Initalize the plugins self.plugins = PluginManager(self) @@ -112,6 +112,6 @@ class GtkUI: # Clean-up del self.mainwindow - del self.signal_receiver + #del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index d9c79f5ff..64d0f9379 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -74,8 +74,7 @@ class StatusBar: def update(self): # Set the max connections label - max_connections = functions.get_config_value("max_connections_global", - core=self.core) + max_connections = functions.get_config_value("max_connections_global") if max_connections < 0: max_connections = _("Unlimited") @@ -83,8 +82,7 @@ class StatusBar: self.core.get_num_connections(), max_connections)) # Set the download speed label - max_download_speed = functions.get_config_value("max_download_speed", - core=self.core) + max_download_speed = functions.get_config_value("max_download_speed") if max_download_speed < 0: max_download_speed = _("Unlimited") else: @@ -95,8 +93,7 @@ class StatusBar: max_download_speed)) # Set the upload speed label - max_upload_speed = functions.get_config_value("max_upload_speed", - core=self.core) + max_upload_speed = functions.get_config_value("max_upload_speed") if max_upload_speed < 0: max_upload_speed = _("Unlimited") else: diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 2f270e495..14767206d 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -86,14 +86,13 @@ class SystemTray: # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( self.config["tray_download_speed_list"], self.tray_setbwdown, - functions.get_config_value("max_download_speed", - core=self.core), _("KiB/s"), show_notset=True, - show_other=True) + functions.get_config_value("max_download_speed"), + _("KiB/s"), show_notset=True, show_other=True) # Create the Upload speed list sub-menu submenu_bwupset = self.build_menu_radio_list( self.config["tray_upload_speed_list"], self.tray_setbwup, - functions.get_config_value("max_upload_speed", core=self.core), + functions.get_config_value("max_upload_speed"), _("KiB/s"), show_notset=True, show_other=True) # Add the sub-menus to the tray menu @@ -282,7 +281,7 @@ class SystemTray: spin_title.set_text(_("%s Speed (KiB/s):" % string)) spin_speed = dialog_glade.get_widget("spin_speed") spin_speed.set_value( - functions.get_config_value(core_key, core=self.core)) + functions.get_config_value(core_key)) spin_speed.select_region(0, -1) response = speed_dialog.run() if response == 1: # OK Response @@ -295,7 +294,7 @@ class SystemTray: # Set the config in the core value = float(value) config_to_set = {core_key: value} - functions.set_config(config_to_set, core=self.core) + functions.set_config(config_to_set) # Update the tray speed limit list if value not in self.config[ui_key] and value >= 0: diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 40a97e0b9..03cc0c0bf 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -47,8 +47,6 @@ class TorrentDetails: self.window = window glade = self.window.main_glade - self.core = functions.get_core() - self.notebook = glade.get_widget("torrent_info") self.details_tab = glade.get_widget("torrentdetails_tab") @@ -95,9 +93,7 @@ class TorrentDetails: "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", "tracker_status", "save_path"] - status = functions.get_torrent_status(self.core, - selected, - status_keys) + status = functions.get_torrent_status(selected, status_keys) # Check to see if we got valid data from the core if status is None: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 657c45bce..dbf961862 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -104,7 +104,6 @@ class TorrentView(listview.ListView): listview.ListView.__init__(self, self.window.main_glade.get_widget("torrent_view")) log.debug("TorrentView Init..") - self.core = functions.get_core() # Register the columns menu with the listview so it gets updated # accordingly. @@ -166,7 +165,7 @@ class TorrentView(listview.ListView): # We need to get the core session state to know which torrents are in # the session so we can add them to our list. - session_state = functions.get_session_state(self.core) + session_state = functions.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) @@ -213,7 +212,7 @@ class TorrentView(listview.ListView): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - status = functions.get_torrent_status(self.core, torrent_id, + status = functions.get_torrent_status(torrent_id, status_keys) # Set values for each column in the row From 64a94ec197ed19a760df3cc0aa8cfbc9b602f2d8 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sun, 14 Oct 2007 07:06:19 +0000 Subject: [PATCH 02/13] Add SignalReceiver and use it in the client. Add SignalManager in the core for emitting signals to clients. --- deluge/core/core.py | 22 +++++++- deluge/core/signalmanager.py | 53 ++++++++++++++++++ deluge/ui/gtkui/gtkui.py | 5 +- deluge/ui/gtkui/signals.py | 20 ++++--- deluge/ui/signalreceiver.py | 105 +++++++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 deluge/core/signalmanager.py create mode 100644 deluge/ui/signalreceiver.py diff --git a/deluge/core/core.py b/deluge/core/core.py index c830ed496..6d266ad0c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -49,6 +49,7 @@ import deluge.common from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager +from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log DEFAULT_PREFS = { @@ -76,7 +77,10 @@ DEFAULT_PREFS = { "enabled_plugins": ["Queue"] } -class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): +class Core( + threading.Thread, + ThreadingMixIn, + SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self): log.debug("Core init..") threading.Thread.__init__(self) @@ -161,7 +165,10 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ # Start the AlertManager self.alerts = AlertManager(self.session) - + + # Start the SignalManager + self.signals = SignalManager() + # Start the TorrentManager self.torrents = TorrentManager(self.session, self.alerts) @@ -197,6 +204,11 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ # Make shutdown an async call gobject.idle_add(self._shutdown) + def export_register_client(self, uri): + """Registers a client with the signal manager so that signals are + sent to it.""" + self.signals.register_client(uri) + def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session This requires the torrents filename and a dump of it's content @@ -352,26 +364,32 @@ class Core(threading.Thread, ThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServ def torrent_added(self, torrent_id): """Emitted when a new torrent is added to the core""" log.debug("torrent_added signal emitted") + self.signals.emit("torrent_added", torrent_id) def torrent_removed(self, torrent_id): """Emitted when a torrent has been removed from the core""" log.debug("torrent_remove signal emitted") + self.signals.emit("torrent_removed", torrent_id) def torrent_paused(self, torrent_id): """Emitted when a torrent is paused""" log.debug("torrent_paused signal emitted") + self.signals.emit("torrent_paused", torrent_id) def torrent_resumed(self, torrent_id): """Emitted when a torrent is resumed""" log.debug("torrent_resumed signal emitted") + self.signals.emit("torrent_resumed", torrent_id) def torrent_all_paused(self): """Emitted when all torrents have been paused""" log.debug("torrent_all_paused signal emitted") + self.signals.emit("torrent_all_paused", torrent_id) def torrent_all_resumed(self): """Emitted when all torrents have been resumed""" log.debug("torrent_all_resumed signal emitted") + self.signals.emit("torrent_all_resumed", torrent_id) # Config set functions def _on_set_listen_ports(self, key, value): diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py new file mode 100644 index 000000000..ec3fdf318 --- /dev/null +++ b/deluge/core/signalmanager.py @@ -0,0 +1,53 @@ +# +# signalmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import xmlrpclib + +from deluge.log import LOG as log + +class SignalManager: + def __init__(self): + self.clients = [] + + def register_client(self, uri): + """Registers a client to emit signals to.""" + log.debug("Registering %s as a signal reciever..", uri) + self.clients.append(xmlrpclib.ServerProxy(uri)) + + def emit(self, signal, data): + for client in self.clients: + try: + client.emit_signal(signal, data) + except: + log.warning("Unable to emit signal to client %s", client) + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 6d0b0f6a7..f11c89198 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,8 +93,8 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - #self.signal_receiver = Signals(self) - + self.signal_receiver = Signals(self) + # Initalize the plugins self.plugins = PluginManager(self) @@ -102,6 +102,7 @@ class GtkUI: self.mainwindow.start() # Start the gtk main loop + gtk.gdk.threads_init() gtk.main() log.debug("gtkui shutting down..") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 1698af094..37b9f35e3 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -31,21 +31,23 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. -import deluge.ui.functions as functions +from deluge.ui.signalreceiver import SignalReceiver from deluge.log import LOG as log class Signals: def __init__(self, ui): self.ui = ui - self.core = functions.get_core() - self.core.connect_to_signal("torrent_added", self.torrent_added_signal) - self.core.connect_to_signal("torrent_removed", - self.torrent_removed_signal) - self.core.connect_to_signal("torrent_paused", self.torrent_paused) - self.core.connect_to_signal("torrent_resumed", self.torrent_resumed) - self.core.connect_to_signal("torrent_all_paused", + self.receiver = SignalReceiver(6667, "http://localhost:6666") + self.receiver.start() + self.receiver.connect_to_signal("torrent_added", + self.torrent_added_signal) + self.receiver.connect_to_signal("torrent_removed", + self.torrent_removed_signal) + self.receiver.connect_to_signal("torrent_paused", self.torrent_paused) + self.receiver.connect_to_signal("torrent_resumed", self.torrent_resumed) + self.receiver.connect_to_signal("torrent_all_paused", self.torrent_all_paused) - self.core.connect_to_signal("torrent_all_resumed", + self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) def torrent_added_signal(self, torrent_id): diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py new file mode 100644 index 000000000..d22809942 --- /dev/null +++ b/deluge/ui/signalreceiver.py @@ -0,0 +1,105 @@ +# +# signalreceiver.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +import sys +import SimpleXMLRPCServer +from SocketServer import ThreadingMixIn +import xmlrpclib +import threading + +from deluge.log import LOG as log + +class SignalReceiver( + threading.Thread, + ThreadingMixIn, + SimpleXMLRPCServer.SimpleXMLRPCServer): + + def __init__(self, port, core_uri): + log.debug("SignalReceiver init..") + threading.Thread.__init__(self) + + # Daemonize the thread so it exits when the main program does + self.setDaemon(True) + + # Setup the xmlrpc server + try: + SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( + self, ("localhost", port), logRequests=False, allow_none=True) + except: + log.info("SignalReceiver already running or port not available..") + sys.exit(0) + + self.signals = {} + + # Register the emit_signal function + self.register_function(self.emit_signal) + + # Register the signal receiver with the core + # FIXME: send actual URI not localhost + core = xmlrpclib.ServerProxy(core_uri) + core.register_client("http://localhost:" + str(port)) + + def run(self): + """This gets called when we start the thread""" + t = threading.Thread(target=self.serve_forever) + t.start() + + def emit_signal(self, signal, data): + """Exported method used by the core to emit a signal to the client""" + log.debug("Received signal %s with data %s from core..", signal, data) + try: + if data != None: + for callback in self.signals[signal]: + try: + callback(data) + except: + log.warning("Unable to call callback for signal %s", + signal) + else: + for callback in self.signals[signal]: + try: + callback() + except: + log.warning("Unable to call callback for signal %s", + signal) + except KeyError: + log.debug("There are no callbacks registered for this signal..") + + def connect_to_signal(self, signal, callback): + """Connect to a signal""" + try: + self.signals[signal].append(callback) + except KeyError: + self.signals[signal] = [callback] + + From 5b7b027f4888abaee4ab11e64b2c025eb1579392 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 01:56:47 +0000 Subject: [PATCH 03/13] Save the uploaded_total in torrent state. --- deluge/core/torrent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 9f2935360..6c1b9f903 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -62,7 +62,7 @@ class Torrent: """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, - self.save_path, self.total_uploaded) + self.save_path, self.total_uploaded + status.total_payload_upload) def get_eta(self): """Returns the ETA in seconds for this torrent""" From 287d96d1e71440097de532c1b78c047a62a6f127 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 03:49:45 +0000 Subject: [PATCH 04/13] Include SimpleXMLRPCServer from Python 2.5.1 --- deluge/SimpleXMLRPCServer.py | 595 +++++++++++++++++++++++++++++++++++ deluge/core/core.py | 2 +- deluge/ui/signalreceiver.py | 2 +- 3 files changed, 597 insertions(+), 2 deletions(-) create mode 100644 deluge/SimpleXMLRPCServer.py diff --git a/deluge/SimpleXMLRPCServer.py b/deluge/SimpleXMLRPCServer.py new file mode 100644 index 000000000..7a9f26faa --- /dev/null +++ b/deluge/SimpleXMLRPCServer.py @@ -0,0 +1,595 @@ +"""Simple XML-RPC Server. + +This module can be used to create simple XML-RPC servers +by creating a server and either installing functions, a +class instance, or by extending the SimpleXMLRPCServer +class. + +It can also be used to handle XML-RPC requests in a CGI +environment using CGIXMLRPCRequestHandler. + +A list of possible usage patterns follows: + +1. Install functions: + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_function(pow) +server.register_function(lambda x,y: x+y, 'add') +server.serve_forever() + +2. Install an instance: + +class MyFuncs: + def __init__(self): + # make all of the string functions available through + # string.func_name + import string + self.string = string + def _listMethods(self): + # implement this method so that system.listMethods + # knows to advertise the strings methods + return list_public_methods(self) + \ + ['string.' + method for method in list_public_methods(self.string)] + def pow(self, x, y): return pow(x, y) + def add(self, x, y) : return x + y + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(MyFuncs()) +server.serve_forever() + +3. Install an instance with custom dispatch method: + +class Math: + def _listMethods(self): + # this method must be present for system.listMethods + # to work + return ['add', 'pow'] + def _methodHelp(self, method): + # this method must be present for system.methodHelp + # to work + if method == 'add': + return "add(2,3) => 5" + elif method == 'pow': + return "pow(x, y[, z]) => number" + else: + # By convention, return empty + # string if no help is available + return "" + def _dispatch(self, method, params): + if method == 'pow': + return pow(*params) + elif method == 'add': + return params[0] + params[1] + else: + raise 'bad method' + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(Math()) +server.serve_forever() + +4. Subclass SimpleXMLRPCServer: + +class MathServer(SimpleXMLRPCServer): + def _dispatch(self, method, params): + try: + # We are forcing the 'export_' prefix on methods that are + # callable through XML-RPC to prevent potential security + # problems + func = getattr(self, 'export_' + method) + except AttributeError: + raise Exception('method "%s" is not supported' % method) + else: + return func(*params) + + def export_add(self, x, y): + return x + y + +server = MathServer(("localhost", 8000)) +server.serve_forever() + +5. CGI script: + +server = CGIXMLRPCRequestHandler() +server.register_function(pow) +server.handle_request() +""" + +# Written by Brian Quinlan (brian@sweetapp.com). +# Based on code written by Fredrik Lundh. + +import xmlrpclib +from xmlrpclib import Fault +import SocketServer +import BaseHTTPServer +import sys +import os +try: + import fcntl +except ImportError: + fcntl = None + +def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): + """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d + + Resolves a dotted attribute name to an object. Raises + an AttributeError if any attribute in the chain starts with a '_'. + + If the optional allow_dotted_names argument is false, dots are not + supported and this function operates similar to getattr(obj, attr). + """ + + if allow_dotted_names: + attrs = attr.split('.') + else: + attrs = [attr] + + for i in attrs: + if i.startswith('_'): + raise AttributeError( + 'attempt to access private attribute "%s"' % i + ) + else: + obj = getattr(obj,i) + return obj + +def list_public_methods(obj): + """Returns a list of attribute strings, found in the specified + object, which represent callable attributes""" + + return [member for member in dir(obj) + if not member.startswith('_') and + callable(getattr(obj, member))] + +def remove_duplicates(lst): + """remove_duplicates([2,2,2,1,3,3]) => [3,1,2] + + Returns a copy of a list without duplicates. Every list + item must be hashable and the order of the items in the + resulting list is not defined. + """ + u = {} + for x in lst: + u[x] = 1 + + return u.keys() + +class SimpleXMLRPCDispatcher: + """Mix-in class that dispatches XML-RPC requests. + + This class is used to register XML-RPC method handlers + and then to dispatch them. There should never be any + reason to instantiate this class directly. + """ + + def __init__(self, allow_none, encoding): + self.funcs = {} + self.instance = None + self.allow_none = allow_none + self.encoding = encoding + + def register_instance(self, instance, allow_dotted_names=False): + """Registers an instance to respond to XML-RPC requests. + + Only one instance can be installed at a time. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. Methods beginning with an '_' + are considered private and will not be called by + SimpleXMLRPCServer. + + If a registered function matches a XML-RPC request, then it + will be called instead of the registered instance. + + If the optional allow_dotted_names argument is true and the + instance does not have a _dispatch method, method names + containing dots are supported and resolved, as long as none of + the name segments start with an '_'. + + *** SECURITY WARNING: *** + + Enabling the allow_dotted_names options allows intruders + to access your module's global variables and may allow + intruders to execute arbitrary code on your machine. Only + use this option on a secure, closed network. + + """ + + self.instance = instance + self.allow_dotted_names = allow_dotted_names + + def register_function(self, function, name = None): + """Registers a function to respond to XML-RPC requests. + + The optional name argument can be used to set a Unicode name + for the function. + """ + + if name is None: + name = function.__name__ + self.funcs[name] = function + + def register_introspection_functions(self): + """Registers the XML-RPC introspection methods in the system + namespace. + + see http://xmlrpc.usefulinc.com/doc/reserved.html + """ + + self.funcs.update({'system.listMethods' : self.system_listMethods, + 'system.methodSignature' : self.system_methodSignature, + 'system.methodHelp' : self.system_methodHelp}) + + def register_multicall_functions(self): + """Registers the XML-RPC multicall method in the system + namespace. + + see http://www.xmlrpc.com/discuss/msgReader$1208""" + + self.funcs.update({'system.multicall' : self.system_multicall}) + + def _marshaled_dispatch(self, data, dispatch_method = None): + """Dispatches an XML-RPC method from marshalled (XML) data. + + XML-RPC methods are dispatched from the marshalled (XML) data + using the _dispatch method and the result is returned as + marshalled data. For backwards compatibility, a dispatch + function can be provided as an argument (see comment in + SimpleXMLRPCRequestHandler.do_POST) but overriding the + existing method through subclassing is the prefered means + of changing method dispatch behavior. + """ + + try: + params, method = xmlrpclib.loads(data) + + # generate response + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = xmlrpclib.dumps(response, methodresponse=1, + allow_none=self.allow_none, encoding=self.encoding) + except Fault, fault: + response = xmlrpclib.dumps(fault, allow_none=self.allow_none, + encoding=self.encoding) + except: + # report exception back to server + response = xmlrpclib.dumps( + xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)), + encoding=self.encoding, allow_none=self.allow_none, + ) + + return response + + def system_listMethods(self): + """system.listMethods() => ['add', 'subtract', 'multiple'] + + Returns a list of the methods supported by the server.""" + + methods = self.funcs.keys() + if self.instance is not None: + # Instance can implement _listMethod to return a list of + # methods + if hasattr(self.instance, '_listMethods'): + methods = remove_duplicates( + methods + self.instance._listMethods() + ) + # if the instance has a _dispatch method then we + # don't have enough information to provide a list + # of methods + elif not hasattr(self.instance, '_dispatch'): + methods = remove_duplicates( + methods + list_public_methods(self.instance) + ) + methods.sort() + return methods + + def system_methodSignature(self, method_name): + """system.methodSignature('add') => [double, int, int] + + Returns a list describing the signature of the method. In the + above example, the add method takes two integers as arguments + and returns a double result. + + This server does NOT support system.methodSignature.""" + + # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html + + return 'signatures not supported' + + def system_methodHelp(self, method_name): + """system.methodHelp('add') => "Adds two integers together" + + Returns a string containing documentation for the specified method.""" + + method = None + if self.funcs.has_key(method_name): + method = self.funcs[method_name] + elif self.instance is not None: + # Instance can implement _methodHelp to return help for a method + if hasattr(self.instance, '_methodHelp'): + return self.instance._methodHelp(method_name) + # if the instance has a _dispatch method then we + # don't have enough information to provide help + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name, + self.allow_dotted_names + ) + except AttributeError: + pass + + # Note that we aren't checking that the method actually + # be a callable object of some kind + if method is None: + return "" + else: + import pydoc + return pydoc.getdoc(method) + + def system_multicall(self, call_list): + """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ +[[4], ...] + + Allows the caller to package multiple XML-RPC calls into a single + request. + + See http://www.xmlrpc.com/discuss/msgReader$1208 + """ + + results = [] + for call in call_list: + method_name = call['methodName'] + params = call['params'] + + try: + # XXX A marshalling error in any response will fail the entire + # multicall. If someone cares they should fix this. + results.append([self._dispatch(method_name, params)]) + except Fault, fault: + results.append( + {'faultCode' : fault.faultCode, + 'faultString' : fault.faultString} + ) + except: + results.append( + {'faultCode' : 1, + 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)} + ) + return results + + def _dispatch(self, method, params): + """Dispatches the XML-RPC method. + + XML-RPC calls are forwarded to a registered function that + matches the called XML-RPC method name. If no such function + exists then the call is forwarded to the registered instance, + if available. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. + + Methods beginning with an '_' are considered private and will + not be called. + """ + + func = None + try: + # check to see if a matching function has been registered + func = self.funcs[method] + except KeyError: + if self.instance is not None: + # check for a _dispatch method + if hasattr(self.instance, '_dispatch'): + return self.instance._dispatch(method, params) + else: + # call instance method directly + try: + func = resolve_dotted_attribute( + self.instance, + method, + self.allow_dotted_names + ) + except AttributeError: + pass + + if func is not None: + return func(*params) + else: + raise Exception('method "%s" is not supported' % method) + +class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple XML-RPC request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + """ + + # Class attribute listing the accessible path components; + # paths not on this list will result in a 404 error. + rpc_paths = ('/', '/RPC2') + + def is_rpc_path_valid(self): + if self.rpc_paths: + return self.path in self.rpc_paths + else: + # If .rpc_paths is empty, just assume all paths are legal + return True + + def do_POST(self): + """Handles the HTTP POST request. + + Attempts to interpret all HTTP POST requests as XML-RPC calls, + which are forwarded to the server's _dispatch method for handling. + """ + + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + try: + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + L.append(self.rfile.read(chunk_size)) + size_remaining -= len(L[-1]) + data = ''.join(L) + + # In previous versions of SimpleXMLRPCServer, _dispatch + # could be overridden in this class, instead of in + # SimpleXMLRPCDispatcher. To maintain backwards compatibility, + # check to see if a subclass implements _dispatch and dispatch + # using that method if present. + response = self.server._marshaled_dispatch( + data, getattr(self, '_dispatch', None) + ) + except: # This should only happen if the module is buggy + # internal error, report as HTTP server error + self.send_response(500) + self.end_headers() + else: + # got a valid XML RPC response + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def report_404 (self): + # Report a 404 error + self.send_response(404) + response = 'No such page' + self.send_header("Content-type", "text/plain") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def log_request(self, code='-', size='-'): + """Selectively log an accepted request.""" + + if self.server.logRequests: + BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) + +class SimpleXMLRPCServer(SocketServer.TCPServer, + SimpleXMLRPCDispatcher): + """Simple XML-RPC server. + + Simple XML-RPC server that allows functions and a single instance + to be installed to handle requests. The default implementation + attempts to dispatch XML-RPC calls to the functions or instance + installed in the server. Override the _dispatch method inhereted + from SimpleXMLRPCDispatcher to change this behavior. + """ + + allow_reuse_address = True + + def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, + logRequests=True, allow_none=False, encoding=None): + self.logRequests = logRequests + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + SocketServer.TCPServer.__init__(self, addr, requestHandler) + + # [Bug #1222790] If possible, set close-on-exec flag; if a + # method spawns a subprocess, the subprocess shouldn't have + # the listening socket open. + if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + +class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): + """Simple handler for XML-RPC data passed through CGI.""" + + def __init__(self, allow_none=False, encoding=None): + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + + def handle_xmlrpc(self, request_text): + """Handle a single XML-RPC request""" + + response = self._marshaled_dispatch(request_text) + + print 'Content-Type: text/xml' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_get(self): + """Handle a single HTTP GET request. + + Default implementation indicates an error because + XML-RPC uses the POST method. + """ + + code = 400 + message, explain = \ + BaseHTTPServer.BaseHTTPRequestHandler.responses[code] + + response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ + { + 'code' : code, + 'message' : message, + 'explain' : explain + } + print 'Status: %d %s' % (code, message) + print 'Content-Type: text/html' + print 'Content-Length: %d' % len(response) + print + sys.stdout.write(response) + + def handle_request(self, request_text = None): + """Handle a single XML-RPC request passed through a CGI post method. + + If no XML data is given then it is read from stdin. The resulting + XML-RPC response is printed to stdout along with the correct HTTP + headers. + """ + + if request_text is None and \ + os.environ.get('REQUEST_METHOD', None) == 'GET': + self.handle_get() + else: + # POST data is normally available through stdin + if request_text is None: + request_text = sys.stdin.read() + + self.handle_xmlrpc(request_text) + +if __name__ == '__main__': + print 'Running XML-RPC server on port 8000' + server = SimpleXMLRPCServer(("localhost", 8000)) + server.register_function(pow) + server.register_function(lambda x,y: x+y, 'add') + server.serve_forever() diff --git a/deluge/core/core.py b/deluge/core/core.py index 6d266ad0c..d792dde3c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -37,7 +37,7 @@ import pkg_resources import sys import pickle -import SimpleXMLRPCServer +import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import xmlrpclib import gobject diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index d22809942..1990e6146 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import sys -import SimpleXMLRPCServer +import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn import xmlrpclib import threading From 1440e78caebe19f9926144c24e024ae0f9882120 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:01:46 +0000 Subject: [PATCH 05/13] Add xmlrpclib from Python 2.5.1 --- deluge/core/core.py | 2 +- deluge/ui/functions.py | 5 +- deluge/ui/signalreceiver.py | 2 +- deluge/xmlrpclib.py | 1488 +++++++++++++++++++++++++++++++++++ 4 files changed, 1491 insertions(+), 6 deletions(-) create mode 100644 deluge/xmlrpclib.py diff --git a/deluge/core/core.py b/deluge/core/core.py index d792dde3c..6c2340145 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -39,7 +39,7 @@ import pickle import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib +import deluge.xmlrpclib as xmlrpclib import gobject import threading diff --git a/deluge/ui/functions.py b/deluge/ui/functions.py index 7a0e614a8..2334d1b6d 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/functions.py @@ -34,10 +34,7 @@ import os.path import pickle -import xmlrpclib -#import dbus -#from dbus.mainloop.glib import DBusGMainLoop -#DBusGMainLoop(set_as_default=True) +import deluge.xmlrpclib as xmlrpclib import pygtk pygtk.require('2.0') diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 1990e6146..7816a8d5a 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -34,7 +34,7 @@ import sys import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib +import xmlrpclib as xmlrpclib import threading from deluge.log import LOG as log diff --git a/deluge/xmlrpclib.py b/deluge/xmlrpclib.py new file mode 100644 index 000000000..9305e1018 --- /dev/null +++ b/deluge/xmlrpclib.py @@ -0,0 +1,1488 @@ +# +# XML-RPC CLIENT LIBRARY +# $Id: xmlrpclib.py 41594 2005-12-04 19:11:17Z andrew.kuchling $ +# +# an XML-RPC client interface for Python. +# +# the marshalling and response parser code can also be used to +# implement XML-RPC servers. +# +# Notes: +# this version is designed to work with Python 2.1 or newer. +# +# History: +# 1999-01-14 fl Created +# 1999-01-15 fl Changed dateTime to use localtime +# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service +# 1999-01-19 fl Fixed array data element (from Skip Montanaro) +# 1999-01-21 fl Fixed dateTime constructor, etc. +# 1999-02-02 fl Added fault handling, handle empty sequences, etc. +# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro) +# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) +# 2000-11-28 fl Changed boolean to check the truth value of its argument +# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches +# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1) +# 2001-03-28 fl Make sure response tuple is a singleton +# 2001-03-29 fl Don't require empty params element (from Nicholas Riley) +# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2) +# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod) +# 2001-09-03 fl Allow Transport subclass to override getparser +# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) +# 2001-10-01 fl Remove containers from memo cache when done with them +# 2001-10-01 fl Use faster escape method (80% dumps speedup) +# 2001-10-02 fl More dumps microtuning +# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum) +# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow +# 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems) +# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix) +# 2002-03-17 fl Avoid buffered read when possible (from James Rucker) +# 2002-04-07 fl Added pythondoc comments +# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers +# 2002-05-15 fl Added error constants (from Andrew Kuchling) +# 2002-06-27 fl Merged with Python CVS version +# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby) +# 2003-01-22 sm Add support for the bool type +# 2003-02-27 gvr Remove apply calls +# 2003-04-24 sm Use cStringIO if available +# 2003-04-25 ak Add support for nil +# 2003-06-15 gn Add support for time.struct_time +# 2003-07-12 gp Correct marshalling of Faults +# 2003-10-31 mvl Add multicall support +# 2004-08-20 mvl Bump minimum supported Python version to 2.1 +# +# Copyright (c) 1999-2002 by Secret Labs AB. +# Copyright (c) 1999-2002 by Fredrik Lundh. +# +# info@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The XML-RPC client interface is +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +# +# things to look into some day: + +# TODO: sort out True/False/boolean issues for Python 2.3 + +""" +An XML-RPC client interface for Python. + +The marshalling and response parser code can also be used to +implement XML-RPC servers. + +Exported exceptions: + + Error Base class for client errors + ProtocolError Indicates an HTTP protocol error + ResponseError Indicates a broken response package + Fault Indicates an XML-RPC fault package + +Exported classes: + + ServerProxy Represents a logical connection to an XML-RPC server + + MultiCall Executor of boxcared xmlrpc requests + Boolean boolean wrapper to generate a "boolean" XML-RPC value + DateTime dateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate a "dateTime.iso8601" + XML-RPC value + Binary binary data wrapper + + SlowParser Slow but safe standard parser (based on xmllib) + Marshaller Generate an XML-RPC params chunk from a Python data structure + Unmarshaller Unmarshal an XML-RPC response from incoming XML event message + Transport Handles an HTTP transaction to an XML-RPC server + SafeTransport Handles an HTTPS transaction to an XML-RPC server + +Exported constants: + + True + False + +Exported functions: + + boolean Convert any Python value to an XML-RPC boolean + getparser Create instance of the fastest available parser & attach + to an unmarshalling object + dumps Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + loads Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). +""" + +import re, string, time, operator + +from types import * + +# -------------------------------------------------------------------- +# Internal stuff + +try: + unicode +except NameError: + unicode = None # unicode support not available + +try: + import datetime +except ImportError: + datetime = None + +try: + _bool_is_builtin = False.__class__.__name__ == "bool" +except NameError: + _bool_is_builtin = 0 + +def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): + # decode non-ascii string (if possible) + if unicode and encoding and is8bit(data): + data = unicode(data, encoding) + return data + +def escape(s, replace=string.replace): + s = replace(s, "&", "&") + s = replace(s, "<", "<") + return replace(s, ">", ">",) + +if unicode: + def _stringify(string): + # convert to 7-bit ascii if possible + try: + return string.encode("ascii") + except UnicodeError: + return string +else: + def _stringify(string): + return string + +__version__ = "1.0.1" + +# xmlrpc integer limits +MAXINT = 2L**31-1 +MININT = -2L**31 + +# -------------------------------------------------------------------- +# Error constants (from Dan Libby's specification at +# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) + +# Ranges of errors +PARSE_ERROR = -32700 +SERVER_ERROR = -32600 +APPLICATION_ERROR = -32500 +SYSTEM_ERROR = -32400 +TRANSPORT_ERROR = -32300 + +# Specific errors +NOT_WELLFORMED_ERROR = -32700 +UNSUPPORTED_ENCODING = -32701 +INVALID_ENCODING_CHAR = -32702 +INVALID_XMLRPC = -32600 +METHOD_NOT_FOUND = -32601 +INVALID_METHOD_PARAMS = -32602 +INTERNAL_ERROR = -32603 + +# -------------------------------------------------------------------- +# Exceptions + +## +# Base class for all kinds of client-side errors. + +class Error(Exception): + """Base class for client errors.""" + def __str__(self): + return repr(self) + +## +# Indicates an HTTP-level protocol error. This is raised by the HTTP +# transport layer, if the server returns an error code other than 200 +# (OK). +# +# @param url The target URL. +# @param errcode The HTTP error code. +# @param errmsg The HTTP error message. +# @param headers The HTTP header dictionary. + +class ProtocolError(Error): + """Indicates an HTTP protocol error.""" + def __init__(self, url, errcode, errmsg, headers): + Error.__init__(self) + self.url = url + self.errcode = errcode + self.errmsg = errmsg + self.headers = headers + def __repr__(self): + return ( + "" % + (self.url, self.errcode, self.errmsg) + ) + +## +# Indicates a broken XML-RPC response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response is +# malformed. + +class ResponseError(Error): + """Indicates a broken response package.""" + pass + +## +# Indicates an XML-RPC fault response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response contains +# a fault string. This exception can also used as a class, to +# generate a fault XML-RPC message. +# +# @param faultCode The XML-RPC fault code. +# @param faultString The XML-RPC fault string. + +class Fault(Error): + """Indicates an XML-RPC fault package.""" + def __init__(self, faultCode, faultString, **extra): + Error.__init__(self) + self.faultCode = faultCode + self.faultString = faultString + def __repr__(self): + return ( + "" % + (self.faultCode, repr(self.faultString)) + ) + +# -------------------------------------------------------------------- +# Special values + +## +# Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and +# xmlrpclib.False constants, or the xmlrpclib.boolean() function, to +# generate boolean XML-RPC values. +# +# @param value A boolean value. Any true value is interpreted as True, +# all other values are interpreted as False. + +if _bool_is_builtin: + boolean = Boolean = bool + # to avoid breaking code which references xmlrpclib.{True,False} + True, False = True, False +else: + class Boolean: + """Boolean-value wrapper. + + Use True or False to generate a "boolean" XML-RPC value. + """ + + def __init__(self, value = 0): + self.value = operator.truth(value) + + def encode(self, out): + out.write("%d\n" % self.value) + + def __cmp__(self, other): + if isinstance(other, Boolean): + other = other.value + return cmp(self.value, other) + + def __repr__(self): + if self.value: + return "" % id(self) + else: + return "" % id(self) + + def __int__(self): + return self.value + + def __nonzero__(self): + return self.value + + True, False = Boolean(1), Boolean(0) + + ## + # Map true or false value to XML-RPC boolean values. + # + # @def boolean(value) + # @param value A boolean value. Any true value is mapped to True, + # all other values are mapped to False. + # @return xmlrpclib.True or xmlrpclib.False. + # @see Boolean + # @see True + # @see False + + def boolean(value, _truefalse=(False, True)): + """Convert any Python value to XML-RPC 'boolean'.""" + return _truefalse[operator.truth(value)] + +## +# Wrapper for XML-RPC DateTime values. This converts a time value to +# the format used by XML-RPC. +#

+# The value can be given as a string in the format +# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by +# time.localtime()), or an integer value (as returned by time.time()). +# The wrapper uses time.localtime() to convert an integer to a time +# tuple. +# +# @param value The time, given as an ISO 8601 string, a time +# tuple, or a integer time value. + +class DateTime: + """DateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate 'dateTime.iso8601' XML-RPC + value. + """ + + def __init__(self, value=0): + if not isinstance(value, StringType): + if datetime and isinstance(value, datetime.datetime): + self.value = value.strftime("%Y%m%dT%H:%M:%S") + return + if datetime and isinstance(value, datetime.date): + self.value = value.strftime("%Y%m%dT%H:%M:%S") + return + if datetime and isinstance(value, datetime.time): + today = datetime.datetime.now().strftime("%Y%m%d") + self.value = value.strftime(today+"T%H:%M:%S") + return + if not isinstance(value, (TupleType, time.struct_time)): + if value == 0: + value = time.time() + value = time.localtime(value) + value = time.strftime("%Y%m%dT%H:%M:%S", value) + self.value = value + + def __cmp__(self, other): + if isinstance(other, DateTime): + other = other.value + return cmp(self.value, other) + + ## + # Get date/time value. + # + # @return Date/time value, as an ISO 8601 string. + + def __str__(self): + return self.value + + def __repr__(self): + return "" % (repr(self.value), id(self)) + + def decode(self, data): + data = str(data) + self.value = string.strip(data) + + def encode(self, out): + out.write("") + out.write(self.value) + out.write("\n") + +def _datetime(data): + # decode xml element contents into a DateTime structure. + value = DateTime() + value.decode(data) + return value + +def _datetime_type(data): + t = time.strptime(data, "%Y%m%dT%H:%M:%S") + return datetime.datetime(*tuple(t)[:6]) + +## +# Wrapper for binary data. This can be used to transport any kind +# of binary data over XML-RPC, using BASE64 encoding. +# +# @param data An 8-bit string containing arbitrary data. + +import base64 +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +class Binary: + """Wrapper for binary data.""" + + def __init__(self, data=None): + self.data = data + + ## + # Get buffer contents. + # + # @return Buffer contents, as an 8-bit string. + + def __str__(self): + return self.data or "" + + def __cmp__(self, other): + if isinstance(other, Binary): + other = other.data + return cmp(self.data, other) + + def decode(self, data): + self.data = base64.decodestring(data) + + def encode(self, out): + out.write("\n") + base64.encode(StringIO.StringIO(self.data), out) + out.write("\n") + +def _binary(data): + # decode xml element contents into a Binary structure + value = Binary() + value.decode(data) + return value + +WRAPPERS = (DateTime, Binary) +if not _bool_is_builtin: + WRAPPERS = WRAPPERS + (Boolean,) + +# -------------------------------------------------------------------- +# XML parsers + +try: + # optional xmlrpclib accelerator + import _xmlrpclib + FastParser = _xmlrpclib.Parser + FastUnmarshaller = _xmlrpclib.Unmarshaller +except (AttributeError, ImportError): + FastParser = FastUnmarshaller = None + +try: + import _xmlrpclib + FastMarshaller = _xmlrpclib.Marshaller +except (AttributeError, ImportError): + FastMarshaller = None + +# +# the SGMLOP parser is about 15x faster than Python's builtin +# XML parser. SGMLOP sources can be downloaded from: +# +# http://www.pythonware.com/products/xml/sgmlop.htm +# + +try: + import sgmlop + if not hasattr(sgmlop, "XMLParser"): + raise ImportError +except ImportError: + SgmlopParser = None # sgmlop accelerator not available +else: + class SgmlopParser: + def __init__(self, target): + + # setup callbacks + self.finish_starttag = target.start + self.finish_endtag = target.end + self.handle_data = target.data + self.handle_xml = target.xml + + # activate parser + self.parser = sgmlop.XMLParser() + self.parser.register(self) + self.feed = self.parser.feed + self.entity = { + "amp": "&", "gt": ">", "lt": "<", + "apos": "'", "quot": '"' + } + + def close(self): + try: + self.parser.close() + finally: + self.parser = self.feed = None # nuke circular reference + + def handle_proc(self, tag, attr): + m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) + if m: + self.handle_xml(m.group(1), 1) + + def handle_entityref(self, entity): + # entity + try: + self.handle_data(self.entity[entity]) + except KeyError: + self.handle_data("&%s;" % entity) + +try: + from xml.parsers import expat + if not hasattr(expat, "ParserCreate"): + raise ImportError +except ImportError: + ExpatParser = None # expat not available +else: + class ExpatParser: + # fast expat parser for Python 2.0 and later. this is about + # 50% slower than sgmlop, on roundtrip testing + def __init__(self, target): + self._parser = parser = expat.ParserCreate(None, None) + self._target = target + parser.StartElementHandler = target.start + parser.EndElementHandler = target.end + parser.CharacterDataHandler = target.data + encoding = None + if not parser.returns_unicode: + encoding = "utf-8" + target.xml(encoding, None) + + def feed(self, data): + self._parser.Parse(data, 0) + + def close(self): + self._parser.Parse("", 1) # end of data + del self._target, self._parser # get rid of circular references + +class SlowParser: + """Default XML parser (based on xmllib.XMLParser).""" + # this is about 10 times slower than sgmlop, on roundtrip + # testing. + def __init__(self, target): + import xmllib # lazy subclassing (!) + if xmllib.XMLParser not in SlowParser.__bases__: + SlowParser.__bases__ = (xmllib.XMLParser,) + self.handle_xml = target.xml + self.unknown_starttag = target.start + self.handle_data = target.data + self.handle_cdata = target.data + self.unknown_endtag = target.end + try: + xmllib.XMLParser.__init__(self, accept_utf8=1) + except TypeError: + xmllib.XMLParser.__init__(self) # pre-2.0 + +# -------------------------------------------------------------------- +# XML-RPC marshalling and unmarshalling code + +## +# XML-RPC marshaller. +# +# @param encoding Default encoding for 8-bit strings. The default +# value is None (interpreted as UTF-8). +# @see dumps + +class Marshaller: + """Generate an XML-RPC params chunk from a Python data structure. + + Create a Marshaller instance for each set of parameters, and use + the "dumps" method to convert your data (represented as a tuple) + to an XML-RPC params chunk. To write a fault response, pass a + Fault instance instead. You may prefer to use the "dumps" module + function for this purpose. + """ + + # by the way, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, encoding=None, allow_none=0): + self.memo = {} + self.data = None + self.encoding = encoding + self.allow_none = allow_none + + dispatch = {} + + def dumps(self, values): + out = [] + write = out.append + dump = self.__dump + if isinstance(values, Fault): + # fault instance + write("\n") + dump({'faultCode': values.faultCode, + 'faultString': values.faultString}, + write) + write("\n") + else: + # parameter block + # FIXME: the xml-rpc specification allows us to leave out + # the entire block if there are no parameters. + # however, changing this may break older code (including + # old versions of xmlrpclib.py), so this is better left as + # is for now. See @XMLRPC3 for more information. /F + write("\n") + for v in values: + write("\n") + dump(v, write) + write("\n") + write("\n") + result = string.join(out, "") + return result + + def __dump(self, value, write): + try: + f = self.dispatch[type(value)] + except KeyError: + raise TypeError, "cannot marshal %s objects" % type(value) + else: + f(self, value, write) + + def dump_nil (self, value, write): + if not self.allow_none: + raise TypeError, "cannot marshal None unless allow_none is enabled" + write("") + dispatch[NoneType] = dump_nil + + def dump_int(self, value, write): + # in case ints are > 32 bits + if value > MAXINT or value < MININT: + raise OverflowError, "int exceeds XML-RPC limits" + write("") + write(str(value)) + write("\n") + dispatch[IntType] = dump_int + + if _bool_is_builtin: + def dump_bool(self, value, write): + write("") + write(value and "1" or "0") + write("\n") + dispatch[bool] = dump_bool + + def dump_long(self, value, write): + if value > MAXINT or value < MININT: + raise OverflowError, "long int exceeds XML-RPC limits" + write("") + write(str(int(value))) + write("\n") + dispatch[LongType] = dump_long + + def dump_double(self, value, write): + write("") + write(repr(value)) + write("\n") + dispatch[FloatType] = dump_double + + def dump_string(self, value, write, escape=escape): + write("") + write(escape(value)) + write("\n") + dispatch[StringType] = dump_string + + if unicode: + def dump_unicode(self, value, write, escape=escape): + value = value.encode(self.encoding) + write("") + write(escape(value)) + write("\n") + dispatch[UnicodeType] = dump_unicode + + def dump_array(self, value, write): + i = id(value) + if self.memo.has_key(i): + raise TypeError, "cannot marshal recursive sequences" + self.memo[i] = None + dump = self.__dump + write("\n") + for v in value: + dump(v, write) + write("\n") + del self.memo[i] + dispatch[TupleType] = dump_array + dispatch[ListType] = dump_array + + def dump_struct(self, value, write, escape=escape): + i = id(value) + if self.memo.has_key(i): + raise TypeError, "cannot marshal recursive dictionaries" + self.memo[i] = None + dump = self.__dump + write("\n") + for k, v in value.items(): + write("\n") + if type(k) is not StringType: + if unicode and type(k) is UnicodeType: + k = k.encode(self.encoding) + else: + raise TypeError, "dictionary key must be string" + write("%s\n" % escape(k)) + dump(v, write) + write("\n") + write("\n") + del self.memo[i] + dispatch[DictType] = dump_struct + + if datetime: + def dump_datetime(self, value, write): + write("") + write(value.strftime("%Y%m%dT%H:%M:%S")) + write("\n") + dispatch[datetime.datetime] = dump_datetime + + def dump_date(self, value, write): + write("") + write(value.strftime("%Y%m%dT00:00:00")) + write("\n") + dispatch[datetime.date] = dump_date + + def dump_time(self, value, write): + write("") + write(datetime.datetime.now().date().strftime("%Y%m%dT")) + write(value.strftime("%H:%M:%S")) + write("\n") + dispatch[datetime.time] = dump_time + + def dump_instance(self, value, write): + # check for special wrappers + if value.__class__ in WRAPPERS: + self.write = write + value.encode(self) + del self.write + else: + # store instance attributes as a struct (really?) + self.dump_struct(value.__dict__, write) + dispatch[InstanceType] = dump_instance + +## +# XML-RPC unmarshaller. +# +# @see loads + +class Unmarshaller: + """Unmarshal an XML-RPC response, based on incoming XML event + messages (start, data, end). Call close() to get the resulting + data structure. + + Note that this reader is fairly tolerant, and gladly accepts bogus + XML-RPC data without complaining (but not bogus XML). + """ + + # and again, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, use_datetime=0): + self._type = None + self._stack = [] + self._marks = [] + self._data = [] + self._methodname = None + self._encoding = "utf-8" + self.append = self._stack.append + self._use_datetime = use_datetime + if use_datetime and not datetime: + raise ValueError, "the datetime module is not available" + + def close(self): + # return response tuple and target method + if self._type is None or self._marks: + raise ResponseError() + if self._type == "fault": + raise Fault(**self._stack[0]) + return tuple(self._stack) + + def getmethodname(self): + return self._methodname + + # + # event handlers + + def xml(self, encoding, standalone): + self._encoding = encoding + # FIXME: assert standalone == 1 ??? + + def start(self, tag, attrs): + # prepare to handle this element + if tag == "array" or tag == "struct": + self._marks.append(len(self._stack)) + self._data = [] + self._value = (tag == "value") + + def data(self, text): + self._data.append(text) + + def end(self, tag, join=string.join): + # call the appropriate end tag handler + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, join(self._data, "")) + + # + # accelerator support + + def end_dispatch(self, tag, data): + # dispatch data + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, data) + + # + # element decoders + + dispatch = {} + + def end_nil (self, data): + self.append(None) + self._value = 0 + dispatch["nil"] = end_nil + + def end_boolean(self, data): + if data == "0": + self.append(False) + elif data == "1": + self.append(True) + else: + raise TypeError, "bad boolean value" + self._value = 0 + dispatch["boolean"] = end_boolean + + def end_int(self, data): + self.append(int(data)) + self._value = 0 + dispatch["i4"] = end_int + dispatch["int"] = end_int + + def end_double(self, data): + self.append(float(data)) + self._value = 0 + dispatch["double"] = end_double + + def end_string(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self.append(_stringify(data)) + self._value = 0 + dispatch["string"] = end_string + dispatch["name"] = end_string # struct keys are always strings + + def end_array(self, data): + mark = self._marks.pop() + # map arrays to Python lists + self._stack[mark:] = [self._stack[mark:]] + self._value = 0 + dispatch["array"] = end_array + + def end_struct(self, data): + mark = self._marks.pop() + # map structs to Python dictionaries + dict = {} + items = self._stack[mark:] + for i in range(0, len(items), 2): + dict[_stringify(items[i])] = items[i+1] + self._stack[mark:] = [dict] + self._value = 0 + dispatch["struct"] = end_struct + + def end_base64(self, data): + value = Binary() + value.decode(data) + self.append(value) + self._value = 0 + dispatch["base64"] = end_base64 + + def end_dateTime(self, data): + value = DateTime() + value.decode(data) + if self._use_datetime: + value = _datetime_type(data) + self.append(value) + dispatch["dateTime.iso8601"] = end_dateTime + + def end_value(self, data): + # if we stumble upon a value element with no internal + # elements, treat it as a string element + if self._value: + self.end_string(data) + dispatch["value"] = end_value + + def end_params(self, data): + self._type = "params" + dispatch["params"] = end_params + + def end_fault(self, data): + self._type = "fault" + dispatch["fault"] = end_fault + + def end_methodName(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self._methodname = data + self._type = "methodName" # no params + dispatch["methodName"] = end_methodName + +## Multicall support +# + +class _MultiCallMethod: + # some lesser magic to store calls made to a MultiCall object + # for batch execution + def __init__(self, call_list, name): + self.__call_list = call_list + self.__name = name + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + self.__call_list.append((self.__name, args)) + +class MultiCallIterator: + """Iterates over the results of a multicall. Exceptions are + thrown in response to xmlrpc faults.""" + + def __init__(self, results): + self.results = results + + def __getitem__(self, i): + item = self.results[i] + if type(item) == type({}): + raise Fault(item['faultCode'], item['faultString']) + elif type(item) == type([]): + return item[0] + else: + raise ValueError,\ + "unexpected type in multicall result" + +class MultiCall: + """server -> a object used to boxcar method calls + + server should be a ServerProxy object. + + Methods can be added to the MultiCall using normal + method call syntax e.g.: + + multicall = MultiCall(server_proxy) + multicall.add(2,3) + multicall.get_address("Guido") + + To execute the multicall, call the MultiCall object e.g.: + + add_result, address = multicall() + """ + + def __init__(self, server): + self.__server = server + self.__call_list = [] + + def __repr__(self): + return "" % id(self) + + __str__ = __repr__ + + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, name) + + def __call__(self): + marshalled_list = [] + for name, args in self.__call_list: + marshalled_list.append({'methodName' : name, 'params' : args}) + + return MultiCallIterator(self.__server.system.multicall(marshalled_list)) + +# -------------------------------------------------------------------- +# convenience functions + +## +# Create a parser object, and connect it to an unmarshalling instance. +# This function picks the fastest available XML parser. +# +# return A (parser, unmarshaller) tuple. + +def getparser(use_datetime=0): + """getparser() -> parser, unmarshaller + + Create an instance of the fastest available parser, and attach it + to an unmarshalling object. Return both objects. + """ + if use_datetime and not datetime: + raise ValueError, "the datetime module is not available" + if FastParser and FastUnmarshaller: + if use_datetime: + mkdatetime = _datetime_type + else: + mkdatetime = _datetime + target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault) + parser = FastParser(target) + else: + target = Unmarshaller(use_datetime=use_datetime) + if FastParser: + parser = FastParser(target) + elif SgmlopParser: + parser = SgmlopParser(target) + elif ExpatParser: + parser = ExpatParser(target) + else: + parser = SlowParser(target) + return parser, target + +## +# Convert a Python tuple or a Fault instance to an XML-RPC packet. +# +# @def dumps(params, **options) +# @param params A tuple or Fault instance. +# @keyparam methodname If given, create a methodCall request for +# this method name. +# @keyparam methodresponse If given, create a methodResponse packet. +# If used with a tuple, the tuple must be a singleton (that is, +# it must contain exactly one element). +# @keyparam encoding The packet encoding. +# @return A string containing marshalled data. + +def dumps(params, methodname=None, methodresponse=None, encoding=None, + allow_none=0): + """data [,options] -> marshalled data + + Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + + In addition to the data object, the following options can be given + as keyword arguments: + + methodname: the method name for a methodCall packet + + methodresponse: true to create a methodResponse packet. + If this option is used with a tuple, the tuple must be + a singleton (i.e. it can contain only one element). + + encoding: the packet encoding (default is UTF-8) + + All 8-bit strings in the data structure are assumed to use the + packet encoding. Unicode strings are automatically converted, + where necessary. + """ + + assert isinstance(params, TupleType) or isinstance(params, Fault),\ + "argument must be tuple or Fault instance" + + if isinstance(params, Fault): + methodresponse = 1 + elif methodresponse and isinstance(params, TupleType): + assert len(params) == 1, "response tuple must be a singleton" + + if not encoding: + encoding = "utf-8" + + if FastMarshaller: + m = FastMarshaller(encoding) + else: + m = Marshaller(encoding, allow_none) + + data = m.dumps(params) + + if encoding != "utf-8": + xmlheader = "\n" % str(encoding) + else: + xmlheader = "\n" # utf-8 is default + + # standard XML-RPC wrappings + if methodname: + # a method call + if not isinstance(methodname, StringType): + methodname = methodname.encode(encoding) + data = ( + xmlheader, + "\n" + "", methodname, "\n", + data, + "\n" + ) + elif methodresponse: + # a method response, or a fault structure + data = ( + xmlheader, + "\n", + data, + "\n" + ) + else: + return data # return as is + return string.join(data, "") + +## +# Convert an XML-RPC packet to a Python object. If the XML-RPC packet +# represents a fault condition, this function raises a Fault exception. +# +# @param data An XML-RPC packet, given as an 8-bit string. +# @return A tuple containing the unpacked data, and the method name +# (None if not present). +# @see Fault + +def loads(data, use_datetime=0): + """data -> unmarshalled data, method name + + Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). + + If the XML-RPC packet represents a fault condition, this function + raises a Fault exception. + """ + p, u = getparser(use_datetime=use_datetime) + p.feed(data) + p.close() + return u.close(), u.getmethodname() + + +# -------------------------------------------------------------------- +# request dispatcher + +class _Method: + # some magic to bind an XML-RPC method to an RPC server. + # supports "nested" methods (e.g. examples.getStateName) + def __init__(self, send, name): + self.__send = send + self.__name = name + def __getattr__(self, name): + return _Method(self.__send, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + return self.__send(self.__name, args) + +## +# Standard transport class for XML-RPC over HTTP. +#

+# You can create custom transports by subclassing this method, and +# overriding selected methods. + +class Transport: + """Handles an HTTP transaction to an XML-RPC server.""" + + # client identifier (may be overridden) + user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ + + def __init__(self, use_datetime=0): + self._use_datetime = use_datetime + + ## + # Send a complete request, and parse the response. + # + # @param host Target host. + # @param handler Target PRC handler. + # @param request_body XML-RPC request body. + # @param verbose Debugging flag. + # @return Parsed response. + + def request(self, host, handler, request_body, verbose=0): + # issue XML-RPC request + + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + self.send_request(h, handler, request_body) + self.send_host(h, host) + self.send_user_agent(h) + self.send_content(h, request_body) + + errcode, errmsg, headers = h.getreply() + + if errcode != 200: + raise ProtocolError( + host + handler, + errcode, errmsg, + headers + ) + + self.verbose = verbose + + try: + sock = h._conn.sock + except AttributeError: + sock = None + + return self._parse_response(h.getfile(), sock) + + ## + # Create parser. + # + # @return A 2-tuple containing a parser and a unmarshaller. + + def getparser(self): + # get parser and unmarshaller + return getparser(use_datetime=self._use_datetime) + + ## + # Get authorization info from host parameter + # Host may be a string, or a (host, x509-dict) tuple; if a string, + # it is checked for a "user:pw@host" format, and a "Basic + # Authentication" header is added if appropriate. + # + # @param host Host descriptor (URL or (URL, x509 info) tuple). + # @return A 3-tuple containing (actual host, extra headers, + # x509 info). The header and x509 fields may be None. + + def get_host_info(self, host): + + x509 = {} + if isinstance(host, TupleType): + host, x509 = host + + import urllib + auth, host = urllib.splituser(host) + + if auth: + import base64 + auth = base64.encodestring(urllib.unquote(auth)) + auth = string.join(string.split(auth), "") # get rid of whitespace + extra_headers = [ + ("Authorization", "Basic " + auth) + ] + else: + extra_headers = None + + return host, extra_headers, x509 + + ## + # Connect to server. + # + # @param host Target host. + # @return A connection handle. + + 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) + return httplib.HTTP(host) + + ## + # Send request header. + # + # @param connection Connection handle. + # @param handler Target RPC handler. + # @param request_body XML-RPC body. + + def send_request(self, connection, handler, request_body): + connection.putrequest("POST", handler) + + ## + # Send host name. + # + # @param connection Connection handle. + # @param host Host name. + + def send_host(self, connection, host): + host, extra_headers, x509 = self.get_host_info(host) + connection.putheader("Host", host) + if extra_headers: + if isinstance(extra_headers, DictType): + extra_headers = extra_headers.items() + for key, value in extra_headers: + connection.putheader(key, value) + + ## + # Send user-agent identifier. + # + # @param connection Connection handle. + + def send_user_agent(self, connection): + connection.putheader("User-Agent", self.user_agent) + + ## + # Send request body. + # + # @param connection Connection handle. + # @param request_body XML-RPC request body. + + def send_content(self, connection, request_body): + connection.putheader("Content-Type", "text/xml") + connection.putheader("Content-Length", str(len(request_body))) + connection.endheaders() + if request_body: + connection.send(request_body) + + ## + # Parse response. + # + # @param file Stream. + # @return Response tuple and target method. + + def parse_response(self, file): + # compatibility interface + return self._parse_response(file, None) + + ## + # Parse response (alternate interface). This is similar to the + # parse_response method, but also provides direct access to the + # underlying socket object (where available). + # + # @param file Stream. + # @param sock Socket handle (or None, if the socket object + # could not be accessed). + # @return Response tuple and target method. + + def _parse_response(self, file, sock): + # read response from input file/socket, and parse it + + p, u = self.getparser() + + while 1: + if sock: + response = sock.recv(1024) + else: + response = file.read(1024) + if not response: + break + if self.verbose: + print "body:", repr(response) + p.feed(response) + + file.close() + p.close() + + return u.close() + +## +# Standard transport class for XML-RPC over HTTPS. + +class SafeTransport(Transport): + """Handles an HTTPS transaction to an XML-RPC server.""" + + # FIXME: mostly untested + + def make_connection(self, host): + # create a HTTPS connection object from a host descriptor + # host may be a string, or a (host, x509-dict) tuple + import httplib + host, extra_headers, x509 = self.get_host_info(host) + try: + HTTPS = httplib.HTTPS + except AttributeError: + raise NotImplementedError( + "your version of httplib doesn't support HTTPS" + ) + else: + return HTTPS(host, None, **(x509 or {})) + +## +# Standard server proxy. This class establishes a virtual connection +# to an XML-RPC server. +#

+# This class is available as ServerProxy and Server. New code should +# use ServerProxy, to avoid confusion. +# +# @def ServerProxy(uri, **options) +# @param uri The connection point on the server. +# @keyparam transport A transport factory, compatible with the +# standard transport class. +# @keyparam encoding The default encoding used for 8-bit strings +# (default is UTF-8). +# @keyparam verbose Use a true value to enable debugging output. +# (printed to standard output). +# @see Transport + +class ServerProxy: + """uri [,options] -> a logical connection to an XML-RPC server + + uri is the connection point on the server, given as + scheme://host/target. + + The standard implementation always supports the "http" scheme. If + SSL socket support is available (Python 2.0), it also supports + "https". + + If the target part and the slash preceding it are both omitted, + "/RPC2" is assumed. + + The following options can be given as keyword arguments: + + transport: a transport factory + encoding: the request encoding (default is UTF-8) + + All 8-bit strings passed to the server proxy are assumed to use + the given encoding. + """ + + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=0, use_datetime=0): + # establish a "logical" server connection + + # get the url + import urllib + type, uri = urllib.splittype(uri) + if type not in ("http", "https"): + raise IOError, "unsupported XML-RPC protocol" + self.__host, self.__handler = urllib.splithost(uri) + if not self.__handler: + self.__handler = "/RPC2" + + if transport is None: + if type == "https": + transport = SafeTransport(use_datetime=use_datetime) + else: + transport = Transport(use_datetime=use_datetime) + self.__transport = transport + + self.__encoding = encoding + self.__verbose = verbose + self.__allow_none = allow_none + + def __request(self, methodname, params): + # call a method on the remote server + + request = dumps(params, methodname, encoding=self.__encoding, + allow_none=self.__allow_none) + + response = self.__transport.request( + self.__host, + self.__handler, + request, + verbose=self.__verbose + ) + + if len(response) == 1: + response = response[0] + + return response + + def __repr__(self): + return ( + "" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name) + + # note: to call a remote object with an non-standard name, use + # result getattr(server, "strange-python-name")(args) + +# compatibility + +Server = ServerProxy + +# -------------------------------------------------------------------- +# test code + +if __name__ == "__main__": + + # simple test program (from the XML-RPC specification) + + # server = ServerProxy("http://localhost:8000") # local server + server = ServerProxy("http://time.xmlrpc.com/RPC2") + + print server + + try: + print server.currentTime.getCurrentTime() + except Error, v: + print "ERROR", v + + multi = MultiCall(server) + multi.currentTime.getCurrentTime() + multi.currentTime.getCurrentTime() + try: + for response in multi(): + print response + except Error, v: + print "ERROR", v From 67c7f9a80e4bb34b15bdf10bbbf1fc8341687554 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:06:56 +0000 Subject: [PATCH 06/13] Fix preferences' function calls. --- deluge/ui/gtkui/preferences.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index ef9fee42b..9f65803a6 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -104,7 +104,7 @@ class Preferences: self.liststore.append([index, name]) def show(self): - self.core_config = functions.get_config(self.core) + self.core_config = functions.get_config() # Update the preferences dialog to reflect current config settings ## Downloads tab ## @@ -134,7 +134,7 @@ class Preferences: self.glade.get_widget("spin_port_max").set_value( self.core_config["listen_ports"][1]) self.glade.get_widget("active_port_label").set_text( - str(functions.get_listen_port(self.core))) + str(functions.get_listen_port())) self.glade.get_widget("chk_random_port").set_active( self.core_config["random_port"]) self.glade.get_widget("chk_dht").set_active( @@ -310,7 +310,7 @@ class Preferences: config_to_set[key] = new_core_config[key] # Set each changed config value in the core - functions.set_config(config_to_set, self.core) + functions.set_config(config_to_set) # Update the configuration self.core_config.update(config_to_set) @@ -387,7 +387,7 @@ class Preferences: def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") url = "http://deluge-torrent.org/test-port.php?port=%s" % \ - functions.get_listen_port(self.core) + functions.get_listen_port() functions.open_url_in_browser(url) def on_plugin_toggled(self, renderer, path): From d690e441949607aca0955d0088672ac96337706b Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:25:05 +0000 Subject: [PATCH 07/13] Fix adding torrents by URL. --- deluge/core/core.py | 8 ++++++-- deluge/ui/signalreceiver.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 6c2340145..afd938b2a 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -215,8 +215,12 @@ class Core( """ if save_path == "": save_path = None - - torrent_id = self.torrents.add(filename, filedump=filedump.data, + + # Make sure we are sending a string to add() + if not isinstance(filedump, str): + filedump = filedump.data + + torrent_id = self.torrents.add(filename, filedump=filedump, save_path=save_path) # Run the plugin hooks for 'post_torrent_add' diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 7816a8d5a..2df0435e2 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -34,7 +34,7 @@ import sys import deluge.SimpleXMLRPCServer as SimpleXMLRPCServer from SocketServer import ThreadingMixIn -import xmlrpclib as xmlrpclib +import deluge.xmlrpclib as xmlrpclib import threading from deluge.log import LOG as log From b3d15c71176478e85fc6761f34484e14f70da770 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 04:35:37 +0000 Subject: [PATCH 08/13] Daemonize receiver thread. --- deluge/core/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deluge/core/core.py b/deluge/core/core.py index afd938b2a..cdf219004 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -180,6 +180,7 @@ class Core( self._on_alert_torrent_paused) t = threading.Thread(target=self.serve_forever) + t.setDaemon(True) t.start() gobject.threads_init() From a16eb7625ca18e9cf01febe1db14071936c2b1d4 Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 15 Oct 2007 04:50:17 +0000 Subject: [PATCH 09/13] lt updates --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 6b85e7f8d..17a054f7f 100644 --- a/TODO +++ b/TODO @@ -11,3 +11,4 @@ to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Docstrings! +* Update libtorrent and bindings for sparse_mode allocation and remove_torrent options From b02b71e97849923f00edd681777b4254fca332ec Mon Sep 17 00:00:00 2001 From: Marcos Pinto Date: Mon, 15 Oct 2007 06:11:37 +0000 Subject: [PATCH 10/13] lt sync 1677 --- libtorrent/bindings/python/src/session.cpp | 17 +- libtorrent/include/libtorrent/alert_types.hpp | 10 + .../include/libtorrent/aux_/session_impl.hpp | 20 +- .../include/libtorrent/disk_io_thread.hpp | 1 + .../include/libtorrent/peer_connection.hpp | 2 +- libtorrent/include/libtorrent/policy.hpp | 2 + libtorrent/include/libtorrent/session.hpp | 16 +- .../include/libtorrent/session_settings.hpp | 2 +- libtorrent/include/libtorrent/storage.hpp | 69 +- libtorrent/include/libtorrent/torrent.hpp | 9 +- .../include/libtorrent/torrent_handle.hpp | 5 +- libtorrent/src/bt_peer_connection.cpp | 5 +- libtorrent/src/disk_io_thread.cpp | 8 + libtorrent/src/peer_connection.cpp | 65 +- libtorrent/src/piece_picker.cpp | 2 - libtorrent/src/policy.cpp | 10 +- libtorrent/src/session.cpp | 16 +- libtorrent/src/session_impl.cpp | 47 +- libtorrent/src/storage.cpp | 748 ++++++++++-------- libtorrent/src/torrent.cpp | 66 +- libtorrent/src/torrent_handle.cpp | 17 +- 21 files changed, 670 insertions(+), 467 deletions(-) diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 6d4855c10..7332da2ea 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -87,10 +87,10 @@ namespace torrent_handle add_torrent(session& s, torrent_info const& ti , boost::filesystem::path const& save, entry const& resume - , bool compact, bool paused) + , storage_mode_t storage_mode, bool paused) { allow_threading_guard guard; - return s.add_torrent(ti, save, resume, compact, paused, default_storage_constructor); + return s.add_torrent(ti, save, resume, storage_mode, paused, default_storage_constructor); } } // namespace unnamed @@ -154,6 +154,17 @@ void bind_session() #endif ; + enum_("storage_mode_t") + .value("storage_mode_allocate", storage_mode_allocate) + .value("storage_mode_compact", storage_mode_compact) + .value("storage_mode_sparse", storage_mode_sparse) + ; + + enum_("options_t") + .value("none", session::none) + .value("delete_files", session::delete_files) + ; + class_("session", session_doc, no_init) .def( init(arg("fingerprint")=fingerprint("LT",0,1,0,0), session_init_doc) @@ -235,8 +246,6 @@ void bind_session() .def("stop_natpmp", allow_threads(&session::stop_natpmp), session_stop_natpmp_doc) ; - def("supports_sparse_files", &supports_sparse_files); - register_ptr_to_python >(); } diff --git a/libtorrent/include/libtorrent/alert_types.hpp b/libtorrent/include/libtorrent/alert_types.hpp index a46eb7817..6f41758fa 100755 --- a/libtorrent/include/libtorrent/alert_types.hpp +++ b/libtorrent/include/libtorrent/alert_types.hpp @@ -252,6 +252,16 @@ namespace libtorrent { return std::auto_ptr(new storage_moved_alert(*this)); } }; + struct TORRENT_EXPORT torrent_deleted_alert: torrent_alert + { + torrent_deleted_alert(torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::warning, msg) + {} + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new torrent_deleted_alert(*this)); } + }; + struct TORRENT_EXPORT torrent_paused_alert: torrent_alert { torrent_paused_alert(torrent_handle const& h, std::string const& msg) diff --git a/libtorrent/include/libtorrent/aux_/session_impl.hpp b/libtorrent/include/libtorrent/aux_/session_impl.hpp index d069d73e3..bff8e3387 100644 --- a/libtorrent/include/libtorrent/aux_/session_impl.hpp +++ b/libtorrent/include/libtorrent/aux_/session_impl.hpp @@ -140,7 +140,7 @@ namespace libtorrent checker_impl(session_impl& s): m_ses(s), m_abort(false) {} void operator()(); piece_checker_data* find_torrent(const sha1_hash& info_hash); - void remove_torrent(sha1_hash const& info_hash); + void remove_torrent(sha1_hash const& info_hash, int options); #ifndef NDEBUG void check_invariant() const; @@ -254,7 +254,7 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata); @@ -265,12 +265,12 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata); - void remove_torrent(torrent_handle const& h); + void remove_torrent(torrent_handle const& h, int options); std::vector get_torrents(); @@ -371,12 +371,6 @@ namespace libtorrent void on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih); - // handles disk io requests asynchronously - // peers have pointers into the disk buffer - // pool, and must be destructed before this - // object. - disk_io_thread m_disk_thread; - // this pool is used to allocate and recycle send // buffers from. boost::pool<> m_send_buffers; @@ -395,6 +389,12 @@ namespace libtorrent // when they are destructed. file_pool m_files; + // handles disk io requests asynchronously + // peers have pointers into the disk buffer + // pool, and must be destructed before this + // object. + disk_io_thread m_disk_thread; + // this is a list of half-open tcp connections // (only outgoing connections) // this has to be one of the last diff --git a/libtorrent/include/libtorrent/disk_io_thread.hpp b/libtorrent/include/libtorrent/disk_io_thread.hpp index b893aaf60..bd6d5e1ba 100644 --- a/libtorrent/include/libtorrent/disk_io_thread.hpp +++ b/libtorrent/include/libtorrent/disk_io_thread.hpp @@ -64,6 +64,7 @@ namespace libtorrent , hash , move_storage , release_files + , delete_files }; action_t action; diff --git a/libtorrent/include/libtorrent/peer_connection.hpp b/libtorrent/include/libtorrent/peer_connection.hpp index 2ea3c7d34..e1581affe 100755 --- a/libtorrent/include/libtorrent/peer_connection.hpp +++ b/libtorrent/include/libtorrent/peer_connection.hpp @@ -176,7 +176,7 @@ namespace libtorrent void set_non_prioritized(bool b) { m_non_prioritized = b; } - void fast_reconnect(bool r) { m_fast_reconnect = r; } + void fast_reconnect(bool r); bool fast_reconnect() const { return m_fast_reconnect; } // this adds an announcement in the announcement queue diff --git a/libtorrent/include/libtorrent/policy.hpp b/libtorrent/include/libtorrent/policy.hpp index 551cb4f2c..c38bb426c 100755 --- a/libtorrent/include/libtorrent/policy.hpp +++ b/libtorrent/include/libtorrent/policy.hpp @@ -156,6 +156,8 @@ namespace libtorrent // this is true if the peer is a seed bool seed; + int fast_reconnects; + // true if this peer currently is unchoked // because of an optimistic unchoke. // when the optimistic unchoke is moved to diff --git a/libtorrent/include/libtorrent/session.hpp b/libtorrent/include/libtorrent/session.hpp index 3a9eb563b..5093e2336 100755 --- a/libtorrent/include/libtorrent/session.hpp +++ b/libtorrent/include/libtorrent/session.hpp @@ -115,7 +115,7 @@ namespace libtorrent : m_impl(impl) {} boost::shared_ptr m_impl; }; - + class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer { public: @@ -140,7 +140,7 @@ namespace libtorrent torrent_info const& ti , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor) TORRENT_DEPRECATED; @@ -148,7 +148,7 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor , void* userdata = 0); @@ -159,7 +159,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& resume_data = entry() - , bool compact_mode = true + , storage_mode_t storage_mode = storage_mode_sparse , bool paused = false , storage_constructor_type sc = default_storage_constructor , void* userdata = 0); @@ -219,7 +219,13 @@ namespace libtorrent // number of half open connections. int num_connections() const; - void remove_torrent(const torrent_handle& h); + enum options_t + { + none = 0, + delete_files = 1 + }; + + void remove_torrent(const torrent_handle& h, int options = none); void set_settings(session_settings const& s); session_settings const& settings(); diff --git a/libtorrent/include/libtorrent/session_settings.hpp b/libtorrent/include/libtorrent/session_settings.hpp index fbf2c8a03..a792e296a 100644 --- a/libtorrent/include/libtorrent/session_settings.hpp +++ b/libtorrent/include/libtorrent/session_settings.hpp @@ -99,7 +99,7 @@ namespace libtorrent , allow_multiple_connections_per_ip(false) , max_failcount(3) , min_reconnect_time(60) - , peer_connect_timeout(10) + , peer_connect_timeout(7) , ignore_limits_on_local_network(true) , connection_speed(20) , send_redundant_have(false) diff --git a/libtorrent/include/libtorrent/storage.hpp b/libtorrent/include/libtorrent/storage.hpp index 67d74153d..68a81c75b 100755 --- a/libtorrent/include/libtorrent/storage.hpp +++ b/libtorrent/include/libtorrent/storage.hpp @@ -71,6 +71,13 @@ namespace libtorrent struct file_pool; struct disk_io_job; + enum storage_mode_t + { + storage_mode_allocate = 0, + storage_mode_sparse, + storage_mode_compact + }; + #if defined(_WIN32) && defined(UNICODE) TORRENT_EXPORT std::wstring safe_convert(std::string const& s); @@ -144,6 +151,10 @@ namespace libtorrent // writing. This is called when a torrent has finished // downloading. virtual void release_files() = 0; + + // this will close all open files and delete them + virtual void delete_files() = 0; + virtual ~storage_interface() {} }; @@ -155,10 +166,6 @@ namespace libtorrent boost::intrusive_ptr ti , fs::path const& path, file_pool& fp); - // returns true if the filesystem the path relies on supports - // sparse files or automatic zero filling of files. - TORRENT_EXPORT bool supports_sparse_files(fs::path const& p); - struct disk_io_thread; class TORRENT_EXPORT piece_manager @@ -180,7 +187,8 @@ namespace libtorrent ~piece_manager(); bool check_fastresume(aux::piece_checker_data& d - , std::vector& pieces, int& num_pieces, bool compact_mode); + , std::vector& pieces, int& num_pieces, storage_mode_t storage_mode + , std::string& error_msg); std::pair check_files(std::vector& pieces , int& num_pieces, boost::recursive_mutex& mutex); @@ -191,8 +199,8 @@ namespace libtorrent bool verify_resume_data(entry& rd, std::string& error); bool is_allocating() const - { return m_state == state_allocating; } - + { return m_state == state_expand_pieces; } + void mark_failed(int index); unsigned long piece_crc( @@ -200,8 +208,9 @@ namespace libtorrent , int block_size , piece_picker::block_info const* bi); - int slot_for_piece(int piece_index) const; - + int slot_for(int piece) const; + int piece_for(int slot) const; + void async_read( peer_request const& r , boost::function const& handler @@ -221,6 +230,10 @@ namespace libtorrent boost::function const& handler = boost::function()); + void async_delete_files( + boost::function const& handler + = boost::function()); + void async_move_storage(fs::path const& p , boost::function const& handler); @@ -228,10 +241,11 @@ namespace libtorrent // slots to the piece that is stored (or // partially stored) there. -2 is the index // of unassigned pieces and -1 is unallocated - void export_piece_map(std::vector& pieces) const; + void export_piece_map(std::vector& pieces + , std::vector const& have) const; bool compact_allocation() const - { return m_compact_mode; } + { return m_storage_mode == storage_mode_compact; } #ifndef NDEBUG std::string name() const { return m_info->name(); } @@ -261,9 +275,11 @@ namespace libtorrent , int offset , int size); + void switch_to_full_mode(); sha1_hash hash_for_piece_impl(int piece); - void release_files_impl(); + void release_files_impl() { m_storage->release_files(); } + void delete_files_impl() { m_storage->delete_files(); } bool move_storage_impl(fs::path const& save_path); @@ -276,19 +292,7 @@ namespace libtorrent #endif boost::scoped_ptr m_storage; - // if this is true, pieces are always allocated at the - // lowest possible slot index. If it is false, pieces - // are always written to their final place immediately - bool m_compact_mode; - - // if this is true, pieces that haven't been downloaded - // will be filled with zeroes. Not filling with zeroes - // will not work in some cases (where a seek cannot pass - // the end of the file). - bool m_fill_mode; - - // a bitmask representing the pieces we have - std::vector m_have_piece; + storage_mode_t m_storage_mode; boost::intrusive_ptr m_info; @@ -329,10 +333,21 @@ namespace libtorrent state_create_files, // checking the files state_full_check, - // allocating files (in non-compact mode) - state_allocating + // move pieces to their final position + state_expand_pieces } m_state; int m_current_slot; + // used during check. If any piece is found + // that is not in its final position, this + // is set to true + bool m_out_of_place; + // used to move pieces while expanding + // the storage from compact allocation + // to full allocation + std::vector m_scratch_buffer; + std::vector m_scratch_buffer2; + // the piece that is in the scratch buffer + int m_scratch_piece; // this is saved in case we need to instantiate a new // storage (osed when remapping files) diff --git a/libtorrent/include/libtorrent/torrent.hpp b/libtorrent/include/libtorrent/torrent.hpp index 4bb68436c..60140f2c2 100755 --- a/libtorrent/include/libtorrent/torrent.hpp +++ b/libtorrent/include/libtorrent/torrent.hpp @@ -101,7 +101,7 @@ namespace libtorrent , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc , bool paused); @@ -116,7 +116,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t m_storage_mode , int block_size , storage_constructor_type sc , bool paused); @@ -177,6 +177,8 @@ namespace libtorrent void resume(); bool is_paused() const { return m_paused; } + void delete_files(); + // ============ start deprecation ============= void filter_piece(int index, bool filter); void filter_pieces(std::vector const& bitmask); @@ -550,6 +552,7 @@ namespace libtorrent private: + void on_files_deleted(int ret, disk_io_job const& j); void on_files_released(int ret, disk_io_job const& j); void on_torrent_paused(int ret, disk_io_job const& j); void on_storage_moved(int ret, disk_io_job const& j); @@ -751,7 +754,7 @@ namespace libtorrent fs::path m_save_path; // determines the storage state for this torrent. - const bool m_compact_mode; + storage_mode_t m_storage_mode; // defaults to 16 kiB, but can be set by the user // when creating the torrent diff --git a/libtorrent/include/libtorrent/torrent_handle.hpp b/libtorrent/include/libtorrent/torrent_handle.hpp index ec2d60ea5..8ae2f7f41 100755 --- a/libtorrent/include/libtorrent/torrent_handle.hpp +++ b/libtorrent/include/libtorrent/torrent_handle.hpp @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent_info.hpp" #include "libtorrent/time.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/storage.hpp" namespace libtorrent { @@ -106,7 +107,7 @@ namespace libtorrent , num_connections(0) , uploads_limit(0) , connections_limit(0) - , compact_mode(false) + , storage_mode(storage_mode_sparse) {} enum state_t @@ -216,7 +217,7 @@ namespace libtorrent // true if the torrent is saved in compact mode // false if it is saved in full allocation mode - bool compact_mode; + storage_mode_t storage_mode; }; struct TORRENT_EXPORT block_info diff --git a/libtorrent/src/bt_peer_connection.cpp b/libtorrent/src/bt_peer_connection.cpp index 21deec1d4..0559aff95 100755 --- a/libtorrent/src/bt_peer_connection.cpp +++ b/libtorrent/src/bt_peer_connection.cpp @@ -204,7 +204,6 @@ namespace libtorrent // if this fails, we need to reconnect // fast. - pi->connected = time_now() - seconds(m_ses.settings().min_reconnect_time); fast_reconnect(true); write_pe1_2_dhkey(); @@ -802,9 +801,9 @@ namespace libtorrent { boost::shared_ptr t = associated_torrent().lock(); TORRENT_ASSERT(t); - while (!request_queue().empty()) + while (!download_queue().empty()) { - piece_block const& b = request_queue().front(); + piece_block const& b = download_queue().front(); peer_request r; r.piece = b.piece_index; r.start = b.block_index * t->block_size(); diff --git a/libtorrent/src/disk_io_thread.cpp b/libtorrent/src/disk_io_thread.cpp index b6b2e20a7..22ee12179 100644 --- a/libtorrent/src/disk_io_thread.cpp +++ b/libtorrent/src/disk_io_thread.cpp @@ -125,6 +125,7 @@ namespace libtorrent , boost::function const& f) { TORRENT_ASSERT(!j.callback); + TORRENT_ASSERT(j.storage); boost::mutex::scoped_lock l(m_mutex); std::deque::reverse_iterator i = m_jobs.rbegin(); @@ -220,6 +221,7 @@ namespace libtorrent bool free_buffer = true; try { + TORRENT_ASSERT(j.storage); #ifdef TORRENT_DISK_STATS ptime start = time_now(); #endif @@ -288,6 +290,12 @@ namespace libtorrent #endif j.storage->release_files_impl(); break; + case disk_io_job::delete_files: +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " delete" << std::endl; +#endif + j.storage->delete_files_impl(); + break; } } catch (std::exception& e) diff --git a/libtorrent/src/peer_connection.cpp b/libtorrent/src/peer_connection.cpp index 9b975ab11..625b8eacd 100755 --- a/libtorrent/src/peer_connection.cpp +++ b/libtorrent/src/peer_connection.cpp @@ -394,6 +394,16 @@ namespace libtorrent #endif } + void peer_connection::fast_reconnect(bool r) + { + if (peer_info_struct() && peer_info_struct()->fast_reconnects > 1) return; + m_fast_reconnect = r; + peer_info_struct()->connected = time_now() + - seconds(m_ses.settings().min_reconnect_time + * m_ses.settings().max_failcount); + if (peer_info_struct()) ++peer_info_struct()->fast_reconnects; + } + void peer_connection::announce_piece(int index) { // dont announce during handshake @@ -643,27 +653,23 @@ namespace libtorrent m_peer_choked = true; t->get_policy().choked(*this); - if (!t->is_seed()) + if (peer_info_struct() == 0 || !peer_info_struct()->on_parole) { - piece_picker& p = t->picker(); - // remove all pieces from this peers download queue and - // remove the 'downloading' flag from piece_picker. - for (std::deque::iterator i = m_download_queue.begin(); - i != m_download_queue.end(); ++i) + // if the peer is not in parole mode, clear the queued + // up block requests + if (!t->is_seed()) { - p.abort_download(*i); - } - for (std::deque::const_iterator i = m_request_queue.begin() - , end(m_request_queue.end()); i != end; ++i) - { - // since this piece was skipped, clear it and allow it to - // be requested from other peers - p.abort_download(*i); + piece_picker& p = t->picker(); + for (std::deque::const_iterator i = m_request_queue.begin() + , end(m_request_queue.end()); i != end; ++i) + { + // since this piece was skipped, clear it and allow it to + // be requested from other peers + p.abort_download(*i); + } } + m_request_queue.clear(); } - - m_download_queue.clear(); - m_request_queue.clear(); } bool match_request(peer_request const& r, piece_block const& b, int block_size) @@ -707,23 +713,17 @@ namespace libtorrent { b = *i; m_download_queue.erase(i); - } - else - { - i = std::find_if(m_request_queue.begin(), m_request_queue.end() - , bind(match_request, boost::cref(r), _1, t->block_size())); - - if (i != m_request_queue.end()) + + // if the peer is in parole mode, keep the request + if (peer_info_struct() && peer_info_struct()->on_parole) { - b = *i; - m_request_queue.erase(i); + m_request_queue.push_front(b); + } + else if (!t->is_seed()) + { + piece_picker& p = t->picker(); + p.abort_download(b); } - } - - if (b.piece_index != -1 && !t->is_seed()) - { - piece_picker& p = t->picker(); - p.abort_download(b); } #ifdef TORRENT_VERBOSE_LOGGING else @@ -1932,7 +1932,6 @@ namespace libtorrent void peer_connection::timed_out() { - if (m_peer_info) ++m_peer_info->failcount; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_ses.m_logger) << "CONNECTION TIMED OUT: " << m_remote.address().to_string() << "\n"; diff --git a/libtorrent/src/piece_picker.cpp b/libtorrent/src/piece_picker.cpp index 652426806..ebfe07637 100755 --- a/libtorrent/src/piece_picker.cpp +++ b/libtorrent/src/piece_picker.cpp @@ -1732,7 +1732,6 @@ namespace libtorrent ++i->writing; info.state = block_info::state_writing; if (info.num_peers > 0) --info.num_peers; - TORRENT_ASSERT(info.num_peers >= 0); if (i->requested == 0) { @@ -1855,7 +1854,6 @@ namespace libtorrent block_info& info = i->info[block.block_index]; --info.num_peers; - TORRENT_ASSERT(info.num_peers >= 0); if (info.num_peers > 0) return; if (i->info[block.block_index].state == block_info::state_finished diff --git a/libtorrent/src/policy.cpp b/libtorrent/src/policy.cpp index dff860bd2..4de01d055 100755 --- a/libtorrent/src/policy.cpp +++ b/libtorrent/src/policy.cpp @@ -986,7 +986,8 @@ namespace libtorrent i->second.prev_amount_upload = 0; i->second.connection = &c; TORRENT_ASSERT(i->second.connection); - i->second.connected = time_now(); + if (!c.fast_reconnect()) + i->second.connected = time_now(); // m_last_optimistic_disconnect = time_now(); } @@ -1045,10 +1046,10 @@ namespace libtorrent // we don't have any info about this peer. // add a new entry - peer p(remote, peer::connectable, src); - i = m_peers.insert(std::make_pair(remote.address(), p)); + i = m_peers.insert(std::make_pair(remote.address() + , peer(remote, peer::connectable, src))); #ifndef TORRENT_DISABLE_ENCRYPTION - if (flags & 0x01) p.pe_support = true; + if (flags & 0x01) i->second.pe_support = true; #endif if (flags & 0x02) i->second.seed = true; @@ -1503,6 +1504,7 @@ namespace libtorrent , failcount(0) , hashfails(0) , seed(false) + , fast_reconnects(0) , optimistically_unchoked(false) , last_optimistically_unchoked(min_time()) , connected(min_time()) diff --git a/libtorrent/src/session.cpp b/libtorrent/src/session.cpp index 68cbd619f..5bdb6b07e 100755 --- a/libtorrent/src/session.cpp +++ b/libtorrent/src/session.cpp @@ -186,28 +186,28 @@ namespace libtorrent torrent_info const& ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc) { TORRENT_ASSERT(!ti.m_half_metadata); boost::intrusive_ptr tip(new torrent_info(ti)); return m_impl->add_torrent(tip, save_path, resume_data - , compact_mode, sc, paused, 0); + , storage_mode, sc, paused, 0); } torrent_handle session::add_torrent( boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc , void* userdata) { TORRENT_ASSERT(!ti->m_half_metadata); return m_impl->add_torrent(ti, save_path, resume_data - , compact_mode, sc, paused, userdata); + , storage_mode, sc, paused, userdata); } torrent_handle session::add_torrent( @@ -216,18 +216,18 @@ namespace libtorrent , char const* name , fs::path const& save_path , entry const& e - , bool compact_mode + , storage_mode_t storage_mode , bool paused , storage_constructor_type sc , void* userdata) { return m_impl->add_torrent(tracker_url, info_hash, name, save_path, e - , compact_mode, sc, paused, userdata); + , storage_mode, sc, paused, userdata); } - void session::remove_torrent(const torrent_handle& h) + void session::remove_torrent(const torrent_handle& h, int options) { - m_impl->remove_torrent(h); + m_impl->remove_torrent(h, options); } bool session::listen_on( diff --git a/libtorrent/src/session_impl.cpp b/libtorrent/src/session_impl.cpp index 83498ac14..63c039010 100755 --- a/libtorrent/src/session_impl.cpp +++ b/libtorrent/src/session_impl.cpp @@ -189,9 +189,11 @@ namespace detail t->parse_resume_data(t->resume_data, t->torrent_ptr->torrent_file() , error_msg); + // lock the session to add the new torrent + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) { - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); m_ses.m_alerts.post_alert(fastresume_rejected_alert( t->torrent_ptr->get_handle() , error_msg)); @@ -202,8 +204,6 @@ namespace detail #endif } - // lock the session to add the new torrent - session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); mutex::scoped_lock l2(m_mutex); if (m_torrents.empty() || m_torrents.front() != t) @@ -328,7 +328,7 @@ namespace detail boost::tie(finished, progress) = processing->torrent_ptr->check_files(); { - mutex::scoped_lock l(m_mutex); + mutex::scoped_lock l2(m_mutex); INVARIANT_CHECK; @@ -340,9 +340,9 @@ namespace detail m_processing.pop_front(); // make sure the lock order is correct - l.unlock(); + l2.unlock(); session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); - l.lock(); + l2.lock(); processing->torrent_ptr->abort(); processing.reset(); @@ -481,7 +481,7 @@ namespace detail return 0; } - void checker_impl::remove_torrent(sha1_hash const& info_hash) + void checker_impl::remove_torrent(sha1_hash const& info_hash, int options) { INVARIANT_CHECK; for (std::deque >::iterator i @@ -490,6 +490,8 @@ namespace detail if ((*i)->info_hash == info_hash) { TORRENT_ASSERT((*i)->processing == false); + if (options & session::delete_files) + (*i)->torrent_ptr->delete_files(); m_torrents.erase(i); return; } @@ -500,6 +502,8 @@ namespace detail if ((*i)->info_hash == info_hash) { TORRENT_ASSERT((*i)->processing == false); + if (options & session::delete_files) + (*i)->torrent_ptr->delete_files(); m_processing.erase(i); return; } @@ -565,8 +569,19 @@ namespace detail , m_checker_impl(*this) { #ifdef WIN32 - // windows XP has a limit of 10 simultaneous connections - m_half_open.limit(8); + // windows XP has a limit on the number of + // simultaneous half-open TCP connections + DWORD windows_version = ::GetVersion(); + if ((windows_version & 0xff) >= 6) + { + // on vista the limit is 5 (in home edition) + m_half_open.limit(4); + } + else + { + // on XP SP2 it's 10 + m_half_open.limit(8); + } #endif m_bandwidth_manager[peer_connection::download_channel] = &m_download_channel; @@ -1623,7 +1638,7 @@ namespace detail boost::intrusive_ptr ti , fs::path const& save_path , entry const& resume_data - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata) @@ -1655,7 +1670,7 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, ti, save_path - , m_listen_interface, compact_mode, 16 * 1024 + , m_listen_interface, storage_mode, 16 * 1024 , sc, paused)); torrent_ptr->start(); @@ -1701,7 +1716,7 @@ namespace detail , char const* name , fs::path const& save_path , entry const& - , bool compact_mode + , storage_mode_t storage_mode , storage_constructor_type sc , bool paused , void* userdata) @@ -1735,7 +1750,7 @@ namespace detail // the thread boost::shared_ptr torrent_ptr( new torrent(*this, m_checker_impl, tracker_url, info_hash, name - , save_path, m_listen_interface, compact_mode, 16 * 1024 + , save_path, m_listen_interface, storage_mode, 16 * 1024 , sc, paused)); torrent_ptr->start(); @@ -1754,7 +1769,7 @@ namespace detail return torrent_handle(this, &m_checker_impl, info_hash); } - void session_impl::remove_torrent(const torrent_handle& h) + void session_impl::remove_torrent(const torrent_handle& h, int options) { if (h.m_ses != this) return; TORRENT_ASSERT(h.m_chk == &m_checker_impl || h.m_chk == 0); @@ -1769,6 +1784,8 @@ namespace detail if (i != m_torrents.end()) { torrent& t = *i->second; + if (options & session::delete_files) + t.delete_files(); t.abort(); if ((!t.is_paused() || t.should_request()) @@ -1815,7 +1832,7 @@ namespace detail if (d != 0) { if (d->processing) d->abort = true; - else m_checker_impl.remove_torrent(h.m_info_hash); + else m_checker_impl.remove_torrent(h.m_info_hash, options); return; } } diff --git a/libtorrent/src/storage.cpp b/libtorrent/src/storage.cpp index 1cac99e44..6671e38e9 100755 --- a/libtorrent/src/storage.cpp +++ b/libtorrent/src/storage.cpp @@ -361,6 +361,7 @@ namespace libtorrent } void release_files(); + void delete_files(); void initialize(bool allocate_files); bool move_storage(fs::path save_path); size_type read(char* buf, int slot, int offset, int size); @@ -447,7 +448,7 @@ namespace libtorrent } // if the file is empty, just create it. But also make sure - // the directory exits. + // the directory exists. if (file_iter->size == 0) { file(m_save_path / file_iter->path, file::out); @@ -470,6 +471,38 @@ namespace libtorrent std::vector().swap(m_scratch_buffer); } + void storage::delete_files() + { + // make sure we don't have the files open + m_files.release(this); + std::vector().swap(m_scratch_buffer); + + // delete the files from disk + std::set directories; + typedef std::set::iterator iter_t; + for (torrent_info::file_iterator i = m_info->begin_files(true) + , end(m_info->end_files(true)); i != end; ++i) + { + std::string p = (m_save_path / i->path).string(); + fs::path bp = i->path.branch_path(); + std::pair ret = directories.insert(bp.string()); + while (ret.second && !bp.empty()) + { + bp = bp.branch_path(); + std::pair ret = directories.insert(bp.string()); + } + std::remove(p.c_str()); + } + + // remove the directories. Reverse order to delete + // subdirectories first + std::for_each(directories.rbegin(), directories.rend() + , bind((int(*)(char const*))&std::remove, bind(&std::string::c_str, _1))); + + std::string p = (m_save_path / m_info->name()).string(); + std::remove(p.c_str()); + } + void storage::write_resume_data(entry& rd) const { std::vector > file_sizes @@ -931,107 +964,6 @@ namespace libtorrent return new storage(ti, path, fp); } - bool supports_sparse_files(fs::path const& p) - { - TORRENT_ASSERT(p.is_complete()); -#if defined(_WIN32) - // assume windows API is available - DWORD max_component_len = 0; - DWORD volume_flags = 0; - std::string root_device = p.root_name() + "\\"; -#if defined(UNICODE) - std::wstring wph(safe_convert(root_device)); - bool ret = ::GetVolumeInformation(wph.c_str(), 0 - , 0, 0, &max_component_len, &volume_flags, 0, 0); -#else - bool ret = ::GetVolumeInformation(root_device.c_str(), 0 - , 0, 0, &max_component_len, &volume_flags, 0, 0); -#endif - - if (!ret) return false; - if (volume_flags & FILE_SUPPORTS_SPARSE_FILES) - return true; -#endif - -#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) - // find the last existing directory of the save path - fs::path query_path = p; - while (!query_path.empty() && !exists(query_path)) - query_path = query_path.branch_path(); -#endif - -#if defined(__APPLE__) - - struct statfs fsinfo; - int ret = statfs(query_path.native_directory_string().c_str(), &fsinfo); - if (ret != 0) return false; - - attrlist request; - request.bitmapcount = ATTR_BIT_MAP_COUNT; - request.reserved = 0; - request.commonattr = 0; - request.volattr = ATTR_VOL_CAPABILITIES; - request.dirattr = 0; - request.fileattr = 0; - request.forkattr = 0; - - struct vol_capabilities_attr_buf - { - unsigned long length; - vol_capabilities_attr_t info; - } vol_cap; - - ret = getattrlist(fsinfo.f_mntonname, &request, &vol_cap - , sizeof(vol_cap), 0); - if (ret != 0) return false; - - if (vol_cap.info.capabilities[VOL_CAPABILITIES_FORMAT] - & (VOL_CAP_FMT_SPARSE_FILES | VOL_CAP_FMT_ZERO_RUNS)) - { - return true; - } - - // workaround for bugs in Mac OS X where zero run is not reported - if (!strcmp(fsinfo.f_fstypename, "hfs") - || !strcmp(fsinfo.f_fstypename, "ufs")) - return true; - - return false; -#endif - -#if defined(__linux__) || defined(__FreeBSD__) - struct statfs buf; - int err = statfs(query_path.native_directory_string().c_str(), &buf); - if (err == 0) - { - switch (buf.f_type) - { - case 0x5346544e: // NTFS - case 0xEF51: // EXT2 OLD - case 0xEF53: // EXT2 and EXT3 - case 0x00011954: // UFS - case 0x52654973: // ReiserFS - case 0x52345362: // Reiser4 - case 0x58465342: // XFS - case 0x65735546: // NTFS-3G - case 0x19540119: // UFS2 - return true; - } - } -#ifndef NDEBUG - else - { - std::cerr << "statfs returned " << err << std::endl; - std::cerr << "errno: " << errno << std::endl; - std::cerr << "path: " << query_path.native_directory_string() << std::endl; - } -#endif -#endif - - // TODO: POSIX implementation - return false; - } - // -- piece_manager ----------------------------------------------------- piece_manager::piece_manager( @@ -1042,15 +974,16 @@ namespace libtorrent , disk_io_thread& io , storage_constructor_type sc) : m_storage(sc(ti, save_path, fp)) - , m_compact_mode(false) - , m_fill_mode(true) + , m_storage_mode(storage_mode_sparse) , m_info(ti) , m_save_path(complete(save_path)) + , m_current_slot(0) + , m_out_of_place(false) + , m_scratch_piece(-1) , m_storage_constructor(sc) , m_io_thread(io) , m_torrent(torrent) { - m_fill_mode = !supports_sparse_files(save_path); } piece_manager::~piece_manager() @@ -1081,6 +1014,15 @@ namespace libtorrent m_io_thread.add_job(j, handler); } + void piece_manager::async_delete_files( + boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::delete_files; + m_io_thread.add_job(j, handler); + } + void piece_manager::async_move_storage(fs::path const& p , boost::function const& handler) { @@ -1158,16 +1100,11 @@ namespace libtorrent m_piece_hasher.erase(i); } - int slot = m_piece_to_slot[piece]; + int slot = slot_for(piece); TORRENT_ASSERT(slot != has_no_slot); return m_storage->hash_for_slot(slot, ph, m_info->piece_size(piece)); } - void piece_manager::release_files_impl() - { - m_storage->release_files(); - } - bool piece_manager::move_storage_impl(fs::path const& save_path) { if (m_storage->move_storage(save_path)) @@ -1177,26 +1114,39 @@ namespace libtorrent } return false; } + void piece_manager::export_piece_map( - std::vector& p) const + std::vector& p, std::vector const& have) const { boost::recursive_mutex::scoped_lock lock(m_mutex); INVARIANT_CHECK; - p.clear(); - std::vector::const_reverse_iterator last; - for (last = m_slot_to_piece.rbegin(); - last != m_slot_to_piece.rend(); ++last) + if (m_storage_mode == storage_mode_compact) { - if (*last != unallocated) break; - } + p.clear(); + p.reserve(m_info->num_pieces()); + std::vector::const_reverse_iterator last; + for (last = m_slot_to_piece.rbegin(); + last != m_slot_to_piece.rend(); ++last) + { + if (*last != unallocated && have[*last]) break; + } - for (std::vector::const_iterator i = - m_slot_to_piece.begin(); - i != last.base(); ++i) + for (std::vector::const_iterator i = + m_slot_to_piece.begin(); + i != last.base(); ++i) + { + p.push_back(have[*i] ? *i : unassigned); + } + } + else { - p.push_back(*i); + p.reserve(m_info->num_pieces()); + for (int i = 0; i < m_info->num_pieces(); ++i) + { + p.push_back(have[i] ? i : unassigned); + } } } @@ -1206,11 +1156,10 @@ namespace libtorrent INVARIANT_CHECK; + if (m_storage_mode != storage_mode_compact) return; + TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); - int slot_index = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(slot_index >= 0); m_slot_to_piece[slot_index] = unassigned; @@ -1218,12 +1167,6 @@ namespace libtorrent m_free_slots.push_back(slot_index); } - int piece_manager::slot_for_piece(int piece_index) const - { - TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); - return m_piece_to_slot[piece_index]; - } - unsigned long piece_manager::piece_crc( int slot_index , int block_size @@ -1275,11 +1218,7 @@ namespace libtorrent TORRENT_ASSERT(buf); TORRENT_ASSERT(offset >= 0); TORRENT_ASSERT(size > 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0 - && m_piece_to_slot[piece_index] < (int)m_slot_to_piece.size()); - int slot = m_piece_to_slot[piece_index]; - TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); + int slot = slot_for(piece_index); return m_storage->read(buf, slot, offset, size); } @@ -1292,7 +1231,7 @@ namespace libtorrent TORRENT_ASSERT(buf); TORRENT_ASSERT(offset >= 0); TORRENT_ASSERT(size > 0); - TORRENT_ASSERT(piece_index >= 0 && piece_index < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(piece_index >= 0 && piece_index < m_info->num_pieces()); if (offset == 0) { @@ -1317,7 +1256,6 @@ namespace libtorrent } int slot = allocate_slot_for_piece(piece_index); - TORRENT_ASSERT(slot >= 0 && slot < (int)m_slot_to_piece.size()); m_storage->write(buf, slot, offset, size); } @@ -1426,7 +1364,8 @@ namespace libtorrent // that piece as unassigned, since this slot // is the correct place for the piece. m_slot_to_piece[other_slot] = unassigned; - m_free_slots.push_back(other_slot); + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(other_slot); } TORRENT_ASSERT(m_piece_to_slot[piece_index] != current_slot); TORRENT_ASSERT(m_piece_to_slot[piece_index] >= 0); @@ -1485,7 +1424,8 @@ namespace libtorrent bool piece_manager::check_fastresume( aux::piece_checker_data& data , std::vector& pieces - , int& num_pieces, bool compact_mode) + , int& num_pieces, storage_mode_t storage_mode + , std::string& error_msg) { boost::recursive_mutex::scoped_lock lock(m_mutex); @@ -1493,7 +1433,7 @@ namespace libtorrent TORRENT_ASSERT(m_info->piece_length() > 0); - m_compact_mode = compact_mode; + m_storage_mode = storage_mode; // This will corrupt the storage // use while debugging to find @@ -1503,9 +1443,13 @@ namespace libtorrent m_piece_to_slot.resize(m_info->num_pieces(), has_no_slot); m_slot_to_piece.resize(m_info->num_pieces(), unallocated); - m_free_slots.clear(); - m_unallocated_slots.clear(); + TORRENT_ASSERT(m_free_slots.empty()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + // assume no piece is out of place (i.e. in a slot + // other than the one it should be in) + bool out_of_place = false; + pieces.clear(); pieces.resize(m_info->num_pieces(), false); num_pieces = 0; @@ -1513,13 +1457,14 @@ namespace libtorrent // if we have fast-resume info // use it instead of doing the actual checking if (!data.piece_map.empty() - && data.piece_map.size() <= m_slot_to_piece.size()) + && int(data.piece_map.size()) <= m_info->num_pieces()) { for (int i = 0; i < (int)data.piece_map.size(); ++i) { m_slot_to_piece[i] = data.piece_map[i]; if (data.piece_map[i] >= 0) { + if (data.piece_map[i] != i) out_of_place = true; m_piece_to_slot[data.piece_map[i]] = i; int found_piece = data.piece_map[i]; @@ -1537,27 +1482,54 @@ namespace libtorrent } else if (data.piece_map[i] == unassigned) { - m_free_slots.push_back(i); + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(i); } else { TORRENT_ASSERT(data.piece_map[i] == unallocated); - m_unallocated_slots.push_back(i); + if (m_storage_mode == storage_mode_compact) + m_unallocated_slots.push_back(i); } } - m_unallocated_slots.reserve(int(pieces.size() - data.piece_map.size())); - for (int i = (int)data.piece_map.size(); i < (int)pieces.size(); ++i) + if (m_storage_mode == storage_mode_compact) { - m_unallocated_slots.push_back(i); + m_unallocated_slots.reserve(int(m_info->num_pieces() - data.piece_map.size())); + for (int i = (int)data.piece_map.size(); i < (int)m_info->num_pieces(); ++i) + { + m_unallocated_slots.push_back(i); + } + if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + } + else + { + if (!out_of_place) + { + // if no piece is out of place + // since we're in full allocation mode, we can + // forget the piece allocation tables + + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + m_state = state_create_files; + return false; + } + else + { + // in this case we're in full allocation mode, but + // we're resuming a compact allocated storage + m_state = state_expand_pieces; + m_current_slot = 0; + error_msg = "pieces needs to be reordered"; + return false; + } } - if (m_unallocated_slots.empty()) - m_state = state_create_files; - else if (m_compact_mode) - m_state = state_create_files; - else - m_state = state_allocating; + m_state = state_create_files; return false; } @@ -1572,18 +1544,13 @@ namespace libtorrent | | | v - | +------------+ - | | full_check | - | +------------+ - | | - | v - | +------------+ - |->| allocating | - | +------------+ - | | - | v - | +--------------+ - |->| create_files | + | +------------+ +---------------+ + | | full_check |-->| expand_pieses | + | +------------+ +---------------+ + | | | + | v | + | +--------------+ | + +->| create_files | <------+ +--------------+ | v @@ -1602,67 +1569,97 @@ namespace libtorrent std::pair piece_manager::check_files( std::vector& pieces, int& num_pieces, boost::recursive_mutex& mutex) { +#ifndef NDEBUG + boost::recursive_mutex::scoped_lock l_(mutex); TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); - - if (m_state == state_allocating) - { - if (m_compact_mode || m_unallocated_slots.empty()) - { - m_state = state_create_files; - return std::make_pair(false, 1.f); - } - - if (int(m_unallocated_slots.size()) == m_info->num_pieces() - && !m_fill_mode) - { - // if there is not a single file on disk, just - // create the files - m_state = state_create_files; - return std::make_pair(false, 1.f); - } - - // if we're not in compact mode, make sure the - // pieces are spread out and placed at their - // final position. - TORRENT_ASSERT(!m_unallocated_slots.empty()); - - if (!m_fill_mode) - { - // if we're not filling the allocation - // just make sure we move the current pieces - // into place, and just skip all other - // allocation - // allocate_slots returns true if it had to - // move any data - allocate_slots(m_unallocated_slots.size(), true); - } - else - { - allocate_slots(1); - } - - return std::make_pair(false, 1.f - (float)m_unallocated_slots.size() - / (float)m_slot_to_piece.size()); - } + l_.unlock(); +#endif if (m_state == state_create_files) { - m_storage->initialize(!m_fill_mode && !m_compact_mode); - - if (!m_unallocated_slots.empty() && !m_compact_mode) - { - TORRENT_ASSERT(!m_fill_mode); - std::vector().swap(m_unallocated_slots); - std::fill(m_slot_to_piece.begin(), m_slot_to_piece.end(), int(unassigned)); - m_free_slots.resize(m_info->num_pieces()); - for (int i = 0; i < m_info->num_pieces(); ++i) - m_free_slots[i] = i; - } - + m_storage->initialize(m_storage_mode == storage_mode_allocate); m_state = state_finished; return std::make_pair(true, 1.f); } + if (m_state == state_expand_pieces) + { + INVARIANT_CHECK; + + if (m_scratch_piece >= 0) + { + int piece = m_scratch_piece; + int other_piece = m_slot_to_piece[piece]; + m_scratch_piece = -1; + + if (other_piece >= 0) + { + if (m_scratch_buffer2.empty()) + m_scratch_buffer2.resize(m_info->piece_length()); + + m_storage->read(&m_scratch_buffer2[0], piece, 0, m_info->piece_size(other_piece)); + m_scratch_piece = other_piece; + m_piece_to_slot[other_piece] = unassigned; + } + + // the slot where this piece belongs is + // free. Just move the piece there. + m_storage->write(&m_scratch_buffer[0], piece, 0, m_info->piece_size(piece)); + m_piece_to_slot[piece] = piece; + m_slot_to_piece[piece] = piece; + + if (other_piece >= 0) + m_scratch_buffer.swap(m_scratch_buffer2); + + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + } + + while (m_current_slot < m_info->num_pieces() + && (m_slot_to_piece[m_current_slot] == m_current_slot + || m_slot_to_piece[m_current_slot] < 0)) + { + ++m_current_slot; + } + + if (m_current_slot == m_info->num_pieces()) + { + m_state = state_create_files; + std::vector().swap(m_scratch_buffer); + std::vector().swap(m_scratch_buffer2); + if (m_storage_mode != storage_mode_compact) + { + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + } + return std::make_pair(false, 1.f); + } + + int piece = m_slot_to_piece[m_current_slot]; + TORRENT_ASSERT(piece >= 0); + int other_piece = m_slot_to_piece[piece]; + if (other_piece >= 0) + { + // there is another piece in the slot + // where this one goes. Store it in the scratch + // buffer until next iteration. + if (m_scratch_buffer.empty()) + m_scratch_buffer.resize(m_info->piece_length()); + + m_storage->read(&m_scratch_buffer[0], piece, 0, m_info->piece_size(other_piece)); + m_scratch_piece = other_piece; + m_piece_to_slot[other_piece] = unassigned; + } + + // the slot where this piece belongs is + // free. Just move the piece there. + m_storage->move_slot(m_current_slot, piece); + m_piece_to_slot[piece] = piece; + m_slot_to_piece[m_current_slot] = unassigned; + m_slot_to_piece[piece] = piece; + + return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); + } + TORRENT_ASSERT(m_state == state_full_check); // ------------------------ @@ -1674,12 +1671,13 @@ namespace libtorrent // initialization for the full check if (m_hash_to_piece.empty()) { - m_current_slot = 0; for (int i = 0; i < m_info->num_pieces(); ++i) { m_hash_to_piece.insert(std::make_pair(m_info->hash_for_piece(i), i)); } + boost::recursive_mutex::scoped_lock l(mutex); std::fill(pieces.begin(), pieces.end(), false); + num_pieces = 0; } m_piece_data.resize(int(m_info->piece_length())); @@ -1694,6 +1692,10 @@ namespace libtorrent int piece_index = identify_data(m_piece_data, m_current_slot , pieces, num_pieces, m_hash_to_piece, mutex); + if (piece_index != m_current_slot + && piece_index >= 0) + m_out_of_place = true; + TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); TORRENT_ASSERT(piece_index == unassigned || piece_index >= 0); @@ -1745,8 +1747,11 @@ namespace libtorrent std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), other_slot); TORRENT_ASSERT(i != m_free_slots.end()); - m_free_slots.erase(i); - m_free_slots.push_back(m_current_slot); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(m_current_slot); + } } if (other_piece >= 0) @@ -1770,7 +1775,8 @@ namespace libtorrent m_slot_to_piece[other_slot] = piece_index; m_piece_to_slot[other_piece] = m_current_slot; - if (piece_index == unassigned) + if (piece_index == unassigned + && m_storage_mode == storage_mode_compact) m_free_slots.push_back(other_slot); if (piece_index >= 0) @@ -1845,8 +1851,11 @@ namespace libtorrent std::vector::iterator i = std::find(m_free_slots.begin(), m_free_slots.end(), slot1); TORRENT_ASSERT(i != m_free_slots.end()); - m_free_slots.erase(i); - m_free_slots.push_back(slot2); + if (m_storage_mode == storage_mode_compact) + { + m_free_slots.erase(i); + m_free_slots.push_back(slot2); + } } if (piece1 >= 0) @@ -1873,7 +1882,7 @@ namespace libtorrent // the slot was identified as piece 'piece_index' if (piece_index != unassigned) m_piece_to_slot[piece_index] = m_current_slot; - else + else if (m_storage_mode == storage_mode_compact) m_free_slots.push_back(m_current_slot); m_slot_to_piece[m_current_slot] = piece_index; @@ -1899,10 +1908,13 @@ namespace libtorrent (file_offset - current_offset + m_info->piece_length() - 1) / m_info->piece_length()); - for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) + if (m_storage_mode == storage_mode_compact) { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); - m_unallocated_slots.push_back(i); + for (int i = m_current_slot; i < m_current_slot + skip_blocks; ++i) + { + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + m_unallocated_slots.push_back(i); + } } // current slot will increase by one at the end of the for-loop too @@ -1910,15 +1922,46 @@ namespace libtorrent } ++m_current_slot; - if (m_current_slot >= m_info->num_pieces()) + if (m_current_slot >= m_info->num_pieces()) { TORRENT_ASSERT(m_current_slot == m_info->num_pieces()); // clear the memory we've been using std::vector().swap(m_piece_data); std::multimap().swap(m_hash_to_piece); - m_state = state_allocating; + + if (m_storage_mode != storage_mode_compact) + { + if (!m_out_of_place) + { + // if no piece is out of place + // since we're in full allocation mode, we can + // forget the piece allocation tables + + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + m_state = state_create_files; + return std::make_pair(false, 1.f); + } + else + { + // in this case we're in full allocation mode, but + // we're resuming a compact allocated storage + m_state = state_expand_pieces; + m_current_slot = 0; + return std::make_pair(false, 0.f); + } + } + else if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + m_state = state_create_files; + +#ifndef NDEBUG + boost::recursive_mutex::scoped_lock l(mutex); TORRENT_ASSERT(num_pieces == std::count(pieces.begin(), pieces.end(), true)); +#endif return std::make_pair(false, 1.f); } @@ -1927,10 +1970,26 @@ namespace libtorrent return std::make_pair(false, (float)m_current_slot / m_info->num_pieces()); } + void piece_manager::switch_to_full_mode() + { + TORRENT_ASSERT(m_storage_mode == storage_mode_compact); + TORRENT_ASSERT(m_unallocated_slots.empty()); + // we have allocated all slots, switch to + // full allocation mode in order to free + // some unnecessary memory. + m_storage_mode = storage_mode_sparse; + std::vector().swap(m_unallocated_slots); + std::vector().swap(m_free_slots); + std::vector().swap(m_piece_to_slot); + std::vector().swap(m_slot_to_piece); + } + int piece_manager::allocate_slot_for_piece(int piece_index) { boost::recursive_mutex::scoped_lock lock(m_mutex); + if (m_storage_mode != storage_mode_compact) return piece_index; + // INVARIANT_CHECK; TORRENT_ASSERT(piece_index >= 0); @@ -2030,26 +2089,27 @@ namespace libtorrent debug_log(); #endif } - TORRENT_ASSERT(slot_index >= 0); TORRENT_ASSERT(slot_index < (int)m_slot_to_piece.size()); + + if (m_unallocated_slots.empty()) + { + switch_to_full_mode(); + } + return slot_index; } bool piece_manager::allocate_slots(int num_slots, bool abort_on_disk) { - TORRENT_ASSERT(num_slots > 0); - boost::recursive_mutex::scoped_lock lock(m_mutex); + TORRENT_ASSERT(num_slots > 0); // INVARIANT_CHECK; TORRENT_ASSERT(!m_unallocated_slots.empty()); + TORRENT_ASSERT(m_storage_mode == storage_mode_compact); - const int stack_buffer_size = 16*1024; - char zeroes[stack_buffer_size]; - memset(zeroes, 0, stack_buffer_size); - bool written = false; for (int i = 0; i < num_slots && !m_unallocated_slots.empty(); ++i) @@ -2069,134 +2129,160 @@ namespace libtorrent m_piece_to_slot[pos] = pos; written = true; } - else if (m_fill_mode) - { - int piece_size = int(m_info->piece_size(pos)); - int offset = 0; - for (; piece_size > 0; piece_size -= stack_buffer_size - , offset += stack_buffer_size) - { - m_storage->write(zeroes, pos, offset - , (std::min)(piece_size, stack_buffer_size)); - } - written = true; - } m_unallocated_slots.erase(m_unallocated_slots.begin()); m_slot_to_piece[new_free_slot] = unassigned; m_free_slots.push_back(new_free_slot); - if (abort_on_disk && written) return true; + if (abort_on_disk && written) break; } TORRENT_ASSERT(m_free_slots.size() > 0); return written; } + int piece_manager::slot_for(int piece) const + { + if (m_storage_mode != storage_mode_compact) return piece; + TORRENT_ASSERT(piece < int(m_piece_to_slot.size())); + TORRENT_ASSERT(piece >= 0); + return m_piece_to_slot[piece]; + } + + int piece_manager::piece_for(int slot) const + { + if (m_storage_mode != storage_mode_compact) return slot; + TORRENT_ASSERT(slot < int(m_slot_to_piece.size())); + TORRENT_ASSERT(slot >= 0); + return m_slot_to_piece[slot]; + } + #ifndef NDEBUG void piece_manager::check_invariant() const { boost::recursive_mutex::scoped_lock lock(m_mutex); - if (m_piece_to_slot.empty()) return; - TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces()); - TORRENT_ASSERT((int)m_slot_to_piece.size() == m_info->num_pieces()); - - for (std::vector::const_iterator i = m_free_slots.begin(); - i != m_free_slots.end(); ++i) + if (m_unallocated_slots.empty() && m_state == state_finished) { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); - TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) - == m_free_slots.end()); + TORRENT_ASSERT(m_storage_mode != storage_mode_compact); } - - for (std::vector::const_iterator i = m_unallocated_slots.begin(); - i != m_unallocated_slots.end(); ++i) + + if (m_storage_mode != storage_mode_compact) { - TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); - TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) - == m_unallocated_slots.end()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + TORRENT_ASSERT(m_free_slots.empty()); } - - for (int i = 0; i < m_info->num_pieces(); ++i) + + if (m_storage_mode != storage_mode_compact + && m_state != state_expand_pieces + && m_state != state_full_check) { - // Check domain of piece_to_slot's elements - if (m_piece_to_slot[i] != has_no_slot) + TORRENT_ASSERT(m_piece_to_slot.empty()); + TORRENT_ASSERT(m_slot_to_piece.empty()); + } + else + { + if (m_piece_to_slot.empty()) return; + + TORRENT_ASSERT((int)m_piece_to_slot.size() == m_info->num_pieces()); + TORRENT_ASSERT((int)m_slot_to_piece.size() == m_info->num_pieces()); + + for (std::vector::const_iterator i = m_free_slots.begin(); + i != m_free_slots.end(); ++i) { - TORRENT_ASSERT(m_piece_to_slot[i] >= 0); - TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unassigned); + TORRENT_ASSERT(std::find(i+1, m_free_slots.end(), *i) + == m_free_slots.end()); } - // Check domain of slot_to_piece's elements - if (m_slot_to_piece[i] != unallocated - && m_slot_to_piece[i] != unassigned) + for (std::vector::const_iterator i = m_unallocated_slots.begin(); + i != m_unallocated_slots.end(); ++i) { - TORRENT_ASSERT(m_slot_to_piece[i] >= 0); - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(*i < (int)m_slot_to_piece.size()); + TORRENT_ASSERT(*i >= 0); + TORRENT_ASSERT(m_slot_to_piece[*i] == unallocated); + TORRENT_ASSERT(std::find(i+1, m_unallocated_slots.end(), *i) + == m_unallocated_slots.end()); } - // do more detailed checks on piece_to_slot - if (m_piece_to_slot[i] >= 0) + for (int i = 0; i < m_info->num_pieces(); ++i) { - TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); - if (m_piece_to_slot[i] != i) + // Check domain of piece_to_slot's elements + if (m_piece_to_slot[i] != has_no_slot) { - TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + TORRENT_ASSERT(m_piece_to_slot[i] >= 0); + TORRENT_ASSERT(m_piece_to_slot[i] < (int)m_slot_to_piece.size()); } - } - else - { - TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); - } - // do more detailed checks on slot_to_piece + // Check domain of slot_to_piece's elements + if (m_slot_to_piece[i] != unallocated + && m_slot_to_piece[i] != unassigned) + { + TORRENT_ASSERT(m_slot_to_piece[i] >= 0); + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + } - if (m_slot_to_piece[i] >= 0) - { - TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); - TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); + // do more detailed checks on piece_to_slot + if (m_piece_to_slot[i] >= 0) + { + TORRENT_ASSERT(m_slot_to_piece[m_piece_to_slot[i]] == i); + if (m_piece_to_slot[i] != i) + { + TORRENT_ASSERT(m_slot_to_piece[i] == unallocated); + } + } + else + { + TORRENT_ASSERT(m_piece_to_slot[i] == has_no_slot); + } + + // do more detailed checks on slot_to_piece + + if (m_slot_to_piece[i] >= 0) + { + TORRENT_ASSERT(m_slot_to_piece[i] < (int)m_piece_to_slot.size()); + TORRENT_ASSERT(m_piece_to_slot[m_slot_to_piece[i]] == i); #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) == m_unallocated_slots.end() - ); - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) == m_free_slots.end() - ); + TORRENT_ASSERT( + std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) == m_unallocated_slots.end() + ); + TORRENT_ASSERT( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) == m_free_slots.end() + ); #endif - } - else if (m_slot_to_piece[i] == unallocated) - { + } + else if (m_slot_to_piece[i] == unallocated) + { #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT(m_unallocated_slots.empty() - || (std::find( - m_unallocated_slots.begin() - , m_unallocated_slots.end() - , i) != m_unallocated_slots.end()) - ); + TORRENT_ASSERT(m_unallocated_slots.empty() + || (std::find( + m_unallocated_slots.begin() + , m_unallocated_slots.end() + , i) != m_unallocated_slots.end()) + ); #endif - } - else if (m_slot_to_piece[i] == unassigned) - { + } + else if (m_slot_to_piece[i] == unassigned) + { #ifdef TORRENT_STORAGE_DEBUG - TORRENT_ASSERT( - std::find( - m_free_slots.begin() - , m_free_slots.end() - , i) != m_free_slots.end() - ); + TORRENT_ASSERT( + std::find( + m_free_slots.begin() + , m_free_slots.end() + , i) != m_free_slots.end() + ); #endif - } - else - { - TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); + } + else + { + TORRENT_ASSERT(false && "m_slot_to_piece[i] is invalid"); + } } } } diff --git a/libtorrent/src/torrent.cpp b/libtorrent/src/torrent.cpp index a094fa749..840e488ba 100755 --- a/libtorrent/src/torrent.cpp +++ b/libtorrent/src/torrent.cpp @@ -154,7 +154,7 @@ namespace libtorrent , boost::intrusive_ptr tf , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t storage_mode , int block_size , storage_constructor_type sc , bool paused) @@ -195,7 +195,7 @@ namespace libtorrent , m_total_redundant_bytes(0) , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) - , m_compact_mode(compact_mode) + , m_storage_mode(storage_mode) , m_default_block_size(block_size) , m_connections_initialized(true) , m_settings(ses.settings()) @@ -215,7 +215,7 @@ namespace libtorrent , char const* name , fs::path const& save_path , tcp::endpoint const& net_interface - , bool compact_mode + , storage_mode_t storage_mode , int block_size , storage_constructor_type sc , bool paused) @@ -255,7 +255,7 @@ namespace libtorrent , m_total_redundant_bytes(0) , m_net_interface(net_interface.address(), 0) , m_save_path(complete(save_path)) - , m_compact_mode(compact_mode) + , m_storage_mode(storage_mode) , m_default_block_size(block_size) , m_connections_initialized(false) , m_settings(ses.settings()) @@ -1032,6 +1032,16 @@ namespace libtorrent m_announce_timer.cancel(); } + void torrent::on_files_deleted(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(torrent_deleted_alert(get_handle(), "files deleted")); + } + } + void torrent::on_files_released(int ret, disk_io_job const& j) { /* @@ -1668,8 +1678,6 @@ namespace libtorrent try { - TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); - // add the newly connected peer to this torrent's peer list TORRENT_ASSERT(m_connections.find(a) == m_connections.end()); m_connections.insert( @@ -1883,10 +1891,13 @@ namespace libtorrent std::make_pair(a, boost::get_pointer(c))); m_ses.m_connections.insert(std::make_pair(s, c)); + int timeout = settings().peer_connect_timeout; + if (peerinfo) timeout += 3 * peerinfo->failcount; + m_ses.m_half_open.enqueue( bind(&peer_connection::connect, c, _1) , bind(&peer_connection::timed_out, c) - , seconds(settings().peer_connect_timeout)); + , seconds(timeout)); } catch (std::exception& e) { @@ -2215,10 +2226,22 @@ namespace libtorrent bool done = true; try { + std::string error_msg; TORRENT_ASSERT(m_storage); TORRENT_ASSERT(m_owning_storage.get()); done = m_storage->check_fastresume(data, m_have_pieces, m_num_pieces - , m_compact_mode); + , m_storage_mode, error_msg); + + if (!error_msg.empty() && m_ses.m_alerts.should_post(alert::warning)) + { + m_ses.m_alerts.post_alert(fastresume_rejected_alert( + get_handle(), error_msg)); +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_ses.m_logger) << "fastresume data for " + << torrent_file().name() << " rejected: " + << error_msg << "\n"; +#endif + } } catch (std::exception& e) { @@ -2378,8 +2401,6 @@ namespace libtorrent piece_manager& torrent::filesystem() { - INVARIANT_CHECK; - TORRENT_ASSERT(m_owning_storage.get()); return *m_owning_storage; } @@ -2537,6 +2558,29 @@ namespace libtorrent return limit; } + void torrent::delete_files() + { +#if defined(TORRENT_VERBOSE_LOGGING) + for (peer_iterator i = m_connections.begin(); + i != m_connections.end(); ++i) + { + (*i->second->m_logger) << "*** DELETING FILES IN TORRENT\n"; + } +#endif + + disconnect_all(); + m_paused = true; + // tell the tracker that we stopped + m_event = tracker_request::stopped; + + if (m_owning_storage.get()) + { + TORRENT_ASSERT(m_storage); + m_storage->async_delete_files( + bind(&torrent::on_files_deleted, shared_from_this(), _1, _2)); + } + } + void torrent::pause() { INVARIANT_CHECK; @@ -2768,7 +2812,7 @@ namespace libtorrent !boost::bind(&peer_connection::is_connecting , boost::bind(&std::map::value_type::second, _1))); - st.compact_mode = m_compact_mode; + st.storage_mode = m_storage_mode; st.num_complete = m_complete; st.num_incomplete = m_incomplete; diff --git a/libtorrent/src/torrent_handle.cpp b/libtorrent/src/torrent_handle.cpp index f6b5d47de..9418d50e8 100755 --- a/libtorrent/src/torrent_handle.cpp +++ b/libtorrent/src/torrent_handle.cpp @@ -661,7 +661,7 @@ namespace libtorrent if (!t->valid_metadata()) return entry(); - t->filesystem().export_piece_map(piece_index); + std::vector have_pieces = t->pieces(); entry ret(entry::dictionary_t); @@ -673,10 +673,6 @@ namespace libtorrent const sha1_hash& info_hash = t->torrent_file().info_hash(); ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); - ret["slots"] = entry(entry::list_t); - entry::list_type& slots = ret["slots"].list(); - std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); - // blocks per piece int num_blocks_per_piece = static_cast(t->torrent_file().piece_length()) / t->block_size(); @@ -706,6 +702,8 @@ namespace libtorrent // the unfinished piece's index piece_struct["piece"] = i->index; + have_pieces[i->index] = true; + std::string bitmask; const int num_bitmask_bytes = (std::max)(num_blocks_per_piece / 8, 1); @@ -722,10 +720,10 @@ namespace libtorrent } piece_struct["bitmask"] = bitmask; - TORRENT_ASSERT(t->filesystem().slot_for_piece(i->index) >= 0); + TORRENT_ASSERT(t->filesystem().slot_for(i->index) >= 0); unsigned long adler = t->filesystem().piece_crc( - t->filesystem().slot_for_piece(i->index) + t->filesystem().slot_for(i->index) , t->block_size() , i->info); @@ -735,6 +733,11 @@ namespace libtorrent up.push_back(piece_struct); } } + + t->filesystem().export_piece_map(piece_index, have_pieces); + entry::list_type& slots = ret["slots"].list(); + std::copy(piece_index.begin(), piece_index.end(), std::back_inserter(slots)); + // write local peers entry::list_type& peer_list = ret["peers"].list(); From c259aea06a571674b2829794ec168ec9247dae9a Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 09:01:59 +0000 Subject: [PATCH 11/13] Fix adding/removing torrents with current libtorrent. --- deluge/core/torrentmanager.py | 10 ++++++++-- libtorrent/bindings/python/src/session.cpp | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 3cea9beae..222ddeebc 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -171,13 +171,19 @@ class TorrentManager: # Make sure we are adding it with the correct allocation method. if compact is None: compact = self.config["compact_allocation"] + + # Set the right storage_mode + if compact: + storage_mode = lt.storage_mode_t(1) + else: + storage_mode = lt.storage_mode_t(2) try: handle = self.session.add_torrent( lt.torrent_info(torrent_filedump), str(save_path), resume_data=fastresume, - compact_mode=compact, + storage_mode=storage_mode, paused=paused) except RuntimeError: log.warning("Error adding torrent") @@ -228,7 +234,7 @@ class TorrentManager: """Remove a torrent from the manager""" try: # Remove from libtorrent session - self.session.remove_torrent(self.torrents[torrent_id].handle) + self.session.remove_torrent(self.torrents[torrent_id].handle, 0) except RuntimeError, KeyError: log.warning("Error removing torrent") return False diff --git a/libtorrent/bindings/python/src/session.cpp b/libtorrent/bindings/python/src/session.cpp index 7332da2ea..05ec2a497 100755 --- a/libtorrent/bindings/python/src/session.cpp +++ b/libtorrent/bindings/python/src/session.cpp @@ -185,8 +185,8 @@ void bind_session() .def( "add_torrent", &add_torrent , ( - arg("resume_data") = entry(), arg("compact_mode") = true - , arg("paused") = false + arg("resume_data") = entry(), arg("storage_mode") = storage_mode_sparse, + arg("paused") = false ) , session_add_torrent_doc ) From bf6a709340063dd8344617d36349c559baf0ce31 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 15 Oct 2007 15:12:35 +0000 Subject: [PATCH 12/13] Start of ConnectionManager. --- deluge/ui/gtkui/connectionmanager.py | 36 ++++ .../ui/gtkui/glade/connection_manager.glade | 170 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 deluge/ui/gtkui/connectionmanager.py create mode 100644 deluge/ui/gtkui/glade/connection_manager.glade diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py new file mode 100644 index 000000000..d088e6c00 --- /dev/null +++ b/deluge/ui/gtkui/connectionmanager.py @@ -0,0 +1,36 @@ +# +# connectionmanager.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. + +class ConnectionManager: + def __init__(self): + diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade new file mode 100644 index 000000000..301294e08 --- /dev/null +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -0,0 +1,170 @@ + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Deluge Connection Manager + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-missing-image + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <big><b>Deluge Connection Manager</b></big> + True + + + False + 1 + + + + + False + 5 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_RESIZE_QUEUE + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_SPREAD + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + gtk-add + + + + + False + False + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + gtk-remove + + + + + False + False + 1 + + + + + False + 1 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_EDGE + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-connect + True + + + 1 + + + + + False + 2 + + + + + + From c852cfd7c154072df8cb15169b72c699a6903944 Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Sat, 20 Oct 2007 16:55:38 +0000 Subject: [PATCH 13/13] Updates to the ConnectionManager stuff. This is a work in progress. Renamed functions.py to client.py. --- deluge/core/core.py | 10 +- deluge/core/signalmanager.py | 5 + deluge/ui/{functions.py => client.py} | 36 +- deluge/ui/gtkui/aboutdialog.py | 4 +- deluge/ui/gtkui/connectionmanager.py | 165 ++- .../ui/gtkui/glade/connection_manager.glade | 261 +++- deluge/ui/gtkui/glade/main_window.glade | 1114 +++++++++-------- deluge/ui/gtkui/gtkui.py | 6 +- deluge/ui/gtkui/mainwindow.py | 19 +- deluge/ui/gtkui/menubar.py | 22 +- deluge/ui/gtkui/pluginmanager.py | 13 +- deluge/ui/gtkui/preferences.py | 17 +- deluge/ui/gtkui/signals.py | 4 +- deluge/ui/gtkui/statusbar.py | 17 +- deluge/ui/gtkui/systemtray.py | 20 +- deluge/ui/gtkui/torrentdetails.py | 4 +- deluge/ui/gtkui/torrentview.py | 10 +- deluge/ui/signalreceiver.py | 6 + 18 files changed, 1093 insertions(+), 640 deletions(-) rename deluge/ui/{functions.py => client.py} (83%) diff --git a/deluge/core/core.py b/deluge/core/core.py index cdf219004..562da3c7c 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -88,7 +88,7 @@ class Core( # Setup the xmlrpc server try: SimpleXMLRPCServer.SimpleXMLRPCServer.__init__( - self, ("localhost", 6666), logRequests=False, allow_none=True) + self, ("localhost", 58846), logRequests=False, allow_none=True) except: log.info("Daemon already running or port not available..") sys.exit(0) @@ -200,6 +200,10 @@ class Core( self.loop.quit() # Exported Methods + def export_ping(self): + """A method to see if the core is running""" + return True + def export_shutdown(self): """Shutdown the core""" # Make shutdown an async call @@ -209,6 +213,10 @@ class Core( """Registers a client with the signal manager so that signals are sent to it.""" self.signals.register_client(uri) + + def export_deregister_client(self, uri): + """De-registers a client with the signal manager.""" + self.signals.deregister_client(uri) def export_add_torrent_file(self, filename, save_path, filedump): """Adds a torrent file to the libtorrent session diff --git a/deluge/core/signalmanager.py b/deluge/core/signalmanager.py index ec3fdf318..db018330f 100644 --- a/deluge/core/signalmanager.py +++ b/deluge/core/signalmanager.py @@ -39,6 +39,11 @@ class SignalManager: def __init__(self): self.clients = [] + def deregister_client(self, uri): + """Deregisters a client""" + log.debug("Deregistering %s as a signal reciever..", uri) + self.clients.remove(self.clients.index(uri)) + def register_client(self, uri): """Registers a client to emit signals to.""" log.debug("Registering %s as a signal reciever..", uri) diff --git a/deluge/ui/functions.py b/deluge/ui/client.py similarity index 83% rename from deluge/ui/functions.py rename to deluge/ui/client.py index 2334d1b6d..c84a864e6 100644 --- a/deluge/ui/functions.py +++ b/deluge/ui/client.py @@ -45,12 +45,27 @@ from deluge.log import LOG as log class CoreProxy: def __init__(self): + self._uri = None self._core = None + self._on_new_core_callbacks = [] + + def connect_on_new_core(self, callback): + """Connect a callback to be called when a new core is connected to.""" + self._on_new_core_callbacks.append(callback) + + def set_core_uri(self, uri): + log.info("Setting core uri as %s", uri) + self._uri = uri + # Get a new core + self.get_core() def get_core(self): - if self._core is None: + if self._core is None and self._uri is not None: log.debug("Creating ServerProxy..") - self._core = xmlrpclib.ServerProxy("http://localhost:6666") + self._core = xmlrpclib.ServerProxy(self._uri) + # Call any callbacks registered + for callback in self._on_new_core_callbacks: + callback() return self._core @@ -69,6 +84,14 @@ def get_core_plugin(plugin): core = dbus.Interface(proxy, "org.deluge_torrent.Deluge." + plugin) return core +def connect_on_new_core(callback): + """Connect a callback whenever a new core is connected to.""" + return _core.connect_on_new_core(callback) + +def set_core_uri(uri): + """Sets the core uri""" + return _core.set_core_uri(uri) + def shutdown(): """Shutdown the core daemon""" get_core().shutdown() @@ -156,6 +179,15 @@ def get_available_plugins(): def get_enabled_plugins(): return get_core().get_enabled_plugins() +def get_download_rate(): + return get_core().get_download_rate() + +def get_upload_rate(): + return get_core().get_upload_rate() + +def get_num_connections(): + return get_core().get_num_connections() + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/aboutdialog.py b/deluge/ui/gtkui/aboutdialog.py index 2c596580d..e53ef24f4 100644 --- a/deluge/ui/gtkui/aboutdialog.py +++ b/deluge/ui/gtkui/aboutdialog.py @@ -37,13 +37,13 @@ import gtk import pkg_resources import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client class AboutDialog: def __init__(self): # Get the glade file for the about dialog def url_hook(dialog, url): - functions.open_url_in_browser(url) + client.open_url_in_browser(url) gtk.about_dialog_set_url_hook(url_hook) self.about = gtk.glade.XML(pkg_resources.resource_filename(\ "deluge.ui.gtkui", "glade/aboutdialog.glade")).get_widget(\ diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index d088e6c00..9fe9e4f31 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -31,6 +31,169 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import gtk, gtk.glade +import pkg_resources +import gobject +import socket + +import deluge.xmlrpclib as xmlrpclib +import deluge.common +import deluge.ui.client as client +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +DEFAULT_CONFIG = { + "hosts": ["localhost:58846"] +} + class ConnectionManager: - def __init__(self): + def __init__(self, window): + # 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 = window + self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG) + self.connection_manager = self.glade.get_widget("connection_manager") + self.hostlist = self.glade.get_widget("hostlist") + self.connection_manager.set_icon(deluge.common.get_logo(16)) + self.glade.get_widget("image1").set_from_pixbuf( + deluge.common.get_logo(32)) + + self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str) + + # Fill in hosts from config file + for host in self.config["hosts"]: + row = self.liststore.append() + self.liststore.set_value(row, 1, host) + + # Setup host list treeview + self.hostlist.set_model(self.liststore) + render = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn("Status", render, pixbuf=0) + self.hostlist.append_column(column) + render = gtk.CellRendererText() + column = gtk.TreeViewColumn("Host", render, text=1) + 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_cancel_clicked": self.on_button_cancel_clicked, + "on_button_connect_clicked": self.on_button_connect_clicked, + }) + + self.connection_manager.connect("delete-event", self.on_delete_event) + + def show(self): + self.update_timer = gobject.timeout_add(5000, self.update) + self.update() + self.connection_manager.show_all() + + def hide(self): + self.connection_manager.hide() + gobject.source_remove(self.update_timer) + + def update(self): + """Updates the host status""" + def update_row(model=None, path=None, row=None, columns=None): + uri = model.get_value(row, 1) + uri = "http://" + uri + online = True + host = None + try: + host = xmlrpclib.ServerProxy(uri) + host.ping() + except socket.error: + print "socket.error!" + online = False + + print "online: ", online + del host + + if online: + image = gtk.STOCK_YES + else: + image = gtk.STOCK_NO + + pixbuf = self.connection_manager.render_icon( + image, gtk.ICON_SIZE_MENU) + + model.set_value(row, 0, pixbuf) + + self.liststore.foreach(update_row) + return 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, 1)) + + hostlist = [] + self.liststore.foreach(append_row, hostlist) + self.config["hosts"] = hostlist + self.config.save() + + ## Callbacks + def on_delete_event(self, widget, event): + self.hide() + return True + + def on_button_addhost_clicked(self, widget): + log.debug("on_button_addhost_clicked") + dialog = self.glade.get_widget("addhost_dialog") + dialog.set_icon(deluge.common.get_logo(16)) + hostname_entry = self.glade.get_widget("entry_hostname") + port_spinbutton = self.glade.get_widget("spinbutton_port") + response = dialog.run() + if response == 1: + # We add the host + hostname = hostname_entry.get_text() + if hostname.startswith("http://"): + hostname = hostname[7:] + + # Check to make sure the hostname is at least 1 character long + if len(hostname) < 1: + dialog.hide() + return + + # Get the port and concatenate the hostname string + port = port_spinbutton.get_value_as_int() + hostname = hostname + ":" + str(port) + row = self.liststore.append() + self.liststore.set_value(row, 1, hostname) + # Save the host list to file + self.save() + + dialog.hide() + + def on_button_removehost_clicked(self, widget): + log.debug("on_button_removehost_clicked") + # Get the selected rows + paths = self.hostlist.get_selection().get_selected_rows()[1] + for path in paths: + self.liststore.remove(self.liststore.get_iter(path)) + + # Save the host list + self.save() + + def on_button_startdaemon_clicked(self, widget): + log.debug("on_button_startdaemon_clicked") + + def on_button_cancel_clicked(self, widget): + log.debug("on_button_cancel_clicked") + self.hide() + + def on_button_connect_clicked(self, widget): + log.debug("on_button_connect_clicked") + paths = self.hostlist.get_selection().get_selected_rows()[1] + row = self.liststore.get_iter(paths[0]) + uri = self.liststore.get_value(row, 1) + uri = "http://" + uri + client.set_core_uri(uri) + self.window.start() + self.hide() diff --git a/deluge/ui/gtkui/glade/connection_manager.glade b/deluge/ui/gtkui/glade/connection_manager.glade index 301294e08..ba85aee3b 100644 --- a/deluge/ui/gtkui/glade/connection_manager.glade +++ b/deluge/ui/gtkui/glade/connection_manager.glade @@ -1,14 +1,23 @@ - + - + + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Deluge Connection Manager - - + 5 + Connection Manager + GTK_WIN_POS_CENTER_ON_PARENT + 350 + 300 + GDK_WINDOW_TYPE_HINT_DIALOG + False + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 True @@ -29,7 +38,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <big><b>Deluge Connection Manager</b></big> + <big><b>Connection Manager</b></big> True @@ -41,12 +50,14 @@ False 5 + 1 - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 True @@ -60,7 +71,7 @@ GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC - + True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -72,25 +83,41 @@ - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_SPREAD - + True - True - True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + GTK_BUTTONBOX_START - + True + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - gtk-add + gtk-add + True + 0 + + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-remove + True + 0 + + + + 1 + + False @@ -98,61 +125,123 @@ - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 + 0 + - + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - gtk-remove + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-execute + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Start local daemon + True + + + 1 + + False False + GTK_PACK_END 1 False + False 1 - 1 + 10 + 2 - + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + checkbutton + 0 + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Options + + + label_item + + + + + False + 3 + + + + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_BUTTONBOX_EDGE + GTK_BUTTONBOX_END - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True + 0 + - + True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-connect True + 0 + 1 @@ -161,7 +250,123 @@ False - 2 + GTK_PACK_END + + + + + + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Add Host + GTK_WIN_POS_CENTER + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Hostname: + + + False + False + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Port: + + + False + False + 2 + + + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + 5 + 1 + 58846 1 65535 1 10 10 + True + + + False + False + 3 + + + + + False + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + True + 1 + + + 1 + + + + + False + GTK_PACK_END diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index abbfc68fc..801b52a1a 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -113,6 +113,20 @@ + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + _Connection Manager + True + + + + gtk-connect + + + + @@ -348,6 +362,287 @@ 1 2 10 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 10 + 10 + 15 + 15 + + + True + 7 + 2 + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 1 + 2 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + <b>Path:</b> + True + + + + + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 6 + 7 + + + + + + True + 0 + + + 1 + 2 + 5 + 6 + + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + 4 + 5 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 0 + 1 + <b>Name:</b> + True + + + + + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Next Announce:</b> + True + + + + + 6 + 7 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker Status:</b> + True + + + + + 5 + 6 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Tracker:</b> + True + + + + + 4 + 5 + GTK_FILL + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b>Total Size:</b> + True + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + True + PANGO_WRAP_WORD_CHAR + + + 1 + 2 + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + 1 + <b># of files:</b> + True + + + + + 3 + 4 + GTK_FILL + + + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Torrent Info</b> + True + + + label_item + + + + + 1 + 2 + GTK_FILL + + True @@ -380,277 +675,18 @@ 4 5 - - True - 0 - - - 1 - 2 - - - - + True 0 + True + PANGO_WRAP_WORD_CHAR 3 4 - - - - - True - 0 - - - 1 - 2 - 1 - 2 - - - - - True - 0 - - - 3 - 4 - 1 - 2 - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - True - 0 - - - 3 - 4 - 2 - 3 - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - True - 0 - - - 3 - 4 - 3 - 4 - - - - - True - 5 - - - True - 0 - <b>Downloaded:</b> - True - - - - - - - True - 5 - - - True - 0 - <b>Uploaded:</b> - True - - - - - 1 - 2 - - - - - True - 5 - - - True - 0 - <b>Seeders:</b> - True - - - - - 2 - 3 - - - - - True - 5 - - - True - 0 - <b>Share Ratio:</b> - True - - - - - 3 - 4 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>Speed:</b> - True - - - - - 2 - 3 - 1 - 2 - - - - - True - 15 - 5 - - - True - 0 - <b>Peers:</b> - True - - - - - 2 - 3 - 2 - 3 - - - - - True - 15 - 5 - - - True - 0 - <b>ETA:</b> - True - - - - - 2 - 3 - 3 - 4 - - - - - True - 0 - 1 - <b>Pieces:</b> - True - - - 4 - 5 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - - - - - 1 - 2 4 5 + @@ -677,18 +713,277 @@ - + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + 0 + + + + + 1 + 2 + 4 + 5 + + + + + True + 0 + 1 + <b>Pieces:</b> + True + + + 4 + 5 + + + + + True + 15 + 5 + + + True + 0 + <b>ETA:</b> + True + + + + + 2 + 3 + 3 + 4 + + + + + True + 15 + 5 + + + True + 0 + <b>Peers:</b> + True + + + + + 2 + 3 + 2 + 3 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + 1 + 2 + + + + + True + 15 + 5 + + + True + 0 + <b>Speed:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Share Ratio:</b> + True + + + + + 3 + 4 + + + + + True + 5 + + + True + 0 + <b>Seeders:</b> + True + + + + + 2 + 3 + + + + + True + 5 + + + True + 0 + <b>Uploaded:</b> + True + + + + + 1 + 2 + + + + + True + 5 + + + True + 0 + <b>Downloaded:</b> + True + + + + + + True 0 - True - PANGO_WRAP_WORD_CHAR 3 4 - 4 - 5 - + 3 + 4 + + + + + True + 0 + + + 1 + 2 + 3 + 4 + + + + + True + 0 + + + 3 + 4 + 2 + 3 + + + + + True + 0 + + + 1 + 2 + 2 + 3 + + + + + True + 0 + + + 3 + 4 + 1 + 2 + + + + + True + 0 + + + 1 + 2 + 1 + 2 + + + + + True + 0 + + + 3 + 4 + + + + + True + 0 + + + 1 + 2 @@ -718,287 +1013,6 @@ GTK_FILL - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 10 - 10 - 15 - 15 - - - True - 7 - 2 - 2 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b># of files:</b> - True - - - - - 3 - 4 - GTK_FILL - - - - - True - 0 - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - - - 1 - 2 - 2 - 3 - - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Total Size:</b> - True - - - - - 2 - 3 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker:</b> - True - - - - - 4 - 5 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Tracker Status:</b> - True - - - - - 5 - 6 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 1 - <b>Next Announce:</b> - True - - - - - 6 - 7 - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - 0 - 0 - 1 - <b>Name:</b> - True - - - - - GTK_FILL - - - - - True - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 4 - 5 - - - - - - True - 0 - - - 1 - 2 - 5 - 6 - - - - - - True - 0 - - - 1 - 2 - 6 - 7 - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - <b>Path:</b> - True - - - - - 1 - 2 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - True - PANGO_WRAP_WORD_CHAR - - - 1 - 2 - 1 - 2 - - - - - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Torrent Info</b> - True - - - label_item - - - - - 1 - 2 - GTK_FILL - - diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index f11c89198..8d761072d 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -93,13 +93,13 @@ class GtkUI: self.mainwindow = MainWindow() # Start the signal receiver - self.signal_receiver = Signals(self) + #self.signal_receiver = Signals(self) # Initalize the plugins self.plugins = PluginManager(self) # Start the mainwindow and show it - self.mainwindow.start() + #self.mainwindow.start() # Start the gtk main loop gtk.gdk.threads_init() @@ -113,6 +113,6 @@ class GtkUI: # Clean-up del self.mainwindow - #del self.signal_receiver + # del self.signal_receiver del self.plugins del deluge.configmanager diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index 5d882cbb4..5d4d14316 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -37,6 +37,7 @@ import gtk, gtk.glade import gobject import pkg_resources +import deluge.ui.client as client from deluge.configmanager import ConfigManager from menubar import MenuBar from toolbar import ToolBar @@ -45,6 +46,7 @@ from torrentdetails import TorrentDetails from preferences import Preferences from systemtray import SystemTray from statusbar import StatusBar +from connectionmanager import ConnectionManager import deluge.common from deluge.log import LOG as log @@ -81,15 +83,19 @@ class MainWindow: self.preferences = Preferences(self) self.systemtray = SystemTray(self) self.statusbar = StatusBar(self) - - def start(self): - """Start the update thread and show the window""" - self.update_timer = gobject.timeout_add(1000, self.update) + self.connectionmanager = ConnectionManager(self) + client.connect_on_new_core(self.start) if not(self.config["start_in_tray"] and \ self.config["enable_system_tray"]) and not \ self.window.get_property("visible"): log.debug("Showing window") self.show() + self.connectionmanager.show() + + def start(self): + """Start the update thread and show the window""" + self.torrentview.start() + self.update_timer = gobject.timeout_add(1000, self.update) def update(self): # Don't update the UI if the the window is minimized. @@ -121,7 +127,10 @@ class MainWindow: def quit(self): # Stop the update timer from running - gobject.source_remove(self.update_timer) + try: + gobject.source_remove(self.update_timer) + except: + pass del self.systemtray del self.menubar del self.toolbar diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index e7e3d8c0e..721228f3e 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -36,7 +36,7 @@ pygtk.require('2.0') import gtk, gtk.glade import pkg_resources -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.log import LOG as log @@ -70,6 +70,8 @@ class MenuBar: ## Edit Menu "on_menuitem_preferences_activate": \ self.on_menuitem_preferences_activate, + "on_menuitem_connectionmanager_activate": \ + self.on_menuitem_connectionmanager_activate, ## View Menu "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled, @@ -97,14 +99,14 @@ class MenuBar: def on_menuitem_addtorrent_activate(self, data=None): log.debug("on_menuitem_addtorrent_activate") from addtorrentdialog import AddTorrentDialog - functions.add_torrent_file(AddTorrentDialog().run()) + client.add_torrent_file(AddTorrentDialog().run()) def on_menuitem_addurl_activate(self, data=None): log.debug("on_menuitem_addurl_activate") from addtorrenturl import AddTorrentUrl result = AddTorrentUrl().run() if result is not None: - functions.add_torrent_url(result) + client.add_torrent_url(result) def on_menuitem_clear_activate(self, data=None): log.debug("on_menuitem_clear_activate") @@ -113,7 +115,7 @@ class MenuBar: log.debug("on_menuitem_quitdaemon_activate") # Tell the core to shutdown self.window.quit() - functions.shutdown() + client.shutdown() def on_menuitem_quit_activate(self, data=None): log.debug("on_menuitem_quit_activate") @@ -124,20 +126,24 @@ class MenuBar: log.debug("on_menuitem_preferences_activate") self.window.preferences.show() + def on_menuitem_connectionmanager_activate(self, data=None): + log.debug("on_menuitem_connectionmanager_activate") + self.window.connectionmanager.show() + ## Torrent Menu ## def on_menuitem_pause_activate(self, data=None): log.debug("on_menuitem_pause_activate") - functions.pause_torrent( + client.pause_torrent( self.window.torrentview.get_selected_torrents()) def on_menuitem_resume_activate(self, data=None): log.debug("on_menuitem_resume_activate") - functions.resume_torrent( + client.resume_torrent( self.window.torrentview.get_selected_torrents()) def on_menuitem_updatetracker_activate(self, data=None): log.debug("on_menuitem_updatetracker_activate") - functions.force_reannounce( + client.force_reannounce( self.window.torrentview.get_selected_torrents()) def on_menuitem_edittrackers_activate(self, data=None): @@ -145,7 +151,7 @@ class MenuBar: def on_menuitem_remove_activate(self, data=None): log.debug("on_menuitem_remove_activate") - functions.remove_torrent( + client.remove_torrent( self.window.torrentview.get_selected_torrents()) ## View Menu ## diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 9c46ada4f..57217d415 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -32,7 +32,7 @@ # statement from all source files in the program, then also delete it here. import deluge.pluginmanagerbase -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -41,16 +41,21 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase): self.config = ConfigManager("gtkui.conf") self._gtkui = gtkui - + + # Register a callback with the client + client.connect_on_new_core(self.start) + + def start(self): + """Start the plugin manager""" # Update the enabled_plugins from the core - enabled_plugins = functions.get_enabled_plugins() + enabled_plugins = client.get_enabled_plugins() enabled_plugins += self.config["enabled_plugins"] enabled_plugins = list(set(enabled_plugins)) self.config["enabled_plugins"] = enabled_plugins deluge.pluginmanagerbase.PluginManagerBase.__init__( self, "gtkui.conf", "deluge.plugin.ui.gtk") - + def get_torrentview(self): """Returns a reference to the torrentview component""" return self._gtkui.mainwindow.torrentview diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index 9f65803a6..e78178243 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -37,7 +37,7 @@ import gtk, gtk.glade import pkg_resources from deluge.log import LOG as log -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager @@ -51,7 +51,6 @@ class Preferences: self.pref_dialog.set_icon(deluge.common.get_logo(32)) self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") - self.core = functions.get_core() self.gtkui_config = ConfigManager("gtkui.conf") # Setup the liststore for the categories (tab pages) self.liststore = gtk.ListStore(int, str) @@ -104,7 +103,7 @@ class Preferences: self.liststore.append([index, name]) def show(self): - self.core_config = functions.get_config() + self.core_config = client.get_config() # Update the preferences dialog to reflect current config settings ## Downloads tab ## @@ -134,7 +133,7 @@ class Preferences: self.glade.get_widget("spin_port_max").set_value( self.core_config["listen_ports"][1]) self.glade.get_widget("active_port_label").set_text( - str(functions.get_listen_port())) + str(client.get_listen_port())) self.glade.get_widget("chk_random_port").set_active( self.core_config["random_port"]) self.glade.get_widget("chk_dht").set_active( @@ -193,8 +192,8 @@ class Preferences: self.gtkui_config["send_info"]) ## Plugins tab ## - all_plugins = functions.get_available_plugins() - enabled_plugins = functions.get_enabled_plugins() + all_plugins = client.get_available_plugins() + enabled_plugins = client.get_enabled_plugins() # Clear the existing list so we don't duplicate entries. self.plugin_liststore.clear() # Iterate through the lists and add them to the liststore @@ -310,7 +309,7 @@ class Preferences: config_to_set[key] = new_core_config[key] # Set each changed config value in the core - functions.set_config(config_to_set) + client.set_config(config_to_set) # Update the configuration self.core_config.update(config_to_set) @@ -387,8 +386,8 @@ class Preferences: def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") url = "http://deluge-torrent.org/test-port.php?port=%s" % \ - functions.get_listen_port() - functions.open_url_in_browser(url) + client.get_listen_port() + client.open_url_in_browser(url) def on_plugin_toggled(self, renderer, path): log.debug("on_plugin_toggled") diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 37b9f35e3..dff1f8533 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -37,7 +37,7 @@ from deluge.log import LOG as log class Signals: def __init__(self, ui): self.ui = ui - self.receiver = SignalReceiver(6667, "http://localhost:6666") + self.receiver = SignalReceiver(6667, "http://localhost:56684") self.receiver.start() self.receiver.connect_to_signal("torrent_added", self.torrent_added_signal) @@ -49,7 +49,7 @@ class Signals: self.torrent_all_paused) self.receiver.connect_to_signal("torrent_all_resumed", self.torrent_all_resumed) - + def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index 64d0f9379..5d619bc8d 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -34,13 +34,12 @@ import gtk import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client class StatusBar: def __init__(self, window): self.window = window self.statusbar = self.window.main_glade.get_widget("statusbar") - self.core = functions.get_core() # Add a HBox to the statusbar after removing the initial label widget self.hbox = gtk.HBox() @@ -69,36 +68,36 @@ class StatusBar: expand=False, fill=False) # Update once before showing - self.update() +# self.update() self.statusbar.show_all() def update(self): # Set the max connections label - max_connections = functions.get_config_value("max_connections_global") + max_connections = client.get_config_value("max_connections_global") if max_connections < 0: max_connections = _("Unlimited") self.label_connections.set_text("%s (%s)" % ( - self.core.get_num_connections(), max_connections)) + client.get_num_connections(), max_connections)) # Set the download speed label - max_download_speed = functions.get_config_value("max_download_speed") + max_download_speed = client.get_config_value("max_download_speed") if max_download_speed < 0: max_download_speed = _("Unlimited") else: max_download_speed = "%s %s" % (max_download_speed, _("KiB/s")) self.label_download_speed.set_text("%s/s (%s)" % ( - deluge.common.fsize(self.core.get_download_rate()), + deluge.common.fsize(client.get_download_rate()), max_download_speed)) # Set the upload speed label - max_upload_speed = functions.get_config_value("max_upload_speed") + max_upload_speed = client.get_config_value("max_upload_speed") if max_upload_speed < 0: max_upload_speed = _("Unlimited") else: max_upload_speed = "%s %s" % (max_upload_speed, _("KiB/s")) self.label_upload_speed.set_text("%s/s (%s)" % ( - deluge.common.fsize(self.core.get_upload_rate()), + deluge.common.fsize(client.get_upload_rate()), max_upload_speed)) diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 14767206d..3f85072be 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -34,7 +34,7 @@ import gtk import pkg_resources -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.configmanager import ConfigManager from deluge.log import LOG as log @@ -49,7 +49,6 @@ class SystemTray: def enable(self): """Enables the system tray icon.""" log.debug("Enabling the system tray icon..") - self.core = functions.get_core() self.tray = gtk.status_icon_new_from_icon_name('deluge') self.tray.connect("activate", self.on_tray_clicked) self.tray.connect("popup-menu", self.on_tray_popup) @@ -79,6 +78,7 @@ class SystemTray: self.tray_glade.get_widget("upload-limit-image").set_from_file( deluge.common.get_pixmap("seeding16.png")) + def start(self): # Build the bandwidth speed limit menus self.build_tray_bwsetsubmenu() @@ -86,13 +86,13 @@ class SystemTray: # Create the Download speed list sub-menu submenu_bwdownset = self.build_menu_radio_list( self.config["tray_download_speed_list"], self.tray_setbwdown, - functions.get_config_value("max_download_speed"), + client.get_config_value("max_download_speed"), _("KiB/s"), show_notset=True, show_other=True) # Create the Upload speed list sub-menu submenu_bwupset = self.build_menu_radio_list( self.config["tray_upload_speed_list"], self.tray_setbwup, - functions.get_config_value("max_upload_speed"), + client.get_config_value("max_upload_speed"), _("KiB/s"), show_notset=True, show_other=True) # Add the sub-menus to the tray menu @@ -160,7 +160,7 @@ class SystemTray: def on_menuitem_add_torrent_activate(self, menuitem): log.debug("on_menuitem_add_torrent_activate") from addtorrentdialog import AddTorrentDialog - functions.add_torrent_file(AddTorrentDialog().run()) + client.add_torrent_file(AddTorrentDialog().run()) def on_menuitem_pause_all_activate(self, menuitem): log.debug("on_menuitem_pause_all_activate") @@ -184,13 +184,13 @@ class SystemTray: log.debug("on_menuitem_quitdaemon_activate") if self.window.visible(): self.window.quit() - functions.shutdown() + client.shutdown() else: if self.config["lock_tray"] == True: self.unlock_tray("quitdaemon") else: self.window.quit() - functions.shutdown() + client.shutdown() def build_menu_radio_list(self, value_list, callback, pref_value=None, suffix=None, show_notset=False, notset_label=None, notset_lessthan=0, @@ -281,7 +281,7 @@ class SystemTray: spin_title.set_text(_("%s Speed (KiB/s):" % string)) spin_speed = dialog_glade.get_widget("spin_speed") spin_speed.set_value( - functions.get_config_value(core_key)) + client.get_config_value(core_key)) spin_speed.select_region(0, -1) response = speed_dialog.run() if response == 1: # OK Response @@ -294,7 +294,7 @@ class SystemTray: # Set the config in the core value = float(value) config_to_set = {core_key: value} - functions.set_config(config_to_set) + client.set_config(config_to_set) # Update the tray speed limit list if value not in self.config[ui_key] and value >= 0: @@ -338,7 +338,7 @@ window, please enter your password")) log.debug("Showing main window via tray") self.window.show() elif comingnext == "quitdaemon": - functions.shutdown() + client.shutdown() self.window.hide() self.window.quit() elif comingnext == "quitui": diff --git a/deluge/ui/gtkui/torrentdetails.py b/deluge/ui/gtkui/torrentdetails.py index 03cc0c0bf..df36673ec 100644 --- a/deluge/ui/gtkui/torrentdetails.py +++ b/deluge/ui/gtkui/torrentdetails.py @@ -38,7 +38,7 @@ pygtk.require('2.0') import gtk, gtk.glade import gettext -import deluge.ui.functions as functions +import deluge.ui.client as client import deluge.common from deluge.log import LOG as log @@ -93,7 +93,7 @@ class TorrentDetails: "upload_payload_rate", "num_peers", "num_seeds", "total_peers", "total_seeds", "eta", "ratio", "tracker", "next_announce", "tracker_status", "save_path"] - status = functions.get_torrent_status(selected, status_keys) + status = client.get_torrent_status(selected, status_keys) # Check to see if we got valid data from the core if status is None: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index dbf961862..f01649fff 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -40,7 +40,7 @@ import gettext import gobject import deluge.common -import deluge.ui.functions as functions +import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview @@ -163,12 +163,14 @@ class TorrentView(listview.ListView): self.treeview.get_selection().connect("changed", self.on_selection_changed) + def start(self): + """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. - session_state = functions.get_session_state() + session_state = client.get_session_state() for torrent_id in session_state: self.add_row(torrent_id) - + def update(self, columns=None): """Update the view. If columns is not None, it will attempt to only update those columns selected. @@ -212,7 +214,7 @@ class TorrentView(listview.ListView): # Remove duplicates from status_key list status_keys = list(set(status_keys)) - status = functions.get_torrent_status(torrent_id, + status = client.get_torrent_status(torrent_id, status_keys) # Set values for each column in the row diff --git a/deluge/ui/signalreceiver.py b/deluge/ui/signalreceiver.py index 2df0435e2..0c1f7e08e 100644 --- a/deluge/ui/signalreceiver.py +++ b/deluge/ui/signalreceiver.py @@ -48,6 +48,8 @@ class SignalReceiver( log.debug("SignalReceiver init..") threading.Thread.__init__(self) + self.port = port + # Daemonize the thread so it exits when the main program does self.setDaemon(True) @@ -68,7 +70,11 @@ class SignalReceiver( # FIXME: send actual URI not localhost core = xmlrpclib.ServerProxy(core_uri) core.register_client("http://localhost:" + str(port)) + + def __del__(self): + core.deregister_client("http://localhost:" + str(self.port)) + def run(self): """This gets called when we start the thread""" t = threading.Thread(target=self.serve_forever)