Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. #
TorrentFileRenamedEvent, TorrentFinishedEvent, TorrentRemovedEvent, TorrentResumedEvent, TorrentStateChangedEvent)
"""Create a torrent state""" torrent_id=None, filename=None, trackers=None, storage_mode="sparse", paused=False, save_path=None, max_connections=-1, max_upload_slots=-1, max_upload_speed=-1.0, max_download_speed=-1.0, prioritize_first_last=False, sequential_download=False, file_priorities=None, queue=None, auto_managed=True, is_finished=False, error_statusmsg=None, stop_ratio=2.00, stop_at_ratio=False, remove_at_ratio=False, move_completed=False, move_completed_path=None, magnet=None, owner=None, shared=False, super_seeding=False, priority=0, name=None ): # Build the class atrribute list from args
"""TorrentManagerState holds a list of TorrentState objects"""
"""TorrentManager contains a list of torrents in the current libtorrent session.
This object is also responsible for saving the state of the session for use on restart.
"""
depend=["CorePluginManager", "AlertManager"]) # Set the libtorrent session # Set the alertmanager # Get the core config
# Make sure the state folder has been created
# Create the torrents dict { torrent_id: Torrent }
# This is a map of torrent_ids to Deferreds used to track needed resume data. # The Deferreds will be completed when resume data has been saved.
# Keep track of torrents finished but moving storage
# Keeps track of resume data
# Register set functions self.on_set_max_connections_per_torrent) self.on_set_max_upload_slots_per_torrent) self.on_set_max_upload_speed_per_torrent) self.on_set_max_download_speed_per_torrent)
# Register alert functions
# Define timers
# Check for old temp file to verify safe shutdown def archive_file(filename): """Archives the file in 'archive' sub-directory with timestamp appended""" import datetime filepath = os.path.join(self.state_dir, filename) filepath_bak = filepath + ".bak" archive_dir = os.path.join(get_config_dir(), "archive") if not os.path.exists(archive_dir): os.makedirs(archive_dir)
for _filepath in (filepath, filepath_bak): timestamp = datetime.datetime.now().replace(microsecond=0).isoformat().replace(':', '-') archive_filepath = os.path.join(archive_dir, filename + "-" + timestamp) try: shutil.copy2(_filepath, archive_filepath) except IOError: log.error("Unable to archive: %s", filename) else: log.info("Archive of %s successful: %s", filename, archive_filepath)
log.warning("Potential bad shutdown of Deluge detected, archiving torrent state files...") archive_file("torrents.state") archive_file("torrents.fastresume") else:
# Try to load the state from file
# Save the state periodically
# Stop timers
# Save state on shutdown
"""Remove the temp_file to signify successfully saved state"""
# XXX: Should the state check be those that _can_ be stopped at ratio if torrent.options["stop_at_ratio"] and torrent.state not in ( "Checking", "Allocating", "Paused", "Queued"): # If the global setting is set, but the per-torrent isn't... # Just skip to the next torrent. # This is so that a user can turn-off the stop at ratio option on a per-torrent basis if not torrent.options["stop_at_ratio"]: continue if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished: if torrent.options["remove_at_ratio"]: self.remove(torrent_id) break if not torrent.handle.status().paused: torrent.pause()
"""Return the Torrent with torrent_id.
Args: torrent_id (str): The torrent_id.
Returns: Torrent: A torrent object. """ return self.torrents[torrent_id]
"""Creates a list of torrent_ids, owned by current user and any marked shared.
Returns: list: A list of torrent_ids. """
current_user = component.get("RPCServer").get_session_user() for torrent_id in torrent_ids[:]: torrent_status = self[torrent_id].get_status(["owner", "shared"]) if torrent_status["owner"] != current_user and not torrent_status["shared"]: torrent_ids.pop(torrent_ids.index(torrent_id)) return torrent_ids
"""Retrieves torrent_info from the file specified.
Args: filepath (str): The filepath to extract torrent info from.
Returns: lt.torrent_info : A libtorrent torrent_info dict.
Returns None if file or data are not valid """ # Get the torrent data from the torrent file if log.isEnabledFor(logging.DEBUG): log.debug("Attempting to extract torrent_info from %s", filepath) try: with open(filepath, "rb") as _file: torrent_info = lt.torrent_info(lt.bdecode(_file.read())) except (IOError, RuntimeError) as ex: log.warning("Unable to open torrent file %s: %s", filepath, ex) else: return torrent_info
filedump=None, filename=None, magnet=None, resume_data=None): """Adds a torrent to the torrent manager.
Args: torrent_info (lt.torrent_info, optional): A libtorrent torrent_info object. state (TorrentState, optional): The torrent state. options (dict, optional): The options to apply to the torrent on adding. save_state (bool, optional): If True save the session state after adding torrent, defaults to True. filedump (str, optional): bencoded filedump of a torrent file. filename (str, optional): The filename of the torrent file. magnet (str, optional): The magnet uri. resume_data (lt.entry, optional): libtorrent fast resume data.
Returns: str: The torrent_id of the added torrent.
Returns None if adding was unsuccessful.
""" log.debug("You must specify a valid torrent_info, torrent state or magnet.") return
except RuntimeError as ex: log.error("Unable to decode torrent file!: %s", ex) # XXX: Probably should raise an exception here... return
# We have no torrent_info so we need to add the torrent with information # from the state object.
# Populate the options dict from state options = TorrentOptions() options["max_connections"] = state.max_connections options["max_upload_slots"] = state.max_upload_slots options["max_upload_speed"] = state.max_upload_speed options["max_download_speed"] = state.max_download_speed options["prioritize_first_last_pieces"] = state.prioritize_first_last options["sequential_download"] = state.sequential_download options["file_priorities"] = state.file_priorities storage_mode = state.storage_mode options["pre_allocate_storage"] = (storage_mode == "allocate") options["download_location"] = state.save_path options["auto_managed"] = state.auto_managed options["stop_at_ratio"] = state.stop_at_ratio options["stop_ratio"] = state.stop_ratio options["remove_at_ratio"] = state.remove_at_ratio options["move_completed"] = state.move_completed options["move_completed_path"] = state.move_completed_path options["add_paused"] = state.paused options["shared"] = state.shared options["super_seeding"] = state.super_seeding options["priority"] = state.priority options["owner"] = state.owner options["name"] = state.name
torrent_info = self.get_torrent_info_from_file( os.path.join(self.state_dir, state.torrent_id + ".torrent")) if torrent_info: add_torrent_params["ti"] = torrent_info elif state.magnet: magnet = state.magnet else: log.error("Unable to add torrent!") return
if resume_data: add_torrent_params["resume_data"] = resume_data else: # We have a torrent_info object or magnet uri so we're not loading from state. # If this torrent id is already in the session, merge any additional trackers. log.info("Merging trackers for torrent (%s) already in session...", add_torrent_id) # Don't merge trackers if either torrent has private flag set if self[add_torrent_id].get_status(["private"])["private"]: log.info("Merging trackers abandoned: Torrent has private flag set.") return
add_torrent_trackers = [] for value in torrent_info.trackers(): tracker = {} tracker["url"] = value.url tracker["tier"] = value.tier add_torrent_trackers.append(tracker)
torrent_trackers = {} tracker_list = [] for tracker in self[add_torrent_id].get_status(["trackers"])["trackers"]: torrent_trackers[(tracker["url"])] = tracker tracker_list.append(tracker)
added_tracker = 0 for tracker in add_torrent_trackers: if tracker['url'] not in torrent_trackers: tracker_list.append(tracker) added_tracker += 1
if added_tracker: log.info("%s tracker(s) merged into torrent.", added_tracker) self[add_torrent_id].set_trackers(tracker_list) return
# Check if options is None and load defaults options = TorrentOptions() else:
# Check for renamed files and if so, rename them in the torrent_info # before adding to the session. for index, fname in options["mapped_files"].items(): fname = sanitize_filepath(decode_string(fname)) log.debug("renaming file index %s to %s", index, fname) try: torrent_info.rename_file(index, fname) except TypeError: torrent_info.rename_file(index, utf8_encoded(fname))
storage_mode = "allocate" else:
log.debug("options: %s", options)
options["owner"] = "localclient"
# Fill in the rest of the add_torrent_params dictionary
except KeyError: add_torrent_params["storage_mode"] = lt.storage_mode_t.storage_mode_sparse
lt.add_torrent_params_flags_t.flag_auto_managed | lt.add_torrent_params_flags_t.flag_update_subscribe | lt.add_torrent_params_flags_t.flag_apply_ip_filter) # Set flags: enable duplicate_is_error and disable auto_managed | lt.add_torrent_params_flags_t.flag_duplicate_is_error) ^ lt.add_torrent_params_flags_t.flag_auto_managed) add_torrent_params["flags"] |= lt.add_torrent_params_flags_t.flag_seed_mode
add_torrent_params['name'] = options['name'] name = torrent_info.file_at(0).path.replace("\\", "/", 1).split("/", 1)[0]
# We need to pause the AlertManager momentarily to prevent alerts # for this torrent being generated before a Torrent object is created.
except RuntimeError as ex: log.warning("Error adding torrent: %s", ex)
log.debug("torrent handle is invalid!") # The torrent was not added to the session component.resume("AlertManager") return
log.debug("handle id: %s", str(handle.info_hash())) # Set auto_managed to False because the torrent is paused # Create a Torrent object
# Add the torrent object to the dictionary handle.queue_position_top()
# Resume the torrent if needed
# Add to queued torrents set
# Write the .torrent file to the state directory
# Save the session state
# Emit torrent_added signal
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")
""" Remove a torrent from the session.
Args: torrent_id (str): The torrent ID to remove remove_data (bool, optional): If True, remove the downloaded data, defaults to False. save_state (bool, optional): If True, save the session state after removal, defaults to True.
Returns: bool: True if removed successfully, False if not.
Raises: InvalidTorrentError: If the torrent_id is not in the session. """
except RuntimeError as ex: log.warning("Error removing torrent: %s", ex) return False
# Remove fastresume data if it is exists
# Remove the .torrent file in the state
# Emit the signal to the clients
# Remove the torrent file from the user specified directory users_torrent_file = os.path.join(self.config["torrentfiles_location"], filename) log.info("Delete user's torrent file: %s", users_torrent_file) try: os.remove(users_torrent_file) except OSError as ex: log.warning("Unable to remove copy torrent file: %s", ex)
# Remove from set if it wasn't finished except KeyError: log.debug("%s isn't in queued torrents set?", torrent_id) raise InvalidTorrentError("%s isn't in queued torrents set?" % torrent_id)
# Remove the torrent from deluge's session
# Emit the signal to the clients
"""Load the state of the TorrentManager from the torrents.state file"""
state = cPickle.load(_file) else: log.info("Successfully loaded %s: %s", filename, _filepath) break
# Fixup an old state by adding missing TorrentState options and assigning default values state_tmp = TorrentState() if dir(state.torrents[0]) != dir(state_tmp): try: for attr in (set(dir(state_tmp)) - set(dir(state.torrents[0]))): for t_state in state.torrents: if attr == "storage_mode" and getattr(t_state, "compact", None): setattr(t_state, attr, "compact") else: setattr(t_state, attr, getattr(state_tmp, attr, None)) except AttributeError as ex: log.exception("Unable to update state file to a compatible version: %s", ex)
# Reorder the state.torrents list to add torrents in the correct queue # order.
# 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%)
try: self.add(state=torrent_state, save_state=False, resume_data=resume_data.get(torrent_state.torrent_id)) except AttributeError as ex: log.error("Torrent state file is either corrupt or incompatible! %s", ex) import traceback traceback.print_exc() break
"""Save the state of the TorrentManager to the torrents.state file""" # Create the state for each Torrent and append to the list paused = True
torrent.torrent_id, torrent.filename, torrent.trackers, torrent.get_status(["storage_mode"])["storage_mode"], paused, torrent.options["download_location"], torrent.options["max_connections"], torrent.options["max_upload_slots"], torrent.options["max_upload_speed"], torrent.options["max_download_speed"], torrent.options["prioritize_first_last_pieces"], torrent.options["sequential_download"], torrent.options["file_priorities"], torrent.get_queue_position(), torrent.options["auto_managed"], torrent.is_finished, torrent.error_statusmsg, torrent.options["stop_ratio"], torrent.options["stop_at_ratio"], torrent.options["remove_at_ratio"], torrent.options["move_completed"], torrent.options["move_completed_path"], torrent.magnet, torrent.options["owner"], torrent.options["shared"], torrent.options["super_seeding"], torrent.options["priority"], torrent.options["name"] )
except IOError as ex: log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex) else: # Pickle the TorrentManagerState object except (IOError, cPickle.PicklingError) as ex: log.error("Unable to save %s: %s", filename, ex) if os.path.isfile(filepath_bak): log.info("Restoring backup of %s from: %s", filename, filepath_bak) shutil.move(filepath_bak, filepath) # We return True so that the timer thread will continue
"""Saves torrents resume data.
Args: torrent_ids (list of str): A list of torrents to save the resume data for, defaults to None which saves all torrents resume data. flush_disk_cache (bool, optional): If True flushes the disk cache which avoids potential issue with file timestamps, defaults to False. This is only needed when stopping the session.
Returns: t.i.d.DeferredList: A list of twisted Deferred callbacks that will be invoked when save is complete.
"""
"""Recieved torrent resume_data alert so remove from waiting list"""
"""Saves resume data file when no more torrents waiting for resume data
Returns: bool: True if fastresume file is saved.
Used by remove_temp_file callback in stop.
""" # Use flush_disk_cache as a marker for shutdown so fastresume is # saved even if torrents are waiting.
"""Load the resume data from file for all torrents
Returns: dict: A dict of torrents and their resume_data
"""
resume_data = lt.bdecode(_file.read()) else: log.info("Successfully loaded %s: %s", filename, _filepath) break # If the libtorrent bdecode doesn't happen properly, it will return None # so we need to make sure we return a {} else: return resume_data
"""Saves the resume data file with the contents of self.resume_data"""
log.debug("Creating backup of %s at: %s", filename, filepath_bak) shutil.copy2(filepath, filepath_bak) except IOError as ex: log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex) else: except (IOError, EOFError) as ex: log.error("Unable to save %s: %s", filename, ex) if os.path.isfile(filepath_bak): log.info("Restoring backup of %s from: %s", filename, filepath_bak) shutil.move(filepath_bak, filepath) else:
"""Get queue position of torrent""" return self.torrents[torrent_id].get_queue_position()
"""Queue torrent to top""" if self.torrents[torrent_id].get_queue_position() == 0: return False
self.torrents[torrent_id].handle.queue_position_top() return True
"""Queue torrent up one position""" if self.torrents[torrent_id].get_queue_position() == 0: return False
self.torrents[torrent_id].handle.queue_position_up() return True
"""Queue torrent down one position""" if self.torrents[torrent_id].get_queue_position() == (len(self.queued_torrents) - 1): return False
self.torrents[torrent_id].handle.queue_position_down() return True
"""Queue torrent to bottom""" if self.torrents[torrent_id].get_queue_position() == (len(self.queued_torrents) - 1): return False
self.torrents[torrent_id].handle.queue_position_bottom() return True
"""Run cleanup_prev_status for each registered torrent""" torrent[1].cleanup_prev_status()
"""Sets the per-torrent connection limit""" self.torrents[key].set_max_connections(value)
"""Sets the per-torrent upload slot limit""" self.torrents[key].set_max_upload_slots(value)
"""Sets the per-torrent upload speed limit""" self.torrents[key].set_max_upload_speed(value)
"""Sets the per-torrent download speed limit""" self.torrents[key].set_max_download_speed(value)
# --- Alert handlers --- """Alert handler for libtorrent torrent_finished_alert""" log.debug("on_alert_torrent_finished") try: torrent_id = str(alert.handle.info_hash()) torrent = self.torrents[torrent_id] except (RuntimeError, KeyError): return log.debug("Finished %s ", torrent_id)
# If total_download is 0, do not move, it's likely the torrent wasn't downloaded, but just added. # Get fresh data from libtorrent, the cache isn't always up to date total_download = torrent.get_status(["total_payload_download"], update=True)["total_payload_download"]
if log.isEnabledFor(logging.DEBUG): log.debug("Torrent settings: is_finished: %s, total_download: %s, move_completed: %s, move_path: %s", torrent.is_finished, total_download, torrent.options["move_completed"], torrent.options["move_completed_path"])
torrent.update_state() if not torrent.is_finished and total_download: # Move completed download to completed folder if needed if torrent.options["move_completed"] and \ torrent.options["download_location"] != torrent.options["move_completed_path"]: self.waiting_on_finish_moving.append(torrent_id) torrent.move_storage(torrent.options["move_completed_path"]) else: torrent.is_finished = True component.get("EventManager").emit(TorrentFinishedEvent(torrent_id)) else: torrent.is_finished = True
# Torrent is no longer part of the queue try: self.queued_torrents.remove(torrent_id) except KeyError: # Sometimes libtorrent fires a TorrentFinishedEvent twice if log.isEnabledFor(logging.DEBUG): log.debug("%s isn't in queued torrents set?", torrent_id)
# Only save resume data if it was actually downloaded something. Helps # on startup with big queues with lots of seeding torrents. Libtorrent # emits alert_torrent_finished for them, but there seems like nothing # worth really to save in resume data, we just read it up in # self.load_state(). if total_download: self.save_resume_data((torrent_id, ))
"""Alert handler for libtorrent torrent_paused_alert""" log.debug("on_alert_torrent_paused") except (RuntimeError, KeyError): return # Set the torrent state component.get("EventManager").emit(TorrentStateChangedEvent(torrent_id, torrent.state))
# Write the fastresume file if we are not waiting on a bulk write self.save_resume_data((torrent_id,))
"""Alert handler for libtorrent torrent_checked_alert""" log.debug("on_alert_torrent_checked") except (RuntimeError, KeyError): return
# Check to see if we're forcing a recheck and set it back to paused # if necessary torrent.forcing_recheck = False if torrent.forcing_recheck_paused: torrent.handle.pause()
# Set the torrent state
"""Alert handler for libtorrent tracker_reply_alert""" 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 (RuntimeError, KeyError): return
# Set the tracker status for the torrent torrent.set_tracker_status("Announce OK")
# Check for peer information from the tracker, if none then send a scrape request. if alert.handle.status().num_complete == -1 or alert.handle.status().num_incomplete == -1: torrent.scrape_tracker()
"""Alert handler for libtorrent tracker_announce_alert""" log.debug("on_alert_tracker_announce") except (RuntimeError, KeyError): return
# Set the tracker status for the torrent
"""Alert handler for libtorrent tracker_warning_alert""" log.debug("on_alert_tracker_warning") try: torrent = self.torrents[str(alert.handle.info_hash())] except (RuntimeError, KeyError): return # Set the tracker status for the torrent torrent.set_tracker_status("Warning: %s" % decode_string(alert.message()))
"""Alert handler for libtorrent tracker_error_alert""" error_message = decode_string(alert.msg) # If alert.msg is empty then it's a '-1' code so fallback to a.e.message. Note that alert.msg # cannot be replaced by a.e.message because the code is included in the string (for non-'-1'). if not error_message: error_message = decode_string(alert.error.message()) log.debug("Tracker Error Alert: %s [%s]", decode_string(alert.message()), error_message) try: torrent = self.torrents[str(alert.handle.info_hash())] except (RuntimeError, KeyError): return
torrent.set_tracker_status("Error: " + error_message)
"""Alert handler for libtorrent storage_moved_alert""" log.debug("on_alert_storage_moved") try: torrent_id = str(alert.handle.info_hash()) torrent = self.torrents[torrent_id] except (RuntimeError, KeyError): return torrent.set_download_location(os.path.normpath(alert.handle.save_path())) torrent.set_move_completed(False) torrent.moving_storage = False torrent.update_state()
if torrent_id in self.waiting_on_finish_moving: self.waiting_on_finish_moving.remove(torrent_id) torrent.is_finished = True component.get("EventManager").emit(TorrentFinishedEvent(torrent_id))
"""Alert handler for libtorrent storage_moved_failed_alert""" log.warning("on_alert_storage_moved_failed: %s", decode_string(alert.message())) try: torrent_id = str(alert.handle.info_hash()) torrent = self.torrents[torrent_id] except (RuntimeError, KeyError): return # Set an Error message and pause the torrent alert_msg = decode_string(alert.message()).split(':', 1)[1].strip() torrent.set_error_statusmsg("Failed to move download folder: %s" % alert_msg) torrent.moving_storage = False torrent.pause() torrent.update_state()
if torrent_id in self.waiting_on_finish_moving: self.waiting_on_finish_moving.remove(torrent_id) torrent.is_finished = True component.get("EventManager").emit(TorrentFinishedEvent(torrent_id))
"""Alert handler for libtorrent torrent_resumed_alert""" except (RuntimeError, KeyError): return # We need to emit a TorrentStateChangedEvent too
"""Alert handler for libtorrent state_changed_alert Emits a TorrentStateChangedEvent if state has changed """ log.debug("on_alert_state_changed") except (RuntimeError, KeyError): return
# Torrent may need to download data after checking.
# Only emit a state changed event if the state has actually changed
"""Alert handler for libtorrent save_resume_data_alert""" log.debug("on_alert_save_resume_data") except RuntimeError: return
# Libtorrent in add_torrent() expects resume_data to be bencoded
"""Alert handler for libtorrent save_resume_data_failed_alert""" except RuntimeError: return
"""Alert handler for libtorrent file_renamed_alert Emits a TorrentFileCompletedEvent for renamed files """ log.debug("on_alert_file_renamed") log.debug("index: %s name: %s", alert.index, decode_string(alert.name)) try: torrent_id = str(alert.handle.info_hash()) torrent = self.torrents[torrent_id] except (RuntimeError, KeyError): return
# We need to see if this file index is in a waiting_on_folder dict for wait_on_folder in torrent.waiting_on_folder_rename: if alert.index in wait_on_folder: wait_on_folder[alert.index].callback(None) break else: # This is just a regular file rename so send the signal component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name)) self.save_resume_data((torrent_id,))
"""Alert handler for libtorrent metadata_received_alert""" log.debug("on_alert_metadata_received") try: torrent = self.torrents[str(alert.handle.info_hash())] except (RuntimeError, KeyError): return torrent.on_metadata_received()
"""Alert handler for libtorrent file_error_alert""" log.debug("on_alert_file_error: %s", decode_string(alert.message())) try: torrent = self.torrents[str(alert.handle.info_hash())] except (RuntimeError, KeyError): return torrent.update_state()
"""Alert handler for libtorrent file_completed_alert
Emits a TorrentFileCompletedEvent when an individual file completes downloading
""" log.debug("file_completed_alert: %s", decode_string(alert.message())) try: torrent_id = str(alert.handle.info_hash()) except RuntimeError: return if torrent_id in self.torrents: component.get("EventManager").emit(TorrentFileCompletedEvent(torrent_id, alert.index))
"""Alert handler for libtorrent state_update_alert
Result of a session.post_torrent_updates() call and contains the torrent status of all torrents that changed since last time this was posted.
""" log.debug("on_status_notification: %s", alert.message()) self.last_state_update_alert_ts = time.time()
for t_status in alert.status: try: torrent_id = str(t_status.info_hash) except RuntimeError: continue if torrent_id in self.torrents: self.torrents[torrent_id].update_status(t_status)
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
"""Alert handler for libtorrent external_ip_alert""" log.info("on_alert_external_ip: %s", alert.message())
"""Alert handler for libtorrent performance_alert""" log.warning("on_alert_performance: %s, %s", alert.message(), alert.warning_code) if alert.warning_code == lt.performance_warning_t.send_buffer_watermark_too_low: max_send_buffer_watermark = 3 * 1024 * 1024 # 3MiB settings = self.session.get_settings() send_buffer_watermark = settings["send_buffer_watermark"]
# If send buffer is too small, try increasing its size by 512KiB (up to max_send_buffer_watermark) if send_buffer_watermark < max_send_buffer_watermark: value = send_buffer_watermark + (500 * 1024) log.info("Increasing send_buffer_watermark from %s to %s Bytes", send_buffer_watermark, value) settings["send_buffer_watermark"] = value self.session.set_settings(settings) else: log.warning("send_buffer_watermark reached maximum value: %s Bytes", max_send_buffer_watermark)
"""Separates the input keys into torrent class keys and plugins keys""" 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 [], []
"""Build the status dictionary with torrent values""" 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 torrent_id not 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, all_keys=not keys) self.status_dict = status_dict d.callback((status_dict, plugin_keys))
"""Returns status dict for the supplied torrent_ids async
Note: If torrent states was updated recently post_torrent_updates is not called and instead cached state is used.
Args: torrent_ids (list of str): The torrent IDs to get the status of. keys (list of str): The keys to get the status on. diff (bool, optional): If True, will return a diff of the changes since the last call to get_status based on the session_id, defaults to False
Returns: dict: A status dictionary for the requested torrents.
""" 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 |