Merge remote-tracking branch 'bro/master-daemon-optimize-speed'
Conflicts: deluge/core/alertmanager.py deluge/core/torrentmanager.py
This commit is contained in:
commit
8c106ce8c4
|
@ -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,7 +123,8 @@ 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
|
||||||
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
|
# Call any handlers for this alert type
|
||||||
if alert_type in self.handlers:
|
if alert_type in self.handlers:
|
||||||
for handler in self.handlers[alert_type]:
|
for handler in self.handlers[alert_type]:
|
||||||
|
|
|
@ -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:
|
||||||
return status_dict
|
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
|
@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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
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):
|
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:
|
||||||
|
|
|
@ -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,7 +224,17 @@ class Torrent(object):
|
||||||
self.forcing_recheck = False
|
self.forcing_recheck = False
|
||||||
self.forcing_recheck_paused = 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 ##
|
## Options methods ##
|
||||||
def set_options(self, options):
|
def set_options(self, options):
|
||||||
|
@ -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,7 +395,8 @@ class Torrent(object):
|
||||||
self.options["file_priorities"] = self.handle.file_priorities()
|
self.options["file_priorities"] = self.handle.file_priorities()
|
||||||
return
|
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)
|
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
|
||||||
|
|
||||||
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 = []
|
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)
|
||||||
|
|
||||||
log.debug("set_state_based_on_ltstate: %s", deluge.common.LT_TORRENT_STATE[ltstate])
|
session_is_paused = component.get("Core").session.is_paused()
|
||||||
log.debug("session.is_paused: %s", 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
|
# 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.status
|
||||||
status = self.handle.status()
|
|
||||||
else:
|
|
||||||
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:
|
for key in keys:
|
||||||
status_dict = full_status
|
status_dict[key] = self.status_funcs[key]()
|
||||||
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]()
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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,7 +294,8 @@ 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:
|
||||||
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")
|
_file = open(filepath, "rb")
|
||||||
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
|
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
|
||||||
_file.close()
|
_file.close()
|
||||||
|
@ -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,8 +447,8 @@ 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
|
||||||
if options["compact_allocation"]:
|
if options["compact_allocation"]:
|
||||||
|
@ -475,7 +482,8 @@ class TorrentManager(component.Component):
|
||||||
component.resume("AlertManager")
|
component.resume("AlertManager")
|
||||||
return
|
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
|
# Set auto_managed to False because the torrent is paused
|
||||||
handle.auto_managed(False)
|
handle.auto_managed(False)
|
||||||
# Create a Torrent object
|
# Create a Torrent object
|
||||||
|
@ -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:
|
||||||
state_tmp = TorrentState()
|
if len(state.torrents) > 0:
|
||||||
if dir(state.torrents[0]) != dir(state_tmp):
|
state_tmp = TorrentState()
|
||||||
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
|
if dir(state.torrents[0]) != dir(state_tmp):
|
||||||
for s in state.torrents:
|
for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))):
|
||||||
setattr(s, attr, getattr(state_tmp, attr, None))
|
for s in state.torrents:
|
||||||
|
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,7 +934,8 @@ 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):
|
||||||
log.debug("on_alert_torrent_paused")
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
|
log.debug("on_alert_torrent_paused")
|
||||||
try:
|
try:
|
||||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||||
torrent_id = 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,))
|
self.save_resume_data((torrent_id,))
|
||||||
|
|
||||||
def on_alert_torrent_checked(self, alert):
|
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:
|
try:
|
||||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||||
except:
|
except:
|
||||||
|
@ -948,7 +970,8 @@ class TorrentManager(component.Component):
|
||||||
torrent.update_state()
|
torrent.update_state()
|
||||||
|
|
||||||
def on_alert_tracker_reply(self, alert):
|
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:
|
try:
|
||||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||||
except:
|
except:
|
||||||
|
@ -964,7 +987,8 @@ class TorrentManager(component.Component):
|
||||||
torrent.scrape_tracker()
|
torrent.scrape_tracker()
|
||||||
|
|
||||||
def on_alert_tracker_announce(self, alert):
|
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:
|
try:
|
||||||
torrent = self.torrents[str(alert.handle.info_hash())]
|
torrent = self.torrents[str(alert.handle.info_hash())]
|
||||||
except:
|
except:
|
||||||
|
@ -1016,7 +1040,8 @@ 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):
|
||||||
log.debug("on_alert_state_changed")
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
|
log.debug("on_alert_state_changed")
|
||||||
try:
|
try:
|
||||||
torrent_id = str(alert.handle.info_hash())
|
torrent_id = str(alert.handle.info_hash())
|
||||||
torrent = self.torrents[torrent_id]
|
torrent = self.torrents[torrent_id]
|
||||||
|
@ -1036,7 +1061,8 @@ 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):
|
||||||
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())
|
torrent_id = str(alert.handle.info_hash())
|
||||||
|
|
||||||
if torrent_id in self.torrents:
|
if torrent_id in self.torrents:
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue