Merge remote-tracking branch 'bro/master-daemon-optimize-speed'

Conflicts:
	deluge/core/alertmanager.py
	deluge/core/torrentmanager.py
This commit is contained in:
Chase Sterling 2013-02-10 17:30:25 -05:00
commit 8c106ce8c4
8 changed files with 392 additions and 269 deletions

View File

@ -67,12 +67,12 @@ class AlertManager(component.Component):
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {} self.handlers = {}
self.delayed_calls = [] self.delayed_calls = []
self.wait_on_handler = False
def update(self): def update(self):
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()] 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): def stop(self):
for dc in self.delayed_calls: for dc in self.delayed_calls:
@ -123,6 +123,7 @@ class AlertManager(component.Component):
while alert is not None: while alert is not None:
alert_type = type(alert).__name__ alert_type = type(alert).__name__
# Display the alert message # Display the alert message
if log.isEnabledFor(logging.DEBUG):
log.debug("%s: %s", alert_type, decode_string(alert.message())) log.debug("%s: %s", alert_type, decode_string(alert.message()))
# Call any handlers for this alert type # Call any handlers for this alert type
if alert_type in self.handlers: if alert_type in self.handlers:

View File

@ -427,21 +427,25 @@ class Core(component.Component):
for torrent_id in torrent_ids: for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].resume() self.torrentmanager[torrent_id].resume()
@export def create_torrent_status(self, torrent_id, torrent_keys, plugin_keys, diff=False, update=False):
def get_torrent_status(self, torrent_id, keys, diff=False):
# Build the status dictionary
try: try:
status = self.torrentmanager[torrent_id].get_status(keys, diff) status = self.torrentmanager[torrent_id].get_status(torrent_keys, diff, update=update)
except KeyError: except KeyError:
import traceback
traceback.print_exc()
# Torrent was probaly removed meanwhile # Torrent was probaly removed meanwhile
return {} return {}
# Get the leftover fields and ask the plugin manager to fill them # Ask the plugin manager to fill in the plugin keys
leftover_fields = list(set(keys) - set(status.keys())) if len(plugin_keys) > 0:
if len(leftover_fields) > 0: status.update(self.pluginmanager.get_status(torrent_id, plugin_keys))
status.update(self.pluginmanager.get_status(torrent_id, leftover_fields))
return status 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 @export
def get_torrents_status(self, filter_dict, keys, diff=False): def get_torrents_status(self, filter_dict, keys, diff=False):
""" """
@ -449,12 +453,17 @@ class Core(component.Component):
""" """
torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict) torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict)
status_dict = {}.fromkeys(torrent_ids) status_dict = {}.fromkeys(torrent_ids)
d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=False)
# Get the torrent status for each torrent_id def add_plugin_fields(args):
for torrent_id in torrent_ids: status_dict, plugin_keys = args
status_dict[torrent_id] = self.get_torrent_status(torrent_id, keys, diff) # 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 return status_dict
d.addCallback(add_plugin_fields)
return d
@export @export
def get_filter_tree(self , show_zero_hits=True, hide_cat=None): def get_filter_tree(self , show_zero_hits=True, hide_cat=None):

View File

@ -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 # Check all the torrent's tracker_status for 'Error:' and only return torrent_ids
# that have this substring in their tracker_status # that have this substring in their tracker_status
for torrent_id in torrent_ids: 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) filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids return filtered_torrent_ids
class FilterManager(component.Component): class FilterManager(component.Component):
@ -192,13 +191,11 @@ class FilterManager(component.Component):
#leftover filter arguments: #leftover filter arguments:
#default filter on status fields. #default filter on status fields.
status_func = self.core.get_torrent_status #premature optimalisation..
for torrent_id in list(torrent_ids): 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(): for field, values in filter_dict.iteritems():
if (not status[field] in values) and torrent_id in torrent_ids: if (not status[field] in values) and torrent_id in torrent_ids:
torrent_ids.remove(torrent_id) torrent_ids.remove(torrent_id)
return torrent_ids return torrent_ids
def get_filter_tree(self, show_zero_hits=True, hide_cat=None): def get_filter_tree(self, show_zero_hits=True, hide_cat=None):
@ -207,17 +204,16 @@ class FilterManager(component.Component):
for use in sidebar. for use in sidebar.
""" """
torrent_ids = self.torrents.get_torrent_list() torrent_ids = self.torrents.get_torrent_list()
status_func = self.core.get_torrent_status #premature optimalisation..
tree_keys = list(self.tree_fields.keys()) tree_keys = list(self.tree_fields.keys())
if hide_cat: if hide_cat:
for cat in hide_cat: for cat in hide_cat:
tree_keys.remove(cat) tree_keys.remove(cat)
torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids)
items = dict((field, self.tree_fields[field]()) for field in tree_keys) items = dict((field, self.tree_fields[field]()) for field in tree_keys)
#count status fields.
for torrent_id in list(torrent_ids): 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: for field in tree_keys:
value = status[field] value = status[field]
items[field][value] = items[field].get(value, 0) + 1 items[field][value] = items[field].get(value, 0) + 1
@ -264,9 +260,8 @@ class FilterManager(component.Component):
del self.tree_fields[field] del self.tree_fields[field]
def filter_state_active(self, torrent_ids): def filter_state_active(self, torrent_ids):
get_status = self.core.get_torrent_status
for torrent_id in list(torrent_ids): 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"]: if status["download_payload_rate"] or status["upload_payload_rate"]:
pass #ok pass #ok
else: else:

View File

@ -109,10 +109,14 @@ class Torrent(object):
"""Torrent holds information about torrents added to the libtorrent session. """Torrent holds information about torrents added to the libtorrent session.
""" """
def __init__(self, handle, options, state=None, filename=None, magnet=None, owner=None): 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 # Get the core config
self.config = ConfigManager("core.conf") self.config = ConfigManager("core.conf")
self.rpcserver = component.get("RPCServer") self.rpcserver = component.get("RPCServer")
# This dict holds previous status dicts returned for this torrent # This dict holds previous status dicts returned for this torrent
@ -124,8 +128,6 @@ class Torrent(object):
# Set the libtorrent handle # Set the libtorrent handle
self.handle = 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 # 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 # This is so we can send one folder_renamed signal instead of multiple
@ -151,6 +153,9 @@ class Torrent(object):
except RuntimeError: except RuntimeError:
self.torrent_info = None 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 # Default total_uploaded to 0, this may be changed by the state
self.total_uploaded = 0 self.total_uploaded = 0
@ -187,9 +192,11 @@ class Torrent(object):
self.statusmsg = "OK" self.statusmsg = "OK"
# The torrents state # 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 = "" self.tracker_status = ""
# This gets updated when get_tracker_host is called # This gets updated when get_tracker_host is called
@ -217,8 +224,18 @@ class Torrent(object):
self.forcing_recheck = False self.forcing_recheck = False
self.forcing_recheck_paused = 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.") 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 ## ## Options methods ##
def set_options(self, options): def set_options(self, options):
OPTIONS_FUNCS = { OPTIONS_FUNCS = {
@ -248,7 +265,7 @@ class Torrent(object):
return self.options return self.options
def get_name(self): 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] name = self.torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0]
if not name: if not name:
name = self.torrent_info.name() name = self.torrent_info.name()
@ -306,7 +323,7 @@ class Torrent(object):
# reset all the piece priorities # reset all the piece priorities
self.set_file_priorities(self.options["file_priorities"]) self.set_file_priorities(self.options["file_priorities"])
return return
if not self.handle.has_metadata(): if not self.has_metadata():
return return
if self.options["compact_allocation"]: if self.options["compact_allocation"]:
log.debug("Setting first/last priority with compact " 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 # A list of priorities for each piece in the torrent
priorities = self.handle.piece_priorities() priorities = self.handle.piece_priorities()
prioritized_pieces = [] prioritized_pieces = []
ti = self.handle.get_torrent_info() ti = self.torrent_info
for i in range(ti.num_files()): for i in range(ti.num_files()):
f = ti.file_at(i) f = ti.file_at(i)
two_percent_bytes = int(0.02 * f.size) two_percent_bytes = int(0.02 * f.size)
@ -366,7 +383,9 @@ class Torrent(object):
self.options["move_completed_path"] = move_completed_path self.options["move_completed_path"] = move_completed_path
def set_file_priorities(self, file_priorities): 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") log.debug("file_priorities len != num_files")
self.options["file_priorities"] = self.handle.file_priorities() self.options["file_priorities"] = self.handle.file_priorities()
return return
@ -376,6 +395,7 @@ class Torrent(object):
self.options["file_priorities"] = self.handle.file_priorities() self.options["file_priorities"] = self.handle.file_priorities()
return return
if log.isEnabledFor(logging.DEBUG):
log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities) log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
self.handle.prioritize_files(file_priorities) self.handle.prioritize_files(file_priorities)
@ -390,9 +410,9 @@ class Torrent(object):
self.update_state() self.update_state()
break 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() 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 # Set the first/last priorities if needed
if self.options["prioritize_first_last_pieces"]: if self.options["prioritize_first_last_pieces"]:
@ -411,7 +431,9 @@ class Torrent(object):
self.tracker_host = None self.tracker_host = None
return return
if log.isEnabledFor(logging.DEBUG):
log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
tracker_list = [] tracker_list = []
for tracker in trackers: for tracker in trackers:
@ -421,8 +443,9 @@ class Torrent(object):
self.handle.replace_trackers(tracker_list) self.handle.replace_trackers(tracker_list)
# Print out the trackers # Print out the trackers
#for t in self.handle.trackers(): if log.isEnabledFor(logging.DEBUG):
# log.debug("tier: %s tracker: %s", t["tier"], t["url"]) for t in self.handle.trackers():
log.debug("tier: %s tracker: %s", t["tier"], t["url"])
# Set the tracker list in the torrent object # Set the tracker list in the torrent object
self.trackers = trackers self.trackers = trackers
if len(trackers) > 0: if len(trackers) > 0:
@ -444,7 +467,8 @@ class Torrent(object):
"""Updates the state based on what libtorrent's state for the torrent is""" """Updates the state based on what libtorrent's state for the torrent is"""
# Set the initial state based on the lt state # Set the initial state based on the lt state
LTSTATE = deluge.common.LT_TORRENT_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 # Set self.state to the ltstate right away just incase we don't hit some
# of the logic below # of the logic below
@ -453,21 +477,26 @@ class Torrent(object):
else: else:
self.state = str(ltstate) self.state = str(ltstate)
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("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
log.debug("session.is_paused: %s", component.get("Core").session.is_paused()) log.debug("session.is_paused: %s", session_is_paused)
# First we check for an error from libtorrent, and set the state to that # First we check for an error from libtorrent, and set the state to that
# if any occurred. # if any occurred.
if len(self.handle.status().error) > 0: if len(status.error) > 0:
# This is an error'd torrent # This is an error'd torrent
self.state = "Error" self.state = "Error"
self.set_status_message(self.handle.status().error) self.set_status_message(status.error)
if self.handle.is_paused(): if handle_is_paused:
self.handle.auto_managed(False) self.handle.auto_managed(False)
return return
if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]: if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]:
if self.handle.is_paused(): if handle_is_paused:
self.state = "Paused" self.state = "Paused"
else: else:
self.state = "Checking" self.state = "Checking"
@ -479,9 +508,9 @@ class Torrent(object):
elif ltstate == LTSTATE["Allocating"]: elif ltstate == LTSTATE["Allocating"]:
self.state = "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" 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" self.state = "Paused"
def set_state(self, state): def set_state(self, state):
@ -498,11 +527,7 @@ class Torrent(object):
def get_eta(self): def get_eta(self):
"""Returns the ETA in seconds for this torrent""" """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"]: if self.is_finished and self.options["stop_at_ratio"]:
# We're a seed, so calculate the time to the 'stop_share_ratio' # We're a seed, so calculate the time to the 'stop_share_ratio'
if not status.upload_payload_rate: if not status.upload_payload_rate:
@ -524,32 +549,21 @@ class Torrent(object):
def get_ratio(self): def get_ratio(self):
"""Returns the ratio for this torrent""" """Returns the ratio for this torrent"""
if self.status == None: if self.status.total_done > 0:
status = self.handle.status()
else:
status = self.status
if status.total_done > 0:
# We use 'total_done' if the downloaded value is 0 # We use 'total_done' if the downloaded value is 0
downloaded = status.total_done downloaded = self.status.total_done
else: else:
# Return -1.0 to signify infinity # Return -1.0 to signify infinity
return -1.0 return -1.0
return float(status.all_time_upload) / float(downloaded) return float(self.status.all_time_upload) / float(downloaded)
def get_files(self): def get_files(self):
"""Returns a list of files this torrent contains""" """Returns a list of files this torrent contains"""
if self.torrent_info == None and self.handle.has_metadata(): if not self.has_metadata():
torrent_info = self.handle.get_torrent_info()
else:
torrent_info = self.torrent_info
if not torrent_info:
return [] return []
ret = [] ret = []
files = torrent_info.files() files = self.torrent_info.files()
for index, file in enumerate(files): for index, file in enumerate(files):
ret.append({ ret.append({
'index': index, 'index': index,
@ -599,7 +613,7 @@ class Torrent(object):
def get_file_progress(self): def get_file_progress(self):
"""Returns the file progress as a list of floats.. 0.0 -> 1.0""" """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 return 0.0
file_progress = self.handle.file_progress() file_progress = self.handle.file_progress()
@ -618,9 +632,6 @@ class Torrent(object):
if self.tracker_host: if self.tracker_host:
return self.tracker_host return self.tracker_host
if not self.status:
self.status = self.handle.status()
tracker = self.status.current_tracker tracker = self.status.current_tracker
if not tracker and self.trackers: if not tracker and self.trackers:
tracker = self.trackers[0]["url"] tracker = self.trackers[0]["url"]
@ -659,7 +670,7 @@ class Torrent(object):
self.calculate_last_seen_complete() self.calculate_last_seen_complete()
return self._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 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 :param diff: if True, will return a diff of the changes since the last
call to get_status based on the session_id call to get_status based on the session_id
:type diff: bool :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 :returns: a dictionary of the status keys and their values
:rtype: dict :rtype: dict
""" """
if update:
self.update_status(self.handle.status())
# Create the full dictionary if not keys:
self.status = self.handle.status() keys = self.status_funcs.keys()
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 ""
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 = {} status_dict = {}
if len(keys) == 0:
status_dict = full_status
for key in fns:
status_dict[key] = fns[key]()
else:
for key in keys: for key in keys:
if key in full_status: status_dict[key] = self.status_funcs[key]()
status_dict[key] = full_status[key]
elif key in fns:
status_dict[key] = fns[key]()
session_id = self.rpcserver.get_session_id()
if diff: if diff:
session_id = self.rpcserver.get_session_id()
if session_id in self.prev_status: if session_id in self.prev_status:
# We have a previous status dict, so lets make a diff # We have a previous status dict, so lets make a diff
status_diff = {} status_diff = {}
@ -831,6 +718,157 @@ class Torrent(object):
return status_dict 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): def apply_options(self):
"""Applies the per-torrent options that are set.""" """Applies the per-torrent options that are set."""
self.handle.set_max_connections(self.max_connections) self.handle.set_max_connections(self.max_connections)
@ -943,7 +981,6 @@ class Torrent(object):
self.torrent_id) self.torrent_id)
log.debug("Writing torrent file: %s", path) log.debug("Writing torrent file: %s", path)
try: try:
self.torrent_info = self.handle.get_torrent_info()
# Regenerate the file priorities # Regenerate the file priorities
self.set_file_priorities([]) self.set_file_priorities([])
md = lt.bdecode(self.torrent_info.metadata()) md = lt.bdecode(self.torrent_info.metadata())
@ -1116,7 +1153,7 @@ class Torrent(object):
pieces[peer_info.downloading_piece_index] = 2 pieces[peer_info.downloading_piece_index] = 2
# Now, the rest of the pieces # 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: if idx in pieces:
# Piece beeing downloaded, handled above # Piece beeing downloaded, handled above
continue continue

View File

@ -41,9 +41,11 @@ import os
import shutil import shutil
import operator import operator
import logging import logging
import time
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.internet.defer import Deferred, DeferredList from twisted.internet.defer import Deferred, DeferredList
from twisted.internet import reactor
from deluge._libtorrent import lt from deluge._libtorrent import lt
@ -157,6 +159,10 @@ class TorrentManager(component.Component):
# Keeps track of resume data # Keeps track of resume data
self.resume_data = {} self.resume_data = {}
self.torrents_status_requests = []
self.status_dict = {}
self.last_state_update_alert_ts = 0
# Register set functions # Register set functions
self.config.register_set_function("max_connections_per_torrent", self.config.register_set_function("max_connections_per_torrent",
self.on_set_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.on_alert_file_error)
self.alerts.register_handler("file_completed_alert", self.alerts.register_handler("file_completed_alert",
self.on_alert_file_completed) self.on_alert_file_completed)
self.alerts.register_handler("state_update_alert",
self.on_alert_state_update)
def start(self): def start(self):
# Get the pluginmanager reference # Get the pluginmanager reference
@ -286,6 +294,7 @@ class TorrentManager(component.Component):
torrent_info = None torrent_info = None
# Get the torrent data from the torrent file # Get the torrent data from the torrent file
try: try:
if log.isEnabledFor(logging.DEBUG):
log.debug("Attempting to create torrent_info from %s", filepath) log.debug("Attempting to create torrent_info from %s", filepath)
_file = open(filepath, "rb") _file = open(filepath, "rb")
torrent_info = lt.torrent_info(lt.bdecode(_file.read())) torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
@ -321,7 +330,6 @@ class TorrentManager(component.Component):
def add(self, torrent_info=None, state=None, options=None, save_state=True, def add(self, torrent_info=None, state=None, options=None, save_state=True,
filedump=None, filename=None, magnet=None, resume_data=None, owner=None): filedump=None, filename=None, magnet=None, resume_data=None, owner=None):
"""Add a torrent to the manager and returns it's torrent_id""" """Add a torrent to the manager and returns it's torrent_id"""
if owner is None: if owner is None:
owner = component.get("RPCServer").get_session_user() owner = component.get("RPCServer").get_session_user()
if not owner: if not owner:
@ -331,7 +339,6 @@ class TorrentManager(component.Component):
log.debug("You must specify a valid torrent_info, torrent state or magnet.") log.debug("You must specify a valid torrent_info, torrent state or magnet.")
return return
log.debug("torrentmanager.add")
add_torrent_params = {} add_torrent_params = {}
if filedump is not None: if filedump is not None:
@ -440,7 +447,7 @@ class TorrentManager(component.Component):
add_torrent_params["ti"] = torrent_info add_torrent_params["ti"] = torrent_info
#log.info("Adding torrent: %s", filename) if log.isEnabledFor(logging.DEBUG):
log.debug("options: %s", options) log.debug("options: %s", options)
# Set the right storage_mode # Set the right storage_mode
@ -475,6 +482,7 @@ class TorrentManager(component.Component):
component.resume("AlertManager") component.resume("AlertManager")
return return
if log.isEnabledFor(logging.DEBUG):
log.debug("handle id: %s", str(handle.info_hash())) log.debug("handle id: %s", str(handle.info_hash()))
# Set auto_managed to False because the torrent is paused # Set auto_managed to False because the torrent is paused
handle.auto_managed(False) handle.auto_managed(False)
@ -486,6 +494,7 @@ class TorrentManager(component.Component):
if not account_exists: if not account_exists:
owner = 'localclient' owner = 'localclient'
torrent = Torrent(handle, options, state, filename, magnet, owner) torrent = Torrent(handle, options, state, filename, magnet, owner)
# Add the torrent object to the dictionary # Add the torrent object to the dictionary
self.torrents[torrent.torrent_id] = torrent self.torrents[torrent.torrent_id] = torrent
if self.config["queue_new_to_top"]: if self.config["queue_new_to_top"]:
@ -532,10 +541,14 @@ class TorrentManager(component.Component):
component.get("EventManager").emit( component.get("EventManager").emit(
TorrentAddedEvent(torrent.torrent_id, from_state) TorrentAddedEvent(torrent.torrent_id, from_state)
) )
log.info("Torrent %s from user \"%s\" %s",
torrent.get_status(["name"])["name"], if log.isEnabledFor(logging.INFO):
torrent.get_status(["owner"])["owner"], name_and_owner = torrent.get_status(["name", "owner"])
(from_state and "loaded" or "added")) 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 return torrent.torrent_id
def load_torrent(self, torrent_id): def load_torrent(self, torrent_id):
@ -647,28 +660,35 @@ class TorrentManager(component.Component):
# Try to use an old state # Try to use an old state
try: try:
if len(state.torrents) > 0:
state_tmp = TorrentState() state_tmp = TorrentState()
if dir(state.torrents[0]) != dir(state_tmp): if dir(state.torrents[0]) != dir(state_tmp):
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))): for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
for s in state.torrents: for s in state.torrents:
setattr(s, attr, getattr(state_tmp, attr, None)) setattr(s, attr, getattr(state_tmp, attr, None))
except Exception, e: 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 # Reorder the state.torrents list to add torrents in the correct queue
# order. # order.
state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"]) state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"])
resume_data = self.load_resume_data_file() 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: for torrent_state in state.torrents:
try: try:
self.add(state=torrent_state, save_state=False, self.add(state=torrent_state, save_state=False,
resume_data=resume_data.get(torrent_state.torrent_id)) resume_data=resume_data.get(torrent_state.torrent_id))
except AttributeError, e: except AttributeError, e:
log.error("Torrent state file is either corrupt or incompatible! %s", e) log.error("Torrent state file is either corrupt or incompatible! %s", e)
import traceback
traceback.print_exc()
break break
self.alerts.wait_on_handler = False
if lt.version_minor < 16: if lt.version_minor < 16:
log.debug("libtorrent version is lower than 0.16. Start looping " log.debug("libtorrent version is lower than 0.16. Start looping "
@ -914,6 +934,7 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id, )) self.save_resume_data((torrent_id, ))
def on_alert_torrent_paused(self, alert): def on_alert_torrent_paused(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_paused") log.debug("on_alert_torrent_paused")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
@ -931,6 +952,7 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id,)) self.save_resume_data((torrent_id,))
def on_alert_torrent_checked(self, alert): def on_alert_torrent_checked(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_checked") log.debug("on_alert_torrent_checked")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
@ -948,6 +970,7 @@ class TorrentManager(component.Component):
torrent.update_state() torrent.update_state()
def on_alert_tracker_reply(self, alert): def on_alert_tracker_reply(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_reply: %s", decode_string(alert.message())) log.debug("on_alert_tracker_reply: %s", decode_string(alert.message()))
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
@ -964,6 +987,7 @@ class TorrentManager(component.Component):
torrent.scrape_tracker() torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert): def on_alert_tracker_announce(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_announce") log.debug("on_alert_tracker_announce")
try: try:
torrent = self.torrents[str(alert.handle.info_hash())] torrent = self.torrents[str(alert.handle.info_hash())]
@ -1016,6 +1040,7 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentResumedEvent(torrent_id)) component.get("EventManager").emit(TorrentResumedEvent(torrent_id))
def on_alert_state_changed(self, alert): def on_alert_state_changed(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_state_changed") log.debug("on_alert_state_changed")
try: try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
@ -1036,6 +1061,7 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state)) component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
def on_alert_save_resume_data(self, alert): def on_alert_save_resume_data(self, alert):
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_save_resume_data") log.debug("on_alert_save_resume_data")
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
@ -1096,3 +1122,75 @@ class TorrentManager(component.Component):
return return
component.get("EventManager").emit( component.get("EventManager").emit(
TorrentFileCompletedEvent(torrent_id, alert.index)) 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

View File

@ -194,16 +194,14 @@ class StatusBar(component.Component):
self.health = False 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 # Get some config values
client.core.get_config_value( client.core.get_config_values(["max_connections_global", "max_download_speed",
"max_connections_global").addCallback(self._on_max_connections_global) "max_upload_speed", "dht"]).addCallback(update_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)
client.core.get_config_value("dht").addCallback(self._on_dht)
self.send_status_request()
def stop(self): def stop(self):
# When stopped, we just show the not connected thingy # When stopped, we just show the not connected thingy

View File

@ -172,11 +172,10 @@ class SystemTray(component.Component):
self.build_tray_bwsetsubmenu() self.build_tray_bwsetsubmenu()
# Get some config values # Get some config values
client.core.get_config_value( def update_config_values(configs):
"max_download_speed").addCallback(self._on_max_download_speed) self._on_max_download_speed(configs["max_download_speed"])
client.core.get_config_value( self._on_max_upload_speed(configs["max_upload_speed"])
"max_upload_speed").addCallback(self._on_max_upload_speed) client.core.get_config_values(["max_download_speed", "max_upload_speed"]).addCallback(update_config_values)
self.send_status_request()
def start(self): def start(self):
self.__start() self.__start()

View File

@ -47,10 +47,8 @@ class SessionProxy(component.Component):
The SessionProxy component is used to cache session information client-side The SessionProxy component is used to cache session information client-side
to reduce the number of RPCs needed to provide a rich user interface. 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 It will query the Core for only changes in the status of the torrents
the session. After that point, it will query the Core for only changes in and will try to satisfy client requests from the cache.
the status of the torrents and will try to satisfy client requests from the
cache.
""" """
def __init__(self): def __init__(self):
@ -78,18 +76,6 @@ class SessionProxy(component.Component):
# so that upcoming queries or status updates don't throw errors. # so that upcoming queries or status updates don't throw errors.
self.torrents.setdefault(torrent_id, [time.time(), {}]) self.torrents.setdefault(torrent_id, [time.time(), {}])
self.cache_times.setdefault(torrent_id, {}) 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) return client.core.get_session_state().addCallback(on_get_session_state)
def stop(self): def stop(self):