Optimizations to daemon and rpc messages.

* Implemented torrent updates with libtorrent post_torrent_updates.
  This required some changes to how core/torrenthandler
  handles get_torrent_status
* Reworked how torrent.py handles torrent info
* Removed some unneeded RPC message requests from client.
* Added tests for some expensive log prints.
This commit is contained in:
bendikro 2013-01-14 03:14:28 +01:00
parent 8cb55983bb
commit 5cd86aa5bc
8 changed files with 378 additions and 254 deletions

View File

@ -66,12 +66,12 @@ class AlertManager(component.Component):
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {}
self.delayed_calls = []
self.wait_on_handler = False
def update(self):
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
self.handle_alerts()
self.handle_alerts(wait=self.wait_on_handler)
def stop(self):
for dc in self.delayed_calls:
@ -122,7 +122,8 @@ class AlertManager(component.Component):
while alert is not None:
alert_type = type(alert).__name__
# Display the alert message
log.debug("%s: %s", alert_type, alert.message())
if log.isEnabledFor(logging.DEBUG):
log.debug("%s: %s", alert_type, alert.message())
# Call any handlers for this alert type
if alert_type in self.handlers:
for handler in self.handlers[alert_type]:

View File

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

View File

@ -106,16 +106,15 @@ def tracker_error_filter(torrent_ids, values):
# If this is a tracker_host, then we need to filter on it
if values[0] != "Error":
for torrent_id in torrent_ids:
if values[0] == tm[torrent_id].get_status(["tracker_host"])["tracker_host"]:
if values[0] == tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
# Check all the torrent's tracker_status for 'Error:' and only return torrent_ids
# that have this substring in their tracker_status
for torrent_id in torrent_ids:
if _("Error") + ":" in tm[torrent_id].get_status(["tracker_status"])["tracker_status"]:
if _("Error") + ":" in tm[torrent_id].create_status_dict(["tracker_host"])["tracker_host"]:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
class FilterManager(component.Component):
@ -192,13 +191,11 @@ class FilterManager(component.Component):
#leftover filter arguments:
#default filter on status fields.
status_func = self.core.get_torrent_status #premature optimalisation..
for torrent_id in list(torrent_ids):
status = status_func(torrent_id, filter_dict.keys()) #status={key:value}
status = self.torrents[torrent_id].create_status_dict(filter_dict.keys()) #status={key:value}
for field, values in filter_dict.iteritems():
if (not status[field] in values) and torrent_id in torrent_ids:
torrent_ids.remove(torrent_id)
return torrent_ids
def get_filter_tree(self, show_zero_hits=True, hide_cat=None):
@ -207,17 +204,16 @@ class FilterManager(component.Component):
for use in sidebar.
"""
torrent_ids = self.torrents.get_torrent_list()
status_func = self.core.get_torrent_status #premature optimalisation..
tree_keys = list(self.tree_fields.keys())
if hide_cat:
for cat in hide_cat:
tree_keys.remove(cat)
items = dict( (field, self.tree_fields[field]()) for field in tree_keys)
torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids)
items = dict((field, self.tree_fields[field]()) for field in tree_keys)
#count status fields.
for torrent_id in list(torrent_ids):
status = status_func(torrent_id, tree_keys) #status={key:value}
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys, update=False) #status={key:value}
for field in tree_keys:
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
@ -264,9 +260,8 @@ class FilterManager(component.Component):
del self.tree_fields[field]
def filter_state_active(self, torrent_ids):
get_status = self.core.get_torrent_status
for torrent_id in list(torrent_ids):
status = get_status(torrent_id, ["download_payload_rate", "upload_payload_rate"])
status = self.torrents[torrent_id].create_status_dict(["download_payload_rate", "upload_payload_rate"])
if status["download_payload_rate"] or status["upload_payload_rate"]:
pass #ok
else:

View File

@ -109,10 +109,14 @@ class Torrent(object):
"""Torrent holds information about torrents added to the libtorrent session.
"""
def __init__(self, handle, options, state=None, filename=None, magnet=None, owner=None):
log.debug("Creating torrent object %s", str(handle.info_hash()))
# Set the torrent_id for this torrent
self.torrent_id = str(handle.info_hash())
if log.isEnabledFor(logging.DEBUG):
log.debug("Creating torrent object %s", self.torrent_id)
# Get the core config
self.config = ConfigManager("core.conf")
self.rpcserver = component.get("RPCServer")
# This dict holds previous status dicts returned for this torrent
@ -124,8 +128,6 @@ class Torrent(object):
# Set the libtorrent handle
self.handle = handle
# Set the torrent_id for this torrent
self.torrent_id = str(handle.info_hash())
# Keep a list of Deferreds for file indexes we're waiting for file_rename alerts on
# This is so we can send one folder_renamed signal instead of multiple
@ -151,6 +153,9 @@ class Torrent(object):
except RuntimeError:
self.torrent_info = None
self.handle_has_metadata = None
self.status_funcs = None
# Default total_uploaded to 0, this may be changed by the state
self.total_uploaded = 0
@ -187,9 +192,11 @@ class Torrent(object):
self.statusmsg = "OK"
# The torrents state
self.update_state()
# This is only one out of 4 calls to update_state for each torrent on startup.
# This call doesn't seem to be necessary, it can probably be removed
#self.update_state()
self.state = None
# The tracker status
self.tracker_status = ""
# This gets updated when get_tracker_host is called
@ -217,7 +224,14 @@ class Torrent(object):
self.forcing_recheck = False
self.forcing_recheck_paused = False
log.debug("Torrent object created.")
if log.isEnabledFor(logging.DEBUG):
log.debug("Torrent object created.")
def has_metadata(self):
if self.handle_has_metadata:
return self.handle_has_metadata
self.handle_has_metadata = self.handle.has_metadata()
return self.handle_has_metadata
## Options methods ##
def set_options(self, options):
@ -248,7 +262,7 @@ class Torrent(object):
return self.options
def get_name(self):
if self.handle.has_metadata():
if self.has_metadata():
name = self.torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0]
if not name:
name = self.torrent_info.name()
@ -306,7 +320,7 @@ class Torrent(object):
# reset all the piece priorities
self.set_file_priorities(self.options["file_priorities"])
return
if not self.handle.has_metadata():
if not self.has_metadata():
return
if self.options["compact_allocation"]:
log.debug("Setting first/last priority with compact "
@ -315,7 +329,7 @@ class Torrent(object):
# A list of priorities for each piece in the torrent
priorities = self.handle.piece_priorities()
prioritized_pieces = []
ti = self.handle.get_torrent_info()
ti = self.torrent_info
for i in range(ti.num_files()):
f = ti.file_at(i)
two_percent_bytes = int(0.02 * f.size)
@ -366,7 +380,9 @@ class Torrent(object):
self.options["move_completed_path"] = move_completed_path
def set_file_priorities(self, file_priorities):
if len(file_priorities) != len(self.get_files()):
if not self.has_metadata():
return
if len(file_priorities) != self.ti_num_files():
log.debug("file_priorities len != num_files")
self.options["file_priorities"] = self.handle.file_priorities()
return
@ -376,7 +392,8 @@ class Torrent(object):
self.options["file_priorities"] = self.handle.file_priorities()
return
log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
if log.isEnabledFor(logging.DEBUG):
log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
self.handle.prioritize_files(file_priorities)
@ -390,9 +407,10 @@ class Torrent(object):
self.update_state()
break
self.options["file_priorities"] = self.handle.file_priorities()
if self.options["file_priorities"] != list(file_priorities):
log.warning("File priorities were not set for this torrent")
# Do we really need this?
#self.options["file_priorities"] = self.handle.file_priorities()
#if self.options["file_priorities"] != list(file_priorities):
# log.warning("File priorities were not set for this torrent")
# Set the first/last priorities if needed
if self.options["prioritize_first_last_pieces"]:
@ -411,7 +429,9 @@ class Torrent(object):
self.tracker_host = None
return
log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
if log.isEnabledFor(logging.DEBUG):
log.debug("Setting trackers for %s: %s", self.torrent_id, trackers)
tracker_list = []
for tracker in trackers:
@ -421,8 +441,9 @@ class Torrent(object):
self.handle.replace_trackers(tracker_list)
# Print out the trackers
#for t in self.handle.trackers():
# log.debug("tier: %s tracker: %s", t["tier"], t["url"])
if log.isEnabledFor(logging.DEBUG):
for t in self.handle.trackers():
log.debug("tier: %s tracker: %s", t["tier"], t["url"])
# Set the tracker list in the torrent object
self.trackers = trackers
if len(trackers) > 0:
@ -444,7 +465,8 @@ class Torrent(object):
"""Updates the state based on what libtorrent's state for the torrent is"""
# Set the initial state based on the lt state
LTSTATE = deluge.common.LT_TORRENT_STATE
ltstate = int(self.handle.status().state)
status = self.handle.status()
ltstate = int(status.state)
# Set self.state to the ltstate right away just incase we don't hit some
# of the logic below
@ -453,21 +475,26 @@ class Torrent(object):
else:
self.state = str(ltstate)
log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
log.debug("session.is_paused: %s", component.get("Core").session.is_paused())
session_is_paused = component.get("Core").session.is_paused()
is_auto_managed = self.handle.is_auto_managed()
handle_is_paused = self.handle.is_paused()
if log.isEnabledFor(logging.DEBUG):
log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
log.debug("session.is_paused: %s", session_is_paused)
# First we check for an error from libtorrent, and set the state to that
# if any occurred.
if len(self.handle.status().error) > 0:
if len(status.error) > 0:
# This is an error'd torrent
self.state = "Error"
self.set_status_message(self.handle.status().error)
if self.handle.is_paused():
self.set_status_message(status.error)
if self.handle_is_paused:
self.handle.auto_managed(False)
return
if ltstate == LTSTATE["Queued"] or ltstate == LTSTATE["Checking"]:
if self.handle.is_paused():
if self.handle_is_paused:
self.state = "Paused"
else:
self.state = "Checking"
@ -479,9 +506,9 @@ class Torrent(object):
elif ltstate == LTSTATE["Allocating"]:
self.state = "Allocating"
if self.handle.is_paused() and self.handle.is_auto_managed() and not component.get("Core").session.is_paused():
if not session_is_paused and handle_is_paused and is_auto_managed:
self.state = "Queued"
elif component.get("Core").session.is_paused() or (self.handle.is_paused() and not self.handle.is_auto_managed()):
elif session_is_paused or (handle_is_paused and not is_auto_managed):
self.state = "Paused"
def set_state(self, state):
@ -498,11 +525,7 @@ class Torrent(object):
def get_eta(self):
"""Returns the ETA in seconds for this torrent"""
if self.status == None:
status = self.handle.status()
else:
status = self.status
status = self.status
if self.is_finished and self.options["stop_at_ratio"]:
# We're a seed, so calculate the time to the 'stop_share_ratio'
if not status.upload_payload_rate:
@ -524,32 +547,21 @@ class Torrent(object):
def get_ratio(self):
"""Returns the ratio for this torrent"""
if self.status == None:
status = self.handle.status()
else:
status = self.status
if status.total_done > 0:
if self.status.total_done > 0:
# We use 'total_done' if the downloaded value is 0
downloaded = status.total_done
downloaded = self.status.total_done
else:
# Return -1.0 to signify infinity
return -1.0
return float(status.all_time_upload) / float(downloaded)
return float(self.status.all_time_upload) / float(downloaded)
def get_files(self):
"""Returns a list of files this torrent contains"""
if self.torrent_info == None and self.handle.has_metadata():
torrent_info = self.handle.get_torrent_info()
else:
torrent_info = self.torrent_info
if not torrent_info:
if not self.has_metadata():
return []
ret = []
files = torrent_info.files()
files = self.torrent_info.files()
for index, file in enumerate(files):
ret.append({
'index': index,
@ -599,7 +611,7 @@ class Torrent(object):
def get_file_progress(self):
"""Returns the file progress as a list of floats.. 0.0 -> 1.0"""
if not self.handle.has_metadata():
if not self.has_metadata():
return 0.0
file_progress = self.handle.file_progress()
@ -618,9 +630,6 @@ class Torrent(object):
if self.tracker_host:
return self.tracker_host
if not self.status:
self.status = self.handle.status()
tracker = self.status.current_tracker
if not tracker and self.trackers:
tracker = self.trackers[0]["url"]
@ -673,143 +682,96 @@ class Torrent(object):
:rtype: dict
"""
status = self.handle.status()
self.update_status(status)
return self.create_status_dict(keys)
# Create the full dictionary
self.status = self.handle.status()
if self.handle.has_metadata():
def update_status(self, status):
self.status = status
if self.torrent_info is None and self.has_metadata():
self.torrent_info = self.handle.get_torrent_info()
# Adjust progress to be 0-100 value
progress = self.status.progress * 100
# Adjust status.distributed_copies to return a non-negative value
distributed_copies = self.status.distributed_copies
if distributed_copies < 0:
distributed_copies = 0.0
# Calculate the seeds:peers ratio
if self.status.num_incomplete == 0:
# Use -1.0 to signify infinity
seeds_peers_ratio = -1.0
else:
seeds_peers_ratio = self.status.num_complete / float(self.status.num_incomplete)
full_status = {
"active_time": self.status.active_time,
"all_time_download": self.status.all_time_download,
"compact": self.options["compact_allocation"],
"distributed_copies": distributed_copies,
"download_payload_rate": self.status.download_payload_rate,
"file_priorities": self.options["file_priorities"],
"hash": self.torrent_id,
"is_auto_managed": self.options["auto_managed"],
"is_finished": self.is_finished,
"max_connections": self.options["max_connections"],
"max_download_speed": self.options["max_download_speed"],
"max_upload_slots": self.options["max_upload_slots"],
"max_upload_speed": self.options["max_upload_speed"],
"message": self.statusmsg,
"move_on_completed_path": self.options["move_completed_path"],
"move_on_completed": self.options["move_completed"],
"move_completed_path": self.options["move_completed_path"],
"move_completed": self.options["move_completed"],
"next_announce": self.status.next_announce.seconds,
"num_peers": self.status.num_peers - self.status.num_seeds,
"num_seeds": self.status.num_seeds,
"owner": self.owner,
"paused": self.status.paused,
"prioritize_first_last": self.options["prioritize_first_last_pieces"],
"sequential_download": self.options["sequential_download"],
"progress": progress,
"shared": self.options["shared"],
"remove_at_ratio": self.options["remove_at_ratio"],
"save_path": self.options["download_location"],
"seeding_time": self.status.seeding_time,
"seeds_peers_ratio": seeds_peers_ratio,
"seed_rank": self.status.seed_rank,
"state": self.state,
"stop_at_ratio": self.options["stop_at_ratio"],
"stop_ratio": self.options["stop_ratio"],
"time_added": self.time_added,
"total_done": self.status.total_done,
"total_payload_download": self.status.total_payload_download,
"total_payload_upload": self.status.total_payload_upload,
"total_peers": self.status.num_incomplete,
"total_seeds": self.status.num_complete,
"total_uploaded": self.status.all_time_upload,
"total_wanted": self.status.total_wanted,
"tracker": self.status.current_tracker,
"trackers": self.trackers,
"tracker_status": self.tracker_status,
"upload_payload_rate": self.status.upload_payload_rate
}
def ti_comment():
if self.handle.has_metadata():
try:
return self.torrent_info.comment().decode("utf8", "ignore")
except UnicodeDecodeError:
return self.torrent_info.comment()
return ""
def ti_priv():
if self.handle.has_metadata():
return self.torrent_info.priv()
return False
def ti_total_size():
if self.handle.has_metadata():
return self.torrent_info.total_size()
return 0
def ti_num_files():
if self.handle.has_metadata():
return self.torrent_info.num_files()
return 0
def ti_num_pieces():
if self.handle.has_metadata():
return self.torrent_info.num_pieces()
return 0
def ti_piece_length():
if self.handle.has_metadata():
return self.torrent_info.piece_length()
return 0
def ti_pieces_info():
if self.handle.has_metadata():
return self.get_pieces_info()
return None
fns = {
"comment": ti_comment,
"eta": self.get_eta,
"file_progress": self.get_file_progress,
"files": self.get_files,
"is_seed": self.handle.is_seed,
"name": self.get_name,
"num_files": ti_num_files,
"num_pieces": ti_num_pieces,
"pieces": ti_pieces_info,
"peers": self.get_peers,
"piece_length": ti_piece_length,
"private": ti_priv,
"queue": self.handle.queue_position,
"ratio": self.get_ratio,
"total_size": ti_total_size,
"tracker_host": self.get_tracker_host,
"last_seen_complete": self.get_last_seen_complete
}
if not self.status_funcs:
#if you add a key here->add it to core.py STATUS_KEYS too.
self.status_funcs = {
"active_time": lambda: self.status.active_time,
"all_time_download": lambda: self.status.all_time_download,
"compact": lambda: self.options["compact_allocation"],
"distributed_copies": lambda: 0.0 if self.status.distributed_copies < 0 else \
self.status.distributed_copies, # Adjust status.distributed_copies to return a non-negative value
"download_payload_rate": lambda: self.status.download_payload_rate,
"file_priorities": lambda: self.options["file_priorities"],
"hash": lambda: self.torrent_id,
"is_auto_managed": lambda: self.options["auto_managed"],
"is_finished": lambda: self.is_finished,
"max_connections": lambda: self.options["max_connections"],
"max_download_speed": lambda: self.options["max_download_speed"],
"max_upload_slots": lambda: self.options["max_upload_slots"],
"max_upload_speed": lambda: self.options["max_upload_speed"],
"message": lambda: self.statusmsg,
"move_on_completed_path": lambda: self.options["move_completed_path"],
"move_on_completed": lambda: self.options["move_completed"],
"move_completed_path": lambda: self.options["move_completed_path"],
"move_completed": lambda: self.options["move_completed"],
"next_announce": lambda: self.status.next_announce.seconds,
"num_peers": lambda: self.status.num_peers - self.status.num_seeds,
"num_seeds": lambda: self.status.num_seeds,
"owner": lambda: self.owner,
"paused": lambda: self.status.paused,
"prioritize_first_last": lambda: self.options["prioritize_first_last_pieces"],
"sequential_download": lambda: self.options["sequential_download"],
"progress": lambda: self.status.progress * 100,
"shared": lambda: self.options["shared"],
"remove_at_ratio": lambda: self.options["remove_at_ratio"],
"save_path": lambda: self.options["download_location"],
"seeding_time": lambda: self.status.seeding_time,
"seeds_peers_ratio": lambda: -1.0 if self.status.num_incomplete == 0 else \
self.status.num_complete / float(self.status.num_incomplete), # Use -1.0 to signify infinity
"seed_rank": lambda: self.status.seed_rank,
"state": lambda: self.state,
"stop_at_ratio": lambda: self.options["stop_at_ratio"],
"stop_ratio": lambda: self.options["stop_ratio"],
"time_added": lambda: self.time_added,
"total_done": lambda: self.status.total_done,
"total_payload_download": lambda: self.status.total_payload_download,
"total_payload_upload": lambda: self.status.total_payload_upload,
"total_peers": lambda: self.status.num_incomplete,
"total_seeds": lambda: self.status.num_complete,
"total_uploaded": lambda: self.status.all_time_upload,
"total_wanted": lambda: self.status.total_wanted,
"tracker": lambda: self.status.current_tracker,
"trackers": lambda: self.trackers,
"tracker_status": lambda: self.tracker_status,
"upload_payload_rate": lambda: self.status.upload_payload_rate,
"eta": self.get_eta,
"file_progress": self.get_file_progress, # Adjust progress to be 0-100 value
"files": self.get_files,
"is_seed": self.handle.is_seed,
"peers": self.get_peers,
"queue": self.handle.queue_position,
"ratio": self.get_ratio,
"tracker_host": self.get_tracker_host,
"last_seen_complete": self.get_last_seen_complete,
"comment": self.ti_comment,
"name": self.ti_name,
"num_files": self.ti_num_files,
"num_pieces": self.ti_num_pieces,
"pieces": self.ti_pieces_info,
"piece_length": self.ti_piece_length,
"private": self.ti_priv,
"total_size": self.ti_total_size,
}
def create_status_dict(self, keys, diff=False):
# Create the desired status dictionary and return it
status_dict = {}
if len(keys) == 0:
status_dict = full_status
for key in fns:
status_dict[key] = fns[key]()
else:
for key in keys:
if key in full_status:
status_dict[key] = full_status[key]
elif key in fns:
status_dict[key] = fns[key]()
if not keys:
keys = self.status_funcs.keys()
for key in keys:
status_dict[key] = self.status_funcs[key]()
session_id = self.rpcserver.get_session_id()
if diff:
@ -831,6 +793,70 @@ class Torrent(object):
return status_dict
def ti_comment(self):
if self.has_metadata():
try:
return self.torrent_info.comment().decode("utf8", "ignore")
except UnicodeDecodeError:
return self.torrent_info.comment()
return ""
def ti_name(self):
if self.has_metadata():
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
if not name:
name = self.torrent_info.name()
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
elif self.magnet:
try:
keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
name = keys.get('dn')
if not name:
return self.torrent_id
name = unquote(name).replace('+', ' ')
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
except:
pass
return self.torrent_id
def ti_priv(self):
if self.has_metadata():
return self.torrent_info.priv()
return False
def ti_total_size(self):
if self.has_metadata():
return self.torrent_info.total_size()
return 0
def ti_num_files(self):
if self.has_metadata():
return self.torrent_info.num_files()
return 0
def ti_num_pieces(self):
if self.has_metadata():
return self.torrent_info.num_pieces()
return 0
def ti_piece_length(self):
if self.has_metadata():
return self.torrent_info.piece_length()
return 0
def ti_pieces_info(self):
if self.has_metadata():
return self.get_pieces_info()
return None
def apply_options(self):
"""Applies the per-torrent options that are set."""
self.handle.set_max_connections(self.max_connections)
@ -906,7 +932,7 @@ class Torrent(object):
except TypeError:
# String is already unicode
pass
if not os.path.exists(dest):
try:
# Try to make the destination path if it doesn't exist
@ -943,7 +969,6 @@ class Torrent(object):
self.torrent_id)
log.debug("Writing torrent file: %s", path)
try:
self.torrent_info = self.handle.get_torrent_info()
# Regenerate the file priorities
self.set_file_priorities([])
md = lt.bdecode(self.torrent_info.metadata())
@ -1116,7 +1141,7 @@ class Torrent(object):
pieces[peer_info.downloading_piece_index] = 2
# Now, the rest of the pieces
for idx, piece in enumerate(self.handle.status().pieces):
for idx, piece in enumerate(self.status.pieces):
if idx in pieces:
# Piece beeing downloaded, handled above
continue

View File

@ -41,9 +41,11 @@ import os
import shutil
import operator
import logging
import time
from twisted.internet.task import LoopingCall
from twisted.internet.defer import Deferred, DeferredList
from twisted.internet import reactor
from deluge._libtorrent import lt
@ -157,6 +159,10 @@ class TorrentManager(component.Component):
# Keeps track of resume data
self.resume_data = {}
self.torrents_status_requests = []
self.status_dict = {}
self.state_update_alert_start = None
# Register set functions
self.config.register_set_function("max_connections_per_torrent",
self.on_set_max_connections_per_torrent)
@ -200,6 +206,8 @@ class TorrentManager(component.Component):
self.on_alert_file_error)
self.alerts.register_handler("file_completed_alert",
self.on_alert_file_completed)
self.alerts.register_handler("state_update_alert",
self.state_update_alert)
def start(self):
# Get the pluginmanager reference
@ -286,7 +294,8 @@ class TorrentManager(component.Component):
torrent_info = None
# Get the torrent data from the torrent file
try:
log.debug("Attempting to create torrent_info from %s", filepath)
if log.isEnabledFor(logging.DEBUG):
log.debug("Attempting to create torrent_info from %s", filepath)
_file = open(filepath, "rb")
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
_file.close()
@ -321,7 +330,6 @@ class TorrentManager(component.Component):
def add(self, torrent_info=None, state=None, options=None, save_state=True,
filedump=None, filename=None, magnet=None, resume_data=None, owner=None):
"""Add a torrent to the manager and returns it's torrent_id"""
if owner is None:
owner = component.get("RPCServer").get_session_user()
if not owner:
@ -331,7 +339,6 @@ class TorrentManager(component.Component):
log.debug("You must specify a valid torrent_info, torrent state or magnet.")
return
log.debug("torrentmanager.add")
add_torrent_params = {}
if filedump is not None:
@ -439,8 +446,8 @@ class TorrentManager(component.Component):
add_torrent_params["ti"] = torrent_info
#log.info("Adding torrent: %s", filename)
log.debug("options: %s", options)
if log.isEnabledFor(logging.DEBUG):
log.debug("options: %s", options)
# Set the right storage_mode
if options["compact_allocation"]:
@ -474,7 +481,8 @@ class TorrentManager(component.Component):
component.resume("AlertManager")
return
log.debug("handle id: %s", str(handle.info_hash()))
if log.isEnabledFor(logging.DEBUG):
log.debug("handle id: %s", str(handle.info_hash()))
# Set auto_managed to False because the torrent is paused
handle.auto_managed(False)
# Create a Torrent object
@ -485,6 +493,7 @@ class TorrentManager(component.Component):
if not account_exists:
owner = 'localclient'
torrent = Torrent(handle, options, state, filename, magnet, owner)
# Add the torrent object to the dictionary
self.torrents[torrent.torrent_id] = torrent
if self.config["queue_new_to_top"]:
@ -531,10 +540,14 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(
TorrentAddedEvent(torrent.torrent_id, from_state)
)
log.info("Torrent %s from user \"%s\" %s",
torrent.get_status(["name"])["name"],
torrent.get_status(["owner"])["owner"],
(from_state and "loaded" or "added"))
if log.isEnabledFor(logging.INFO):
name_and_owner = torrent.get_status(["name", "owner"])
log.info("Torrent %s from user \"%s\" %s" % (
name_and_owner["name"],
name_and_owner["owner"],
from_state and "loaded" or "added")
)
return torrent.torrent_id
def load_torrent(self, torrent_id):
@ -646,28 +659,37 @@ class TorrentManager(component.Component):
# Try to use an old state
try:
state_tmp = TorrentState()
if dir(state.torrents[0]) != dir(state_tmp):
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
for s in state.torrents:
setattr(s, attr, getattr(state_tmp, attr, None))
if len(state.torrents) > 0:
state_tmp = TorrentState()
if dir(state.torrents[0]) != dir(state_tmp):
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
for s in state.torrents:
setattr(s, attr, getattr(state_tmp, attr, None))
except Exception, e:
log.warning("Unable to update state file to a compatible version: %s", e)
import traceback
print traceback.format_exc()
# Reorder the state.torrents list to add torrents in the correct queue
# order.
state.torrents.sort(key=operator.attrgetter("queue"), reverse=self.config["queue_new_to_top"])
resume_data = self.load_resume_data_file()
# Tell alertmanager to wait for the handlers while adding torrents.
# This speeds up startup loading the torrents by quite a lot for some reason (~40%)
self.alerts.wait_on_handler = True
for torrent_state in state.torrents:
try:
self.add(state=torrent_state, save_state=False,
resume_data=resume_data.get(torrent_state.torrent_id))
except AttributeError, e:
log.error("Torrent state file is either corrupt or incompatible! %s", e)
import traceback
traceback.print_exc()
break
self.alerts.wait_on_handler = False
if lt.version_minor < 16:
log.debug("libtorrent version is lower than 0.16. Start looping "
@ -913,7 +935,8 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id, ))
def on_alert_torrent_paused(self, alert):
log.debug("on_alert_torrent_paused")
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_paused")
try:
torrent = self.torrents[str(alert.handle.info_hash())]
torrent_id = str(alert.handle.info_hash())
@ -930,7 +953,8 @@ class TorrentManager(component.Component):
self.save_resume_data((torrent_id,))
def on_alert_torrent_checked(self, alert):
log.debug("on_alert_torrent_checked")
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_torrent_checked")
try:
torrent = self.torrents[str(alert.handle.info_hash())]
except:
@ -947,7 +971,8 @@ class TorrentManager(component.Component):
torrent.update_state()
def on_alert_tracker_reply(self, alert):
log.debug("on_alert_tracker_reply: %s", alert.message().decode("utf8"))
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_reply: %s", alert.message().decode("utf8"))
try:
torrent = self.torrents[str(alert.handle.info_hash())]
except:
@ -964,7 +989,8 @@ class TorrentManager(component.Component):
torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert):
log.debug("on_alert_tracker_announce")
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_tracker_announce")
try:
torrent = self.torrents[str(alert.handle.info_hash())]
except:
@ -1016,7 +1042,8 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentResumedEvent(torrent_id))
def on_alert_state_changed(self, alert):
log.debug("on_alert_state_changed")
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_state_changed")
try:
torrent_id = str(alert.handle.info_hash())
torrent = self.torrents[torrent_id]
@ -1036,7 +1063,8 @@ class TorrentManager(component.Component):
component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
def on_alert_save_resume_data(self, alert):
log.debug("on_alert_save_resume_data")
if log.isEnabledFor(logging.DEBUG):
log.debug("on_alert_save_resume_data")
torrent_id = str(alert.handle.info_hash())
if torrent_id in self.torrents:
@ -1096,3 +1124,62 @@ class TorrentManager(component.Component):
return
component.get("EventManager").emit(
TorrentFileCompletedEvent(torrent_id, alert.index))
def separate_keys(self, keys, torrent_ids):
"""Separates the input keys into keys for the Torrent class
and keys for plugins.
"""
if not self.torrents:
return [], []
for torrent_id in torrent_ids:
if torrent_id in self.torrents:
status_keys = self.torrents[torrent_id].status_funcs.keys()
leftover_keys = list(set(keys) - set(status_keys))
torrent_keys = list(set(keys) - set(leftover_keys))
return torrent_keys, leftover_keys
def state_update_alert(self, alert):
self.state_update_alert_start = time.time()
log.debug("on_status_notification: %s", alert.message())
for s in alert.status:
torrent_id = str(s.info_hash)
if torrent_id in self.torrents:
self.torrents[torrent_id].update_status(s)
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
def handle_torrents_status_callback(self, status_request):
d, torrent_ids, keys, diff = status_request
status_dict = {}.fromkeys(torrent_ids)
torrent_keys, plugin_keys = self.separate_keys(keys, torrent_ids)
# Get the torrent status for each torrent_id
for torrent_id in torrent_ids:
if not torrent_id in self.torrents:
# This happens sometimes, but why? Unfixed bug?
# Without this test it gives a KeyError.
print "Missing torrent id:", torrent_id
del status_dict[torrent_id]
else:
status_dict[torrent_id] = self.torrents[torrent_id].create_status_dict(torrent_keys, diff)
self.status_dict = status_dict
d.callback((status_dict, plugin_keys))
def torrents_status_update(self, torrent_ids, keys, diff=False):
"""
returns all torrents , optionally filtered by filter_dict.
If the torrent states were updated recently (less than two seconds ago,
post_torrent_updates is not called. Instead the cached state is used.
"""
d = Deferred()
now = time.time()
# Less than two seconds since last time the torrent states were updated
if self.state_update_alert_start and \
(now - self.state_update_alert_start) < 2:
reactor.callLater(0, self.handle_torrents_status_callback, (d, torrent_ids, keys, diff))
else:
# Ask libtorrent for status update
self.torrents_status_requests.insert(0, (d, torrent_ids, keys, diff))
self.session.post_torrent_updates()
return d

View File

@ -194,16 +194,16 @@ class StatusBar(component.Component):
self.health = False
def update_config_values(configs):
self._on_max_connections_global(configs["max_connections_global"])
self._on_max_download_speed(configs["max_download_speed"])
self._on_max_upload_speed(configs["max_upload_speed"])
self._on_dht(configs["dht"])
# Get some config values
client.core.get_config_value(
"max_connections_global").addCallback(self._on_max_connections_global)
client.core.get_config_value(
"max_download_speed").addCallback(self._on_max_download_speed)
client.core.get_config_value(
"max_upload_speed").addCallback(self._on_max_upload_speed)
client.core.get_config_value("dht").addCallback(self._on_dht)
self.send_status_request()
client.core.get_config_values(["max_connections_global", "max_download_speed",
"max_upload_speed", "dht"]).addCallback(update_config_values)
# Called from update a few milliseconds later
#self.send_status_request()
def stop(self):
# When stopped, we just show the not connected thingy

View File

@ -172,11 +172,12 @@ class SystemTray(component.Component):
self.build_tray_bwsetsubmenu()
# Get some config values
client.core.get_config_value(
"max_download_speed").addCallback(self._on_max_download_speed)
client.core.get_config_value(
"max_upload_speed").addCallback(self._on_max_upload_speed)
self.send_status_request()
def update_config_values(configs):
self._on_max_download_speed(configs["max_download_speed"])
self._on_max_upload_speed(configs["max_upload_speed"])
client.core.get_config_values(["max_download_speed", "max_upload_speed"]).addCallback(update_config_values)
# This is called from update a few milliseconds later, so this line can be deleted
#self.send_status_request()
def start(self):
self.__start()

View File

@ -85,11 +85,14 @@ class SessionProxy(component.Component):
# core if it's not going to be used. Additional status fields
# will be queried later, for example, when viewing the status tab
# of a torrent.
inital_keys = [
'queue', 'state', 'name', 'total_wanted', 'progress', 'state',
'download_payload_rate', 'upload_payload_rate', 'eta', 'owner'
]
self.get_torrents_status({'id': torrent_ids}, inital_keys)
#inital_keys = [
# 'queue', 'state', 'name', 'total_wanted', 'progress', 'state',
# 'download_payload_rate', 'upload_payload_rate', 'eta', 'owner'
#]
#The the torrents status will be requested by torrentview, so this
#only causes a second request for the same data withing a few miliseconds
#self.get_torrents_status({'id': torrent_ids}, inital_keys)
return client.core.get_session_state().addCallback(on_get_session_state)
def stop(self):