From 5cd86aa5bcbd246f4ed6b4bed026cd72045304a3 Mon Sep 17 00:00:00 2001 From: bendikro Date: Mon, 14 Jan 2013 03:14:28 +0100 Subject: [PATCH 1/4] Optimizations to daemon and rpc messages. * Implemented torrent updates with libtorrent post_torrent_updates. This required some changes to how core/torrenthandler handles get_torrent_status * Reworked how torrent.py handles torrent info * Removed some unneeded RPC message requests from client. * Added tests for some expensive log prints. --- deluge/core/alertmanager.py | 7 +- deluge/core/core.py | 38 ++-- deluge/core/filtermanager.py | 19 +- deluge/core/torrent.py | 395 ++++++++++++++++++---------------- deluge/core/torrentmanager.py | 131 +++++++++-- deluge/ui/gtkui/statusbar.py | 18 +- deluge/ui/gtkui/systemtray.py | 11 +- deluge/ui/sessionproxy.py | 13 +- 8 files changed, 378 insertions(+), 254 deletions(-) diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 063bda723..970a1506e 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -66,12 +66,12 @@ class AlertManager(component.Component): # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} self.handlers = {} - self.delayed_calls = [] + self.wait_on_handler = False def update(self): self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()] - self.handle_alerts() + self.handle_alerts(wait=self.wait_on_handler) def stop(self): for dc in self.delayed_calls: @@ -122,7 +122,8 @@ class AlertManager(component.Component): while alert is not None: alert_type = type(alert).__name__ # Display the alert message - log.debug("%s: %s", alert_type, alert.message()) + if log.isEnabledFor(logging.DEBUG): + log.debug("%s: %s", alert_type, alert.message()) # Call any handlers for this alert type if alert_type in self.handlers: for handler in self.handlers[alert_type]: diff --git a/deluge/core/core.py b/deluge/core/core.py index 242359a9d..5b8161346 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -427,21 +427,28 @@ class Core(component.Component): for torrent_id in torrent_ids: self.torrentmanager[torrent_id].resume() - @export - def get_torrent_status(self, torrent_id, keys, diff=False): - # Build the status dictionary + def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=True): try: - status = self.torrentmanager[torrent_id].get_status(keys, diff) + if update: + status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff) + else: + status = self.torrentmanager[torrent_id].create_status_dict(torrent_keys, diff) except KeyError: + import traceback + traceback.print_exc() # Torrent was probaly removed meanwhile return {} - # Get the leftover fields and ask the plugin manager to fill them - leftover_fields = list(set(keys) - set(status.keys())) - if len(leftover_fields) > 0: - status.update(self.pluginmanager.get_status(torrent_id, leftover_fields)) + # Ask the plugin manager to fill in the plugin keys + if len(plugin_keys) > 0: + status.update(self.pluginmanager.get_status(torrent_id, plugin_keys)) return status + @export + def get_torrent_status(self, torrent_id, keys, diff=False): + torrent_keys, plugin_keys = self.torrentmanager.separate_keys(keys, [torrent_id]) + return self.create_torrent_status(torrent_id, torrent_keys, plugin_keys, diff=diff, update=True) + @export def get_torrents_status(self, filter_dict, keys, diff=False): """ @@ -449,12 +456,17 @@ class Core(component.Component): """ torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict) status_dict = {}.fromkeys(torrent_ids) + d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=False) - # Get the torrent status for each torrent_id - for torrent_id in torrent_ids: - status_dict[torrent_id] = self.get_torrent_status(torrent_id, keys, diff) - - return status_dict + def add_plugin_fields(args): + status_dict, plugin_keys = args + # Ask the plugin manager to fill in the plugin keys + if len(plugin_keys) > 0: + for key in status_dict.keys(): + status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys)) + return status_dict + d.addCallback(add_plugin_fields) + return d @export def get_filter_tree(self , show_zero_hits=True, hide_cat=None): diff --git a/deluge/core/filtermanager.py b/deluge/core/filtermanager.py index 53e95030c..eba44316c 100644 --- a/deluge/core/filtermanager.py +++ b/deluge/core/filtermanager.py @@ -106,16 +106,15 @@ def tracker_error_filter(torrent_ids, values): # If this is a tracker_host, then we need to filter on it if values[0] != "Error": for torrent_id in torrent_ids: - if values[0] == tm[torrent_id].get_status(["tracker_host"])["tracker_host"]: + if values[0] == tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]: filtered_torrent_ids.append(torrent_id) return filtered_torrent_ids # Check all the torrent's tracker_status for 'Error:' and only return torrent_ids # that have this substring in their tracker_status for torrent_id in torrent_ids: - if _("Error") + ":" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]: + if _("Error") + ":" in tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]: filtered_torrent_ids.append(torrent_id) - return filtered_torrent_ids class FilterManager(component.Component): @@ -192,13 +191,11 @@ class FilterManager(component.Component): #leftover filter arguments: #default filter on status fields. - status_func = self.core.get_torrent_status #premature optimalisation.. for torrent_id in list(torrent_ids): - status = status_func(torrent_id, filter_dict.keys()) #status={key:value} + status = self.torrents[torrent_id].create_status_dict(filter_dict.keys()) #status={key:value} for field, values in filter_dict.iteritems(): if (not status[field] in values) and torrent_id in torrent_ids: torrent_ids.remove(torrent_id) - return torrent_ids def get_filter_tree(self, show_zero_hits=True, hide_cat=None): @@ -207,17 +204,16 @@ class FilterManager(component.Component): for use in sidebar. """ torrent_ids = self.torrents.get_torrent_list() - status_func = self.core.get_torrent_status #premature optimalisation.. tree_keys = list(self.tree_fields.keys()) if hide_cat: for cat in hide_cat: tree_keys.remove(cat) - items = dict( (field, self.tree_fields[field]()) for field in tree_keys) + torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids) + items = dict((field, self.tree_fields[field]()) for field in tree_keys) - #count status fields. for torrent_id in list(torrent_ids): - status = status_func(torrent_id, tree_keys) #status={key:value} + status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys, update=False) #status={key:value} for field in tree_keys: value = status[field] items[field][value] = items[field].get(value, 0) + 1 @@ -264,9 +260,8 @@ class FilterManager(component.Component): del self.tree_fields[field] def filter_state_active(self, torrent_ids): - get_status = self.core.get_torrent_status for torrent_id in list(torrent_ids): - status = get_status(torrent_id, ["download_payload_rate", "upload_payload_rate"]) + status = self.torrents[torrent_id].create_status_dict(["download_payload_rate", "upload_payload_rate"]) if status["download_payload_rate"] or status["upload_payload_rate"]: pass #ok else: diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index c5fd81955..61e52b7b0 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -109,10 +109,14 @@ class Torrent(object): """Torrent holds information about torrents added to the libtorrent session. """ def __init__(self, handle, options, state=None, filename=None, magnet=None, owner=None): - log.debug("Creating torrent object %s", str(handle.info_hash())) + # Set the torrent_id for this torrent + self.torrent_id = str(handle.info_hash()) + + if log.isEnabledFor(logging.DEBUG): + log.debug("Creating torrent object %s", self.torrent_id) + # Get the core config self.config = ConfigManager("core.conf") - self.rpcserver = component.get("RPCServer") # This dict holds previous status dicts returned for this torrent @@ -124,8 +128,6 @@ class Torrent(object): # Set the libtorrent handle self.handle = handle - # Set the torrent_id for this torrent - self.torrent_id = str(handle.info_hash()) # Keep a list of Deferreds for file indexes we're waiting for file_rename alerts on # This is so we can send one folder_renamed signal instead of multiple @@ -151,6 +153,9 @@ class Torrent(object): except RuntimeError: self.torrent_info = None + self.handle_has_metadata = None + self.status_funcs = None + # Default total_uploaded to 0, this may be changed by the state self.total_uploaded = 0 @@ -187,9 +192,11 @@ class Torrent(object): self.statusmsg = "OK" # The torrents state - self.update_state() + # This is only one out of 4 calls to update_state for each torrent on startup. + # This call doesn't seem to be necessary, it can probably be removed + #self.update_state() + self.state = None - # The tracker status self.tracker_status = "" # This gets updated when get_tracker_host is called @@ -217,7 +224,14 @@ class Torrent(object): self.forcing_recheck = False self.forcing_recheck_paused = False - log.debug("Torrent object created.") + if log.isEnabledFor(logging.DEBUG): + log.debug("Torrent object created.") + + def has_metadata(self): + if self.handle_has_metadata: + return self.handle_has_metadata + self.handle_has_metadata = self.handle.has_metadata() + return self.handle_has_metadata ## Options methods ## def set_options(self, options): @@ -248,7 +262,7 @@ class Torrent(object): return self.options def get_name(self): - if self.handle.has_metadata(): + if self.has_metadata(): name = self.torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0] if not name: name = self.torrent_info.name() @@ -306,7 +320,7 @@ class Torrent(object): # reset all the piece priorities self.set_file_priorities(self.options["file_priorities"]) return - if not self.handle.has_metadata(): + if not self.has_metadata(): return if self.options["compact_allocation"]: log.debug("Setting first/last priority with compact " @@ -315,7 +329,7 @@ class Torrent(object): # A list of priorities for each piece in the torrent priorities = self.handle.piece_priorities() prioritized_pieces = [] - ti = self.handle.get_torrent_info() + ti = self.torrent_info for i in range(ti.num_files()): f = ti.file_at(i) two_percent_bytes = int(0.02 * f.size) @@ -366,7 +380,9 @@ class Torrent(object): self.options["move_completed_path"] = move_completed_path def set_file_priorities(self, file_priorities): - if len(file_priorities) != len(self.get_files()): + if not self.has_metadata(): + return + if len(file_priorities) != self.ti_num_files(): log.debug("file_priorities len != num_files") self.options["file_priorities"] = self.handle.file_priorities() return @@ -376,7 +392,8 @@ class Torrent(object): self.options["file_priorities"] = self.handle.file_priorities() return - log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities) + if log.isEnabledFor(logging.DEBUG): + log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities) self.handle.prioritize_files(file_priorities) @@ -390,9 +407,10 @@ class Torrent(object): self.update_state() break - self.options["file_priorities"] = self.handle.file_priorities() - if self.options["file_priorities"] != list(file_priorities): - log.warning("File priorities were not set for this torrent") + # Do we really need this? + #self.options["file_priorities"] = self.handle.file_priorities() + #if self.options["file_priorities"] != list(file_priorities): + # log.warning("File priorities were not set for this torrent") # Set the first/last priorities if needed if self.options["prioritize_first_last_pieces"]: @@ -411,7 +429,9 @@ class Torrent(object): self.tracker_host = None return - log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) + if log.isEnabledFor(logging.DEBUG): + log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) + tracker_list = [] for tracker in trackers: @@ -421,8 +441,9 @@ class Torrent(object): self.handle.replace_trackers(tracker_list) # Print out the trackers - #for t in self.handle.trackers(): - # log.debug("tier: %s tracker: %s", t["tier"], t["url"]) + if log.isEnabledFor(logging.DEBUG): + for t in self.handle.trackers(): + log.debug("tier: %s tracker: %s", t["tier"], t["url"]) # Set the tracker list in the torrent object self.trackers = trackers if len(trackers) > 0: @@ -444,7 +465,8 @@ class Torrent(object): """Updates the state based on what libtorrent's state for the torrent is""" # Set the initial state based on the lt state LTSTATE = deluge.common.LT_TORRENT_STATE - ltstate = int(self.handle.status().state) + status = self.handle.status() + ltstate = int(status.state) # Set self.state to the ltstate right away just incase we don't hit some # of the logic below @@ -453,21 +475,26 @@ class Torrent(object): else: self.state = str(ltstate) - log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate]) - log.debug("session.is_paused: %s", component.get("Core").session.is_paused()) + session_is_paused = component.get("Core").session.is_paused() + is_auto_managed = self.handle.is_auto_managed() + handle_is_paused = self.handle.is_paused() + + if log.isEnabledFor(logging.DEBUG): + log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate]) + log.debug("session.is_paused: %s", session_is_paused) # First we check for an error from libtorrent, and set the state to that # if any occurred. - if len(self.handle.status().error) > 0: + if len(status.error) > 0: # This is an error'd torrent self.state = "Error" - self.set_status_message(self.handle.status().error) - if self.handle.is_paused(): + self.set_status_message(status.error) + if self.handle_is_paused: self.handle.auto_managed(False) return if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]: - if self.handle.is_paused(): + if self.handle_is_paused: self.state = "Paused" else: self.state = "Checking" @@ -479,9 +506,9 @@ class Torrent(object): elif ltstate == LTSTATE["Allocating"]: self.state = "Allocating" - if self.handle.is_paused() and self.handle.is_auto_managed() and not component.get("Core").session.is_paused(): + if not session_is_paused and handle_is_paused and is_auto_managed: self.state = "Queued" - elif component.get("Core").session.is_paused() or (self.handle.is_paused() and not self.handle.is_auto_managed()): + elif session_is_paused or (handle_is_paused and not is_auto_managed): self.state = "Paused" def set_state(self, state): @@ -498,11 +525,7 @@ class Torrent(object): def get_eta(self): """Returns the ETA in seconds for this torrent""" - if self.status == None: - status = self.handle.status() - else: - status = self.status - + status = self.status if self.is_finished and self.options["stop_at_ratio"]: # We're a seed, so calculate the time to the 'stop_share_ratio' if not status.upload_payload_rate: @@ -524,32 +547,21 @@ class Torrent(object): def get_ratio(self): """Returns the ratio for this torrent""" - if self.status == None: - status = self.handle.status() - else: - status = self.status - - if status.total_done > 0: + if self.status.total_done > 0: # We use 'total_done' if the downloaded value is 0 - downloaded = status.total_done + downloaded = self.status.total_done else: # Return -1.0 to signify infinity return -1.0 - return float(status.all_time_upload) / float(downloaded) + return float(self.status.all_time_upload) / float(downloaded) def get_files(self): """Returns a list of files this torrent contains""" - if self.torrent_info == None and self.handle.has_metadata(): - torrent_info = self.handle.get_torrent_info() - else: - torrent_info = self.torrent_info - - if not torrent_info: + if not self.has_metadata(): return [] - ret = [] - files = torrent_info.files() + files = self.torrent_info.files() for index, file in enumerate(files): ret.append({ 'index': index, @@ -599,7 +611,7 @@ class Torrent(object): def get_file_progress(self): """Returns the file progress as a list of floats.. 0.0 -> 1.0""" - if not self.handle.has_metadata(): + if not self.has_metadata(): return 0.0 file_progress = self.handle.file_progress() @@ -618,9 +630,6 @@ class Torrent(object): if self.tracker_host: return self.tracker_host - if not self.status: - self.status = self.handle.status() - tracker = self.status.current_tracker if not tracker and self.trackers: tracker = self.trackers[0]["url"] @@ -673,143 +682,96 @@ class Torrent(object): :rtype: dict """ + status = self.handle.status() + self.update_status(status) + return self.create_status_dict(keys) - # Create the full dictionary - self.status = self.handle.status() - if self.handle.has_metadata(): + def update_status(self, status): + self.status = status + + if self.torrent_info is None and self.has_metadata(): self.torrent_info = self.handle.get_torrent_info() - # Adjust progress to be 0-100 value - progress = self.status.progress * 100 - - # Adjust status.distributed_copies to return a non-negative value - distributed_copies = self.status.distributed_copies - if distributed_copies < 0: - distributed_copies = 0.0 - - # Calculate the seeds:peers ratio - if self.status.num_incomplete == 0: - # Use -1.0 to signify infinity - seeds_peers_ratio = -1.0 - else: - seeds_peers_ratio = self.status.num_complete / float(self.status.num_incomplete) - - full_status = { - "active_time": self.status.active_time, - "all_time_download": self.status.all_time_download, - "compact": self.options["compact_allocation"], - "distributed_copies": distributed_copies, - "download_payload_rate": self.status.download_payload_rate, - "file_priorities": self.options["file_priorities"], - "hash": self.torrent_id, - "is_auto_managed": self.options["auto_managed"], - "is_finished": self.is_finished, - "max_connections": self.options["max_connections"], - "max_download_speed": self.options["max_download_speed"], - "max_upload_slots": self.options["max_upload_slots"], - "max_upload_speed": self.options["max_upload_speed"], - "message": self.statusmsg, - "move_on_completed_path": self.options["move_completed_path"], - "move_on_completed": self.options["move_completed"], - "move_completed_path": self.options["move_completed_path"], - "move_completed": self.options["move_completed"], - "next_announce": self.status.next_announce.seconds, - "num_peers": self.status.num_peers - self.status.num_seeds, - "num_seeds": self.status.num_seeds, - "owner": self.owner, - "paused": self.status.paused, - "prioritize_first_last": self.options["prioritize_first_last_pieces"], - "sequential_download": self.options["sequential_download"], - "progress": progress, - "shared": self.options["shared"], - "remove_at_ratio": self.options["remove_at_ratio"], - "save_path": self.options["download_location"], - "seeding_time": self.status.seeding_time, - "seeds_peers_ratio": seeds_peers_ratio, - "seed_rank": self.status.seed_rank, - "state": self.state, - "stop_at_ratio": self.options["stop_at_ratio"], - "stop_ratio": self.options["stop_ratio"], - "time_added": self.time_added, - "total_done": self.status.total_done, - "total_payload_download": self.status.total_payload_download, - "total_payload_upload": self.status.total_payload_upload, - "total_peers": self.status.num_incomplete, - "total_seeds": self.status.num_complete, - "total_uploaded": self.status.all_time_upload, - "total_wanted": self.status.total_wanted, - "tracker": self.status.current_tracker, - "trackers": self.trackers, - "tracker_status": self.tracker_status, - "upload_payload_rate": self.status.upload_payload_rate - } - - def ti_comment(): - if self.handle.has_metadata(): - try: - return self.torrent_info.comment().decode("utf8", "ignore") - except UnicodeDecodeError: - return self.torrent_info.comment() - return "" - - def ti_priv(): - if self.handle.has_metadata(): - return self.torrent_info.priv() - return False - def ti_total_size(): - if self.handle.has_metadata(): - return self.torrent_info.total_size() - return 0 - def ti_num_files(): - if self.handle.has_metadata(): - return self.torrent_info.num_files() - return 0 - def ti_num_pieces(): - if self.handle.has_metadata(): - return self.torrent_info.num_pieces() - return 0 - def ti_piece_length(): - if self.handle.has_metadata(): - return self.torrent_info.piece_length() - return 0 - def ti_pieces_info(): - if self.handle.has_metadata(): - return self.get_pieces_info() - return None - - fns = { - "comment": ti_comment, - "eta": self.get_eta, - "file_progress": self.get_file_progress, - "files": self.get_files, - "is_seed": self.handle.is_seed, - "name": self.get_name, - "num_files": ti_num_files, - "num_pieces": ti_num_pieces, - "pieces": ti_pieces_info, - "peers": self.get_peers, - "piece_length": ti_piece_length, - "private": ti_priv, - "queue": self.handle.queue_position, - "ratio": self.get_ratio, - "total_size": ti_total_size, - "tracker_host": self.get_tracker_host, - "last_seen_complete": self.get_last_seen_complete - } + if not self.status_funcs: + #if you add a key here->add it to core.py STATUS_KEYS too. + self.status_funcs = { + "active_time": lambda: self.status.active_time, + "all_time_download": lambda: self.status.all_time_download, + "compact": lambda: self.options["compact_allocation"], + "distributed_copies": lambda: 0.0 if self.status.distributed_copies < 0 else \ + self.status.distributed_copies, # Adjust status.distributed_copies to return a non-negative value + "download_payload_rate": lambda: self.status.download_payload_rate, + "file_priorities": lambda: self.options["file_priorities"], + "hash": lambda: self.torrent_id, + "is_auto_managed": lambda: self.options["auto_managed"], + "is_finished": lambda: self.is_finished, + "max_connections": lambda: self.options["max_connections"], + "max_download_speed": lambda: self.options["max_download_speed"], + "max_upload_slots": lambda: self.options["max_upload_slots"], + "max_upload_speed": lambda: self.options["max_upload_speed"], + "message": lambda: self.statusmsg, + "move_on_completed_path": lambda: self.options["move_completed_path"], + "move_on_completed": lambda: self.options["move_completed"], + "move_completed_path": lambda: self.options["move_completed_path"], + "move_completed": lambda: self.options["move_completed"], + "next_announce": lambda: self.status.next_announce.seconds, + "num_peers": lambda: self.status.num_peers - self.status.num_seeds, + "num_seeds": lambda: self.status.num_seeds, + "owner": lambda: self.owner, + "paused": lambda: self.status.paused, + "prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"], + "sequential_download": lambda: self.options["sequential_download"], + "progress": lambda: self.status.progress * 100, + "shared": lambda: self.options["shared"], + "remove_at_ratio": lambda: self.options["remove_at_ratio"], + "save_path": lambda: self.options["download_location"], + "seeding_time": lambda: self.status.seeding_time, + "seeds_peers_ratio": lambda: -1.0 if self.status.num_incomplete == 0 else \ + self.status.num_complete / float(self.status.num_incomplete), # Use -1.0 to signify infinity + "seed_rank": lambda: self.status.seed_rank, + "state": lambda: self.state, + "stop_at_ratio": lambda: self.options["stop_at_ratio"], + "stop_ratio": lambda: self.options["stop_ratio"], + "time_added": lambda: self.time_added, + "total_done": lambda: self.status.total_done, + "total_payload_download": lambda: self.status.total_payload_download, + "total_payload_upload": lambda: self.status.total_payload_upload, + "total_peers": lambda: self.status.num_incomplete, + "total_seeds": lambda: self.status.num_complete, + "total_uploaded": lambda: self.status.all_time_upload, + "total_wanted": lambda: self.status.total_wanted, + "tracker": lambda: self.status.current_tracker, + "trackers": lambda: self.trackers, + "tracker_status": lambda: self.tracker_status, + "upload_payload_rate": lambda: self.status.upload_payload_rate, + "eta": self.get_eta, + "file_progress": self.get_file_progress, # Adjust progress to be 0-100 value + "files": self.get_files, + "is_seed": self.handle.is_seed, + "peers": self.get_peers, + "queue": self.handle.queue_position, + "ratio": self.get_ratio, + "tracker_host": self.get_tracker_host, + "last_seen_complete": self.get_last_seen_complete, + "comment": self.ti_comment, + "name": self.ti_name, + "num_files": self.ti_num_files, + "num_pieces": self.ti_num_pieces, + "pieces": self.ti_pieces_info, + "piece_length": self.ti_piece_length, + "private": self.ti_priv, + "total_size": self.ti_total_size, + } + def create_status_dict(self, keys, diff=False): # Create the desired status dictionary and return it status_dict = {} - if len(keys) == 0: - status_dict = full_status - for key in fns: - status_dict[key] = fns[key]() - else: - for key in keys: - if key in full_status: - status_dict[key] = full_status[key] - elif key in fns: - status_dict[key] = fns[key]() + if not keys: + keys = self.status_funcs.keys() + + for key in keys: + status_dict[key] = self.status_funcs[key]() session_id = self.rpcserver.get_session_id() if diff: @@ -831,6 +793,70 @@ class Torrent(object): return status_dict + def ti_comment(self): + if self.has_metadata(): + try: + return self.torrent_info.comment().decode("utf8", "ignore") + except UnicodeDecodeError: + return self.torrent_info.comment() + return "" + + def ti_name(self): + if self.has_metadata(): + name = self.torrent_info.file_at(0).path.split("/", 1)[0] + if not name: + name = self.torrent_info.name() + try: + return name.decode("utf8", "ignore") + except UnicodeDecodeError: + return name + + elif self.magnet: + try: + keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')]) + name = keys.get('dn') + if not name: + return self.torrent_id + name = unquote(name).replace('+', ' ') + try: + return name.decode("utf8", "ignore") + except UnicodeDecodeError: + return name + except: + pass + + return self.torrent_id + + def ti_priv(self): + if self.has_metadata(): + return self.torrent_info.priv() + return False + + def ti_total_size(self): + if self.has_metadata(): + return self.torrent_info.total_size() + return 0 + + def ti_num_files(self): + if self.has_metadata(): + return self.torrent_info.num_files() + return 0 + + def ti_num_pieces(self): + if self.has_metadata(): + return self.torrent_info.num_pieces() + return 0 + + def ti_piece_length(self): + if self.has_metadata(): + return self.torrent_info.piece_length() + return 0 + + def ti_pieces_info(self): + if self.has_metadata(): + return self.get_pieces_info() + return None + def apply_options(self): """Applies the per-torrent options that are set.""" self.handle.set_max_connections(self.max_connections) @@ -906,7 +932,7 @@ class Torrent(object): except TypeError: # String is already unicode pass - + if not os.path.exists(dest): try: # Try to make the destination path if it doesn't exist @@ -943,7 +969,6 @@ class Torrent(object): self.torrent_id) log.debug("Writing torrent file: %s", path) try: - self.torrent_info = self.handle.get_torrent_info() # Regenerate the file priorities self.set_file_priorities([]) md = lt.bdecode(self.torrent_info.metadata()) @@ -1116,7 +1141,7 @@ class Torrent(object): pieces[peer_info.downloading_piece_index] = 2 # Now, the rest of the pieces - for idx, piece in enumerate(self.handle.status().pieces): + for idx, piece in enumerate(self.status.pieces): if idx in pieces: # Piece beeing downloaded, handled above continue diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 9831646b3..c6542e9cb 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -41,9 +41,11 @@ import os import shutil import operator import logging +import time from twisted.internet.task import LoopingCall from twisted.internet.defer import Deferred, DeferredList +from twisted.internet import reactor from deluge._libtorrent import lt @@ -157,6 +159,10 @@ class TorrentManager(component.Component): # Keeps track of resume data self.resume_data = {} + self.torrents_status_requests = [] + self.status_dict = {} + self.state_update_alert_start = None + # Register set functions self.config.register_set_function("max_connections_per_torrent", self.on_set_max_connections_per_torrent) @@ -200,6 +206,8 @@ class TorrentManager(component.Component): self.on_alert_file_error) self.alerts.register_handler("file_completed_alert", self.on_alert_file_completed) + self.alerts.register_handler("state_update_alert", + self.state_update_alert) def start(self): # Get the pluginmanager reference @@ -286,7 +294,8 @@ class TorrentManager(component.Component): torrent_info = None # Get the torrent data from the torrent file try: - log.debug("Attempting to create torrent_info from %s", filepath) + if log.isEnabledFor(logging.DEBUG): + log.debug("Attempting to create torrent_info from %s", filepath) _file = open(filepath, "rb") torrent_info = lt.torrent_info(lt.bdecode(_file.read())) _file.close() @@ -321,7 +330,6 @@ class TorrentManager(component.Component): def add(self, torrent_info=None, state=None, options=None, save_state=True, filedump=None, filename=None, magnet=None, resume_data=None, owner=None): """Add a torrent to the manager and returns it's torrent_id""" - if owner is None: owner = component.get("RPCServer").get_session_user() if not owner: @@ -331,7 +339,6 @@ class TorrentManager(component.Component): log.debug("You must specify a valid torrent_info, torrent state or magnet.") return - log.debug("torrentmanager.add") add_torrent_params = {} if filedump is not None: @@ -439,8 +446,8 @@ class TorrentManager(component.Component): add_torrent_params["ti"] = torrent_info - #log.info("Adding torrent: %s", filename) - log.debug("options: %s", options) + if log.isEnabledFor(logging.DEBUG): + log.debug("options: %s", options) # Set the right storage_mode if options["compact_allocation"]: @@ -474,7 +481,8 @@ class TorrentManager(component.Component): component.resume("AlertManager") return - log.debug("handle id: %s", str(handle.info_hash())) + if log.isEnabledFor(logging.DEBUG): + log.debug("handle id: %s", str(handle.info_hash())) # Set auto_managed to False because the torrent is paused handle.auto_managed(False) # Create a Torrent object @@ -485,6 +493,7 @@ class TorrentManager(component.Component): if not account_exists: owner = 'localclient' torrent = Torrent(handle, options, state, filename, magnet, owner) + # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent if self.config["queue_new_to_top"]: @@ -531,10 +540,14 @@ class TorrentManager(component.Component): component.get("EventManager").emit( TorrentAddedEvent(torrent.torrent_id, from_state) ) - log.info("Torrent %s from user \"%s\" %s", - torrent.get_status(["name"])["name"], - torrent.get_status(["owner"])["owner"], - (from_state and "loaded" or "added")) + + if log.isEnabledFor(logging.INFO): + name_and_owner = torrent.get_status(["name", "owner"]) + log.info("Torrent %s from user \"%s\" %s" % ( + name_and_owner["name"], + name_and_owner["owner"], + from_state and "loaded" or "added") + ) return torrent.torrent_id def load_torrent(self, torrent_id): @@ -646,28 +659,37 @@ class TorrentManager(component.Component): # Try to use an old state try: - state_tmp = TorrentState() - if dir(state.torrents[0]) != dir(state_tmp): - for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))): - for s in state.torrents: - setattr(s, attr, getattr(state_tmp, attr, None)) + if len(state.torrents) > 0: + state_tmp = TorrentState() + if dir(state.torrents[0]) != dir(state_tmp): + for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))): + for s in state.torrents: + setattr(s, attr, getattr(state_tmp, attr, None)) except Exception, e: log.warning("Unable to update state file to a compatible version: %s", e) + import traceback + print traceback.format_exc() # Reorder the state.torrents list to add torrents in the correct queue # order. state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"]) - resume_data = self.load_resume_data_file() + # Tell alertmanager to wait for the handlers while adding torrents. + # This speeds up startup loading the torrents by quite a lot for some reason (~40%) + self.alerts.wait_on_handler = True + for torrent_state in state.torrents: try: self.add(state=torrent_state, save_state=False, resume_data=resume_data.get(torrent_state.torrent_id)) except AttributeError, e: log.error("Torrent state file is either corrupt or incompatible! %s", e) + import traceback + traceback.print_exc() break + self.alerts.wait_on_handler = False if lt.version_minor < 16: log.debug("libtorrent version is lower than 0.16. Start looping " @@ -913,7 +935,8 @@ class TorrentManager(component.Component): self.save_resume_data((torrent_id, )) def on_alert_torrent_paused(self, alert): - log.debug("on_alert_torrent_paused") + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_torrent_paused") try: torrent = self.torrents[str(alert.handle.info_hash())] torrent_id = str(alert.handle.info_hash()) @@ -930,7 +953,8 @@ class TorrentManager(component.Component): self.save_resume_data((torrent_id,)) def on_alert_torrent_checked(self, alert): - log.debug("on_alert_torrent_checked") + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_torrent_checked") try: torrent = self.torrents[str(alert.handle.info_hash())] except: @@ -947,7 +971,8 @@ class TorrentManager(component.Component): torrent.update_state() def on_alert_tracker_reply(self, alert): - log.debug("on_alert_tracker_reply: %s", alert.message().decode("utf8")) + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_tracker_reply: %s", alert.message().decode("utf8")) try: torrent = self.torrents[str(alert.handle.info_hash())] except: @@ -964,7 +989,8 @@ class TorrentManager(component.Component): torrent.scrape_tracker() def on_alert_tracker_announce(self, alert): - log.debug("on_alert_tracker_announce") + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_tracker_announce") try: torrent = self.torrents[str(alert.handle.info_hash())] except: @@ -1016,7 +1042,8 @@ class TorrentManager(component.Component): component.get("EventManager").emit(TorrentResumedEvent(torrent_id)) def on_alert_state_changed(self, alert): - log.debug("on_alert_state_changed") + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_state_changed") try: torrent_id = str(alert.handle.info_hash()) torrent = self.torrents[torrent_id] @@ -1036,7 +1063,8 @@ class TorrentManager(component.Component): component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state)) def on_alert_save_resume_data(self, alert): - log.debug("on_alert_save_resume_data") + if log.isEnabledFor(logging.DEBUG): + log.debug("on_alert_save_resume_data") torrent_id = str(alert.handle.info_hash()) if torrent_id in self.torrents: @@ -1096,3 +1124,62 @@ class TorrentManager(component.Component): return component.get("EventManager").emit( TorrentFileCompletedEvent(torrent_id, alert.index)) + + def separate_keys(self, keys, torrent_ids): + """Separates the input keys into keys for the Torrent class + and keys for plugins. + """ + if not self.torrents: + return [], [] + for torrent_id in torrent_ids: + if torrent_id in self.torrents: + status_keys = self.torrents[torrent_id].status_funcs.keys() + leftover_keys = list(set(keys) - set(status_keys)) + torrent_keys = list(set(keys) - set(leftover_keys)) + return torrent_keys, leftover_keys + + def state_update_alert(self, alert): + self.state_update_alert_start = time.time() + log.debug("on_status_notification: %s", alert.message()) + + for s in alert.status: + torrent_id = str(s.info_hash) + if torrent_id in self.torrents: + self.torrents[torrent_id].update_status(s) + + self.handle_torrents_status_callback(self.torrents_status_requests.pop()) + + def handle_torrents_status_callback(self, status_request): + d, torrent_ids, keys, diff = status_request + status_dict = {}.fromkeys(torrent_ids) + torrent_keys, plugin_keys = self.separate_keys(keys, torrent_ids) + + # Get the torrent status for each torrent_id + for torrent_id in torrent_ids: + if not torrent_id in self.torrents: + # This happens sometimes, but why? Unfixed bug? + # Without this test it gives a KeyError. + print "Missing torrent id:", torrent_id + del status_dict[torrent_id] + else: + status_dict[torrent_id] = self.torrents[torrent_id].create_status_dict(torrent_keys, diff) + self.status_dict = status_dict + d.callback((status_dict, plugin_keys)) + + def torrents_status_update(self, torrent_ids, keys, diff=False): + """ + returns all torrents , optionally filtered by filter_dict. + If the torrent states were updated recently (less than two seconds ago, + post_torrent_updates is not called. Instead the cached state is used. + """ + d = Deferred() + now = time.time() + # Less than two seconds since last time the torrent states were updated + if self.state_update_alert_start and \ + (now - self.state_update_alert_start) < 2: + reactor.callLater(0, self.handle_torrents_status_callback, (d, torrent_ids, keys, diff)) + else: + # Ask libtorrent for status update + self.torrents_status_requests.insert(0, (d, torrent_ids, keys, diff)) + self.session.post_torrent_updates() + return d diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index f4219900c..e76e188d5 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -194,16 +194,16 @@ class StatusBar(component.Component): self.health = False + def update_config_values(configs): + self._on_max_connections_global(configs["max_connections_global"]) + self._on_max_download_speed(configs["max_download_speed"]) + self._on_max_upload_speed(configs["max_upload_speed"]) + self._on_dht(configs["dht"]) # Get some config values - client.core.get_config_value( - "max_connections_global").addCallback(self._on_max_connections_global) - client.core.get_config_value( - "max_download_speed").addCallback(self._on_max_download_speed) - client.core.get_config_value( - "max_upload_speed").addCallback(self._on_max_upload_speed) - client.core.get_config_value("dht").addCallback(self._on_dht) - - self.send_status_request() + client.core.get_config_values(["max_connections_global", "max_download_speed", + "max_upload_speed", "dht"]).addCallback(update_config_values) + # Called from update a few milliseconds later + #self.send_status_request() def stop(self): # When stopped, we just show the not connected thingy diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 553ebb354..7f957971c 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -172,11 +172,12 @@ class SystemTray(component.Component): self.build_tray_bwsetsubmenu() # Get some config values - client.core.get_config_value( - "max_download_speed").addCallback(self._on_max_download_speed) - client.core.get_config_value( - "max_upload_speed").addCallback(self._on_max_upload_speed) - self.send_status_request() + def update_config_values(configs): + self._on_max_download_speed(configs["max_download_speed"]) + self._on_max_upload_speed(configs["max_upload_speed"]) + client.core.get_config_values(["max_download_speed", "max_upload_speed"]).addCallback(update_config_values) + # This is called from update a few milliseconds later, so this line can be deleted + #self.send_status_request() def start(self): self.__start() diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py index d861afdc6..828e60758 100644 --- a/deluge/ui/sessionproxy.py +++ b/deluge/ui/sessionproxy.py @@ -85,11 +85,14 @@ class SessionProxy(component.Component): # core if it's not going to be used. Additional status fields # will be queried later, for example, when viewing the status tab # of a torrent. - inital_keys = [ - 'queue', 'state', 'name', 'total_wanted', 'progress', 'state', - 'download_payload_rate', 'upload_payload_rate', 'eta', 'owner' - ] - self.get_torrents_status({'id': torrent_ids}, inital_keys) + #inital_keys = [ + # 'queue', 'state', 'name', 'total_wanted', 'progress', 'state', + # 'download_payload_rate', 'upload_payload_rate', 'eta', 'owner' + #] + + #The the torrents status will be requested by torrentview, so this + #only causes a second request for the same data withing a few miliseconds + #self.get_torrents_status({'id': torrent_ids}, inital_keys) return client.core.get_session_state().addCallback(on_get_session_state) def stop(self): From 0f67dc168b03b2bed661e8a75b67c033070072a0 Mon Sep 17 00:00:00 2001 From: bendikro Date: Mon, 14 Jan 2013 22:55:06 +0100 Subject: [PATCH 2/4] Improvements and bug fixes --- deluge/core/core.py | 7 +- deluge/core/filtermanager.py | 10 +- deluge/core/torrent.py | 196 ++++++++++++++++++---------------- deluge/core/torrentmanager.py | 38 ++++--- deluge/ui/gtkui/statusbar.py | 2 - deluge/ui/gtkui/systemtray.py | 2 - deluge/ui/sessionproxy.py | 21 +--- 7 files changed, 138 insertions(+), 138 deletions(-) diff --git a/deluge/core/core.py b/deluge/core/core.py index 5b8161346..4a1fa693d 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -427,12 +427,9 @@ class Core(component.Component): for torrent_id in torrent_ids: self.torrentmanager[torrent_id].resume() - def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=True): + def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False): try: - if update: - status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff) - else: - status = self.torrentmanager[torrent_id].create_status_dict(torrent_keys, diff) + status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update) except KeyError: import traceback traceback.print_exc() diff --git a/deluge/core/filtermanager.py b/deluge/core/filtermanager.py index eba44316c..c9545072c 100644 --- a/deluge/core/filtermanager.py +++ b/deluge/core/filtermanager.py @@ -106,14 +106,14 @@ def tracker_error_filter(torrent_ids, values): # If this is a tracker_host, then we need to filter on it if values[0] != "Error": for torrent_id in torrent_ids: - if values[0] == tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]: + if values[0] == tm[torrent_id].get_status(["tracker_host"])["tracker_host"]: filtered_torrent_ids.append(torrent_id) return filtered_torrent_ids # Check all the torrent's tracker_status for 'Error:' and only return torrent_ids # that have this substring in their tracker_status for torrent_id in torrent_ids: - if _("Error") + ":" in tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]: + if _("Error") + ":" in tm[torrent_id].get_status(["tracker_host"])["tracker_host"]: filtered_torrent_ids.append(torrent_id) return filtered_torrent_ids @@ -192,7 +192,7 @@ class FilterManager(component.Component): #leftover filter arguments: #default filter on status fields. for torrent_id in list(torrent_ids): - status = self.torrents[torrent_id].create_status_dict(filter_dict.keys()) #status={key:value} + status = self.torrents[torrent_id].get_status(filter_dict.keys()) #status={key:value} for field, values in filter_dict.iteritems(): if (not status[field] in values) and torrent_id in torrent_ids: torrent_ids.remove(torrent_id) @@ -213,7 +213,7 @@ class FilterManager(component.Component): items = dict((field, self.tree_fields[field]()) for field in tree_keys) for torrent_id in list(torrent_ids): - status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys, update=False) #status={key:value} + status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys) #status={key:value} for field in tree_keys: value = status[field] items[field][value] = items[field].get(value, 0) + 1 @@ -261,7 +261,7 @@ class FilterManager(component.Component): def filter_state_active(self, torrent_ids): for torrent_id in list(torrent_ids): - status = self.torrents[torrent_id].create_status_dict(["download_payload_rate", "upload_payload_rate"]) + status = self.torrents[torrent_id].get_status(["download_payload_rate", "upload_payload_rate"]) if status["download_payload_rate"] or status["upload_payload_rate"]: pass #ok else: diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 61e52b7b0..ed9c27bfb 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -224,6 +224,9 @@ class Torrent(object): self.forcing_recheck = False self.forcing_recheck_paused = False + self.update_status(self.handle.status()) + self._create_status_funcs() + if log.isEnabledFor(logging.DEBUG): log.debug("Torrent object created.") @@ -407,10 +410,9 @@ class Torrent(object): self.update_state() break - # Do we really need this? - #self.options["file_priorities"] = self.handle.file_priorities() - #if self.options["file_priorities"] != list(file_priorities): - # log.warning("File priorities were not set for this torrent") + # In case values in file_priorities were faulty (old state?) + # we make sure the stored options are in sync + self.options["file_priorities"] = self.handle.file_priorities() # Set the first/last priorities if needed if self.options["prioritize_first_last_pieces"]: @@ -489,12 +491,12 @@ class Torrent(object): # This is an error'd torrent self.state = "Error" self.set_status_message(status.error) - if self.handle_is_paused: + if handle_is_paused: self.handle.auto_managed(False) return if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]: - if self.handle_is_paused: + if handle_is_paused: self.state = "Paused" else: self.state = "Checking" @@ -668,7 +670,7 @@ class Torrent(object): self.calculate_last_seen_complete() return self._last_seen_complete - def get_status(self, keys, diff=False): + def get_status(self, keys, diff=False, update=False): """ Returns the status of the torrent based on the keys provided @@ -677,104 +679,27 @@ class Torrent(object): :param diff: if True, will return a diff of the changes since the last call to get_status based on the session_id :type diff: bool + :param update: if True, the status will be updated from libtorrent + if False, the cached values will be returned + :type update: bool :returns: a dictionary of the status keys and their values :rtype: dict """ - status = self.handle.status() - self.update_status(status) - return self.create_status_dict(keys) - - def update_status(self, status): - self.status = status - - if self.torrent_info is None and self.has_metadata(): - self.torrent_info = self.handle.get_torrent_info() - - if not self.status_funcs: - #if you add a key here->add it to core.py STATUS_KEYS too. - self.status_funcs = { - "active_time": lambda: self.status.active_time, - "all_time_download": lambda: self.status.all_time_download, - "compact": lambda: self.options["compact_allocation"], - "distributed_copies": lambda: 0.0 if self.status.distributed_copies < 0 else \ - self.status.distributed_copies, # Adjust status.distributed_copies to return a non-negative value - "download_payload_rate": lambda: self.status.download_payload_rate, - "file_priorities": lambda: self.options["file_priorities"], - "hash": lambda: self.torrent_id, - "is_auto_managed": lambda: self.options["auto_managed"], - "is_finished": lambda: self.is_finished, - "max_connections": lambda: self.options["max_connections"], - "max_download_speed": lambda: self.options["max_download_speed"], - "max_upload_slots": lambda: self.options["max_upload_slots"], - "max_upload_speed": lambda: self.options["max_upload_speed"], - "message": lambda: self.statusmsg, - "move_on_completed_path": lambda: self.options["move_completed_path"], - "move_on_completed": lambda: self.options["move_completed"], - "move_completed_path": lambda: self.options["move_completed_path"], - "move_completed": lambda: self.options["move_completed"], - "next_announce": lambda: self.status.next_announce.seconds, - "num_peers": lambda: self.status.num_peers - self.status.num_seeds, - "num_seeds": lambda: self.status.num_seeds, - "owner": lambda: self.owner, - "paused": lambda: self.status.paused, - "prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"], - "sequential_download": lambda: self.options["sequential_download"], - "progress": lambda: self.status.progress * 100, - "shared": lambda: self.options["shared"], - "remove_at_ratio": lambda: self.options["remove_at_ratio"], - "save_path": lambda: self.options["download_location"], - "seeding_time": lambda: self.status.seeding_time, - "seeds_peers_ratio": lambda: -1.0 if self.status.num_incomplete == 0 else \ - self.status.num_complete / float(self.status.num_incomplete), # Use -1.0 to signify infinity - "seed_rank": lambda: self.status.seed_rank, - "state": lambda: self.state, - "stop_at_ratio": lambda: self.options["stop_at_ratio"], - "stop_ratio": lambda: self.options["stop_ratio"], - "time_added": lambda: self.time_added, - "total_done": lambda: self.status.total_done, - "total_payload_download": lambda: self.status.total_payload_download, - "total_payload_upload": lambda: self.status.total_payload_upload, - "total_peers": lambda: self.status.num_incomplete, - "total_seeds": lambda: self.status.num_complete, - "total_uploaded": lambda: self.status.all_time_upload, - "total_wanted": lambda: self.status.total_wanted, - "tracker": lambda: self.status.current_tracker, - "trackers": lambda: self.trackers, - "tracker_status": lambda: self.tracker_status, - "upload_payload_rate": lambda: self.status.upload_payload_rate, - "eta": self.get_eta, - "file_progress": self.get_file_progress, # Adjust progress to be 0-100 value - "files": self.get_files, - "is_seed": self.handle.is_seed, - "peers": self.get_peers, - "queue": self.handle.queue_position, - "ratio": self.get_ratio, - "tracker_host": self.get_tracker_host, - "last_seen_complete": self.get_last_seen_complete, - "comment": self.ti_comment, - "name": self.ti_name, - "num_files": self.ti_num_files, - "num_pieces": self.ti_num_pieces, - "pieces": self.ti_pieces_info, - "piece_length": self.ti_piece_length, - "private": self.ti_priv, - "total_size": self.ti_total_size, - } - - def create_status_dict(self, keys, diff=False): - # Create the desired status dictionary and return it - status_dict = {} + if update: + self.update_status(self.handle.status()) if not keys: keys = self.status_funcs.keys() + status_dict = {} + for key in keys: status_dict[key] = self.status_funcs[key]() - session_id = self.rpcserver.get_session_id() if diff: + session_id = self.rpcserver.get_session_id() if session_id in self.prev_status: # We have a previous status dict, so lets make a diff status_diff = {} @@ -793,6 +718,93 @@ class Torrent(object): return status_dict + def update_status(self, status): + """ + Updates the cached status. + + :param status: a libtorrent status + :type status: libtorrent.torrent_status + + """ + #import datetime + #print datetime.datetime.now().strftime("%H:%M:%S.%f"), + #print " update_status" + self.status = status + + if self.torrent_info is None and self.has_metadata(): + self.torrent_info = self.handle.get_torrent_info() + + def _create_status_funcs(self): + #if you add a key here->add it to core.py STATUS_KEYS too. + self.status_funcs = { + "active_time": lambda: self.status.active_time, + "all_time_download": lambda: self.status.all_time_download, + "compact": lambda: self.options["compact_allocation"], + "distributed_copies": lambda: 0.0 if self.status.distributed_copies < 0 else \ + self.status.distributed_copies, # Adjust status.distributed_copies to return a non-negative value + "download_payload_rate": lambda: self.status.download_payload_rate, + "file_priorities": lambda: self.options["file_priorities"], + "hash": lambda: self.torrent_id, + "is_auto_managed": lambda: self.options["auto_managed"], + "is_finished": lambda: self.is_finished, + "max_connections": lambda: self.options["max_connections"], + "max_download_speed": lambda: self.options["max_download_speed"], + "max_upload_slots": lambda: self.options["max_upload_slots"], + "max_upload_speed": lambda: self.options["max_upload_speed"], + "message": lambda: self.statusmsg, + "move_on_completed_path": lambda: self.options["move_completed_path"], + "move_on_completed": lambda: self.options["move_completed"], + "move_completed_path": lambda: self.options["move_completed_path"], + "move_completed": lambda: self.options["move_completed"], + "next_announce": lambda: self.status.next_announce.seconds, + "num_peers": lambda: self.status.num_peers - self.status.num_seeds, + "num_seeds": lambda: self.status.num_seeds, + "owner": lambda: self.owner, + "paused": lambda: self.status.paused, + "prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"], + "sequential_download": lambda: self.options["sequential_download"], + "progress": lambda: self.status.progress * 100, + "shared": lambda: self.options["shared"], + "remove_at_ratio": lambda: self.options["remove_at_ratio"], + "save_path": lambda: self.options["download_location"], + "seeding_time": lambda: self.status.seeding_time, + "seeds_peers_ratio": lambda: -1.0 if self.status.num_incomplete == 0 else \ + self.status.num_complete / float(self.status.num_incomplete), # Use -1.0 to signify infinity + "seed_rank": lambda: self.status.seed_rank, + "state": lambda: self.state, + "stop_at_ratio": lambda: self.options["stop_at_ratio"], + "stop_ratio": lambda: self.options["stop_ratio"], + "time_added": lambda: self.time_added, + "total_done": lambda: self.status.total_done, + "total_payload_download": lambda: self.status.total_payload_download, + "total_payload_upload": lambda: self.status.total_payload_upload, + "total_peers": lambda: self.status.num_incomplete, + "total_seeds": lambda: self.status.num_complete, + "total_uploaded": lambda: self.status.all_time_upload, + "total_wanted": lambda: self.status.total_wanted, + "tracker": lambda: self.status.current_tracker, + "trackers": lambda: self.trackers, + "tracker_status": lambda: self.tracker_status, + "upload_payload_rate": lambda: self.status.upload_payload_rate, + "eta": self.get_eta, + "file_progress": self.get_file_progress, # Adjust progress to be 0-100 value + "files": self.get_files, + "is_seed": self.handle.is_seed, + "peers": self.get_peers, + "queue": self.handle.queue_position, + "ratio": self.get_ratio, + "tracker_host": self.get_tracker_host, + "last_seen_complete": self.get_last_seen_complete, + "comment": self.ti_comment, + "name": self.ti_name, + "num_files": self.ti_num_files, + "num_pieces": self.ti_num_pieces, + "pieces": self.ti_pieces_info, + "piece_length": self.ti_piece_length, + "private": self.ti_priv, + "total_size": self.ti_total_size, + } + def ti_comment(self): if self.has_metadata(): try: diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c6542e9cb..1b86be2ce 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -161,7 +161,7 @@ class TorrentManager(component.Component): self.torrents_status_requests = [] self.status_dict = {} - self.state_update_alert_start = None + self.last_state_update_alert_ts = 0 # Register set functions self.config.register_set_function("max_connections_per_torrent", @@ -207,7 +207,7 @@ class TorrentManager(component.Component): self.alerts.register_handler("file_completed_alert", self.on_alert_file_completed) self.alerts.register_handler("state_update_alert", - self.state_update_alert) + self.on_alert_state_update) def start(self): # Get the pluginmanager reference @@ -666,9 +666,7 @@ class TorrentManager(component.Component): for s in state.torrents: setattr(s, attr, getattr(state_tmp, attr, None)) except Exception, e: - log.warning("Unable to update state file to a compatible version: %s", e) - import traceback - print traceback.format_exc() + log.exception("Unable to update state file to a compatible version: %s", e) # Reorder the state.torrents list to add torrents in the correct queue # order. @@ -1138,9 +1136,9 @@ class TorrentManager(component.Component): torrent_keys = list(set(keys) - set(leftover_keys)) return torrent_keys, leftover_keys - def state_update_alert(self, alert): - self.state_update_alert_start = time.time() + def on_alert_state_update(self, alert): log.debug("on_status_notification: %s", alert.message()) + self.last_state_update_alert_ts = time.time() for s in alert.status: torrent_id = str(s.info_hash) @@ -1150,6 +1148,9 @@ class TorrentManager(component.Component): self.handle_torrents_status_callback(self.torrents_status_requests.pop()) def handle_torrents_status_callback(self, status_request): + """ + Builds the status dictionary with the values from the Torrent. + """ d, torrent_ids, keys, diff = status_request status_dict = {}.fromkeys(torrent_ids) torrent_keys, plugin_keys = self.separate_keys(keys, torrent_ids) @@ -1162,21 +1163,32 @@ class TorrentManager(component.Component): print "Missing torrent id:", torrent_id del status_dict[torrent_id] else: - status_dict[torrent_id] = self.torrents[torrent_id].create_status_dict(torrent_keys, diff) + status_dict[torrent_id] = self.torrents[torrent_id].get_status(torrent_keys, diff) self.status_dict = status_dict d.callback((status_dict, plugin_keys)) def torrents_status_update(self, torrent_ids, keys, diff=False): """ - returns all torrents , optionally filtered by filter_dict. - If the torrent states were updated recently (less than two seconds ago, + returns status dict for the supplied torrent_ids async + If the torrent states were updated recently (less than 1.5 seconds ago, post_torrent_updates is not called. Instead the cached state is used. + + :param torrent_ids: the torrent IDs to get the status on + :type torrent_ids: list of str + :param keys: the keys to get the status on + :type keys: list of str + :param diff: if True, will return a diff of the changes since the last + call to get_status based on the session_id + :type diff: bool + + :returns: a status dictionary for the equested torrents. + :rtype: dict + """ d = Deferred() now = time.time() - # Less than two seconds since last time the torrent states were updated - if self.state_update_alert_start and \ - (now - self.state_update_alert_start) < 2: + # If last update was recent, use cached data instead of request updates from libtorrent + if (now - self.last_state_update_alert_ts) < 1.5: reactor.callLater(0, self.handle_torrents_status_callback, (d, torrent_ids, keys, diff)) else: # Ask libtorrent for status update diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index e76e188d5..d89880782 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -202,8 +202,6 @@ class StatusBar(component.Component): # Get some config values client.core.get_config_values(["max_connections_global", "max_download_speed", "max_upload_speed", "dht"]).addCallback(update_config_values) - # Called from update a few milliseconds later - #self.send_status_request() def stop(self): # When stopped, we just show the not connected thingy diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index 7f957971c..33ad514fb 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -176,8 +176,6 @@ class SystemTray(component.Component): self._on_max_download_speed(configs["max_download_speed"]) self._on_max_upload_speed(configs["max_upload_speed"]) client.core.get_config_values(["max_download_speed", "max_upload_speed"]).addCallback(update_config_values) - # This is called from update a few milliseconds later, so this line can be deleted - #self.send_status_request() def start(self): self.__start() diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py index 828e60758..676dfabb7 100644 --- a/deluge/ui/sessionproxy.py +++ b/deluge/ui/sessionproxy.py @@ -47,10 +47,8 @@ class SessionProxy(component.Component): The SessionProxy component is used to cache session information client-side to reduce the number of RPCs needed to provide a rich user interface. - On start-up it will query the Core for a full status of all the torrents in - the session. After that point, it will query the Core for only changes in - the status of the torrents and will try to satisfy client requests from the - cache. + It will query the Core for only changes in the status of the torrents + and will try to satisfy client requests from the cache. """ def __init__(self): @@ -78,21 +76,6 @@ class SessionProxy(component.Component): # so that upcoming queries or status updates don't throw errors. self.torrents.setdefault(torrent_id, [time.time(), {}]) self.cache_times.setdefault(torrent_id, {}) - # These initial keys are the ones used for the visible columns(by - # default) on the GTK UI torrent view. If either the console-ui - # or the web-ui needs additional keys, add them here; - # There's NO need to fetch every bit of status information from - # core if it's not going to be used. Additional status fields - # will be queried later, for example, when viewing the status tab - # of a torrent. - #inital_keys = [ - # 'queue', 'state', 'name', 'total_wanted', 'progress', 'state', - # 'download_payload_rate', 'upload_payload_rate', 'eta', 'owner' - #] - - #The the torrents status will be requested by torrentview, so this - #only causes a second request for the same data withing a few miliseconds - #self.get_torrents_status({'id': torrent_ids}, inital_keys) return client.core.get_session_state().addCallback(on_get_session_state) def stop(self): From fbeea9159e35269fff7c6a30bb26bc5606eb7564 Mon Sep 17 00:00:00 2001 From: bendikro Date: Wed, 16 Jan 2013 00:08:00 +0100 Subject: [PATCH 3/4] Removed debug print --- deluge/core/torrentmanager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 1b86be2ce..45274e699 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -1158,9 +1158,8 @@ class TorrentManager(component.Component): # Get the torrent status for each torrent_id for torrent_id in torrent_ids: if not torrent_id in self.torrents: - # This happens sometimes, but why? Unfixed bug? - # Without this test it gives a KeyError. - print "Missing torrent id:", torrent_id + # The torrent_id does not exist in the dict. + # Could be the clients cache (sessionproxy) isn't up to speed. del status_dict[torrent_id] else: status_dict[torrent_id] = self.torrents[torrent_id].get_status(torrent_keys, diff) From fbdda1b3a5db6203f1993a03eed92ca489903aab Mon Sep 17 00:00:00 2001 From: bendikro Date: Fri, 25 Jan 2013 20:13:28 +0100 Subject: [PATCH 4/4] Fixed bug in torrentmanager.separate_keys If none of the torrent_id's were present in self.torrents, None would be returned. --- deluge/core/torrentmanager.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 45274e699..3efc9a400 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -1127,14 +1127,14 @@ class TorrentManager(component.Component): """Separates the input keys into keys for the Torrent class and keys for plugins. """ - if not self.torrents: - return [], [] - for torrent_id in torrent_ids: - if torrent_id in self.torrents: - status_keys = self.torrents[torrent_id].status_funcs.keys() - leftover_keys = list(set(keys) - set(status_keys)) - torrent_keys = list(set(keys) - set(leftover_keys)) - return torrent_keys, leftover_keys + if self.torrents: + for torrent_id in torrent_ids: + if torrent_id in self.torrents: + status_keys = self.torrents[torrent_id].status_funcs.keys() + leftover_keys = list(set(keys) - set(status_keys)) + torrent_keys = list(set(keys) - set(leftover_keys)) + return torrent_keys, leftover_keys + return [], [] def on_alert_state_update(self, alert): log.debug("on_status_notification: %s", alert.message())