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,..]}
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]:

View File

@ -427,21 +427,25 @@ 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):
"""
@ -449,12 +453,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

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

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,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()
if not keys:
keys = self.status_funcs.keys()
# 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 = {}
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

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.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

View File

@ -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

View File

@ -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()

View File

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