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