summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChase Sterling <chase.sterling@gmail.com>2013-02-10 17:30:25 -0500
committerChase Sterling <chase.sterling@gmail.com>2013-02-10 17:30:25 -0500
commit8c106ce8c4c0794ddd63e8e8f98b097221a56a52 (patch)
tree45944ea2333b4da97045ccefed2ed5e1de3ac321
parentb4f5e78a775503a5501e2eba7eee73dfa0e1955a (diff)
parentfbdda1b3a5db6203f1993a03eed92ca489903aab (diff)
downloaddeluge-8c106ce8c4c0794ddd63e8e8f98b097221a56a52.tar.gz
deluge-8c106ce8c4c0794ddd63e8e8f98b097221a56a52.tar.bz2
deluge-8c106ce8c4c0794ddd63e8e8f98b097221a56a52.zip
Merge remote-tracking branch 'bro/master-daemon-optimize-speed'
Conflicts: deluge/core/alertmanager.py deluge/core/torrentmanager.py
-rw-r--r--deluge/core/alertmanager.py7
-rw-r--r--deluge/core/core.py37
-rw-r--r--deluge/core/filtermanager.py17
-rw-r--r--deluge/core/torrent.py415
-rw-r--r--deluge/core/torrentmanager.py144
-rw-r--r--deluge/ui/gtkui/statusbar.py16
-rw-r--r--deluge/ui/gtkui/systemtray.py9
-rw-r--r--deluge/ui/sessionproxy.py18
8 files changed, 393 insertions, 270 deletions
diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py
index a4464eb8c..bcc60fedf 100644
--- a/deluge/core/alertmanager.py
+++ b/deluge/core/alertmanager.py
@@ -67,12 +67,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:
@@ -123,7 +123,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, decode_string(alert.message()))
+ if log.isEnabledFor(logging.DEBUG):
+ log.debug("%s: %s", alert_type, decode_string(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..4a1fa693d 100644
--- a/deluge/core/core.py
+++ b/deluge/core/core.py
@@ -427,34 +427,43 @@ 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=False):
try:
- status = self.torrentmanager[torrent_id].get_status(keys, diff)
+ status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update)
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):
"""
returns all torrents , optionally filtered by filter_dict.
"""
torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict)
status_dict = {}.fromkeys(torrent_ids)
-
- # 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
+ d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=False)
+
+ 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..c9545072c 100644
--- a/deluge/core/filtermanager.py
+++ b/deluge/core/filtermanager.py
@@ -113,9 +113,8 @@ def tracker_error_filter(torrent_ids, values):
# 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].get_status(["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].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)
-
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) #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].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 c5fd81955..ed9c27bfb 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,17 @@ class Torrent(object):
self.forcing_recheck = False
self.forcing_recheck_paused = False
- log.debug("Torrent object created.")
+ self.update_status(self.handle.status())
+ self._create_status_funcs()
+
+ 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 +265,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 +323,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 +332,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 +383,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 +395,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 +410,9 @@ class Torrent(object):
self.update_state()
break
+ # 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()
- 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 +431,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 +443,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 +467,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 +477,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 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"
@@ -479,9 +508,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 +527,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 +549,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 +613,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 +632,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"]
@@ -659,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
@@ -668,151 +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
"""
+ if update:
+ self.update_status(self.handle.status())
- # Create the full dictionary
- self.status = self.handle.status()
- if self.handle.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 ""
+ if not keys:
+ keys = self.status_funcs.keys()
- 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
- }
-
- # 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]()
+ 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 = {}
@@ -831,6 +718,157 @@ 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:
+ 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 +944,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 +981,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 +1153,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 9a8d8a59c..6dafd1939 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.last_state_update_alert_ts = 0
+
# 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.on_alert_state_update)
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:
@@ -440,8 +447,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"]:
@@ -475,7 +482,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
@@ -486,6 +494,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"]:
@@ -532,10 +541,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):
@@ -647,28 +660,35 @@ 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)
+ 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.
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 "
@@ -914,7 +934,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())
@@ -931,7 +952,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:
@@ -948,7 +970,8 @@ class TorrentManager(component.Component):
torrent.update_state()
def on_alert_tracker_reply(self, alert):
- log.debug("on_alert_tracker_reply: %s", decode_string(alert.message()))
+ if log.isEnabledFor(logging.DEBUG):
+ log.debug("on_alert_tracker_reply: %s", decode_string(alert.message()))
try:
torrent = self.torrents[str(alert.handle.info_hash())]
except:
@@ -964,7 +987,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 +1040,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 +1061,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 +1122,75 @@ 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 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())
+ self.last_state_update_alert_ts = time.time()
+
+ 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):
+ """
+ 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)
+
+ # Get the torrent status for each torrent_id
+ for torrent_id in torrent_ids:
+ if not torrent_id in self.torrents:
+ # 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)
+ self.status_dict = status_dict
+ d.callback((status_dict, plugin_keys))
+
+ def torrents_status_update(self, torrent_ids, keys, diff=False):
+ """
+ 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()
+ # 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
+ 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..d89880782 100644
--- a/deluge/ui/gtkui/statusbar.py
+++ b/deluge/ui/gtkui/statusbar.py
@@ -194,16 +194,14 @@ 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)
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..33ad514fb 100644
--- a/deluge/ui/gtkui/systemtray.py
+++ b/deluge/ui/gtkui/systemtray.py
@@ -172,11 +172,10 @@ 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)
def start(self):
self.__start()
diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py
index b7acd890f..0b5234704 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,18 +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'
- ]
- self.get_torrents_status({'id': torrent_ids}, inital_keys)
return client.core.get_session_state().addCallback(on_get_session_state)
def stop(self):