Switch to libtorrent queueing

This breaks state.
This commit is contained in:
Andrew Resch 2008-06-12 09:21:13 +00:00
parent 37df050e3b
commit f7010b18f3
23 changed files with 1022 additions and 1162 deletions

View File

@ -100,7 +100,7 @@ class Config:
if filedump == self.config:
# The config has not changed so lets just return
return
except IOError:
except (EOFError, IOError):
log.warning("IOError: Unable to open file: '%s'", filename)
try:

View File

@ -78,7 +78,7 @@ class AlertManager(component.Component):
# Handler is in this alert type list
value.remove(handler)
def handle_alerts(self):
def handle_alerts(self, wait=False):
"""Pops all libtorrent alerts in the session queue and handles them
appropriately."""
alert = self.session.pop_alert()
@ -91,7 +91,10 @@ class AlertManager(component.Component):
# Call any handlers for this alert type
if alert_type in self.handlers.keys():
for handler in self.handlers[alert_type]:
gobject.idle_add(handler, alert)
if not wait:
gobject.idle_add(handler, alert)
else:
handler(alert)
alert = self.session.pop_alert()

View File

@ -60,7 +60,7 @@ DEFAULT_PREFS = {
"config_location": deluge.configmanager.get_config_dir(),
"daemon_port": 58846,
"allow_remote": False,
"compact_allocation": True,
"compact_allocation": False,
"download_location": deluge.common.get_default_download_dir(),
"listen_ports": [6881, 6891],
"torrentfiles_location": os.path.join(deluge.configmanager.get_config_dir(), "torrentfiles"),
@ -68,11 +68,11 @@ DEFAULT_PREFS = {
"state_location": os.path.join(deluge.configmanager.get_config_dir(), "state"),
"prioritize_first_last_pieces": False,
"random_port": True,
"dht": False,
"upnp": False,
"natpmp": False,
"utpex": False,
"lsd": False,
"dht": True,
"upnp": True,
"natpmp": True,
"utpex": True,
"lsd": True,
"enc_in_policy": 1,
"enc_out_policy": 1,
"enc_level": 2,
@ -89,13 +89,16 @@ DEFAULT_PREFS = {
"autoadd_location": "",
"autoadd_enable": False,
"add_paused": False,
"max_active_seeding": -1,
"max_active_downloading": -1,
"max_active_seeding": 5,
"max_active_downloading": 8,
"queue_new_to_top": False,
"queue_finished_to_bottom": False,
"stop_seed_at_ratio": False,
"remove_seed_at_ratio": False,
"stop_seed_ratio": 1.00
"stop_seed_ratio": 2.00,
"share_ratio_limit": 2.00,
"seed_time_ratio_limit": 7.00,
"seed_time_limit": 180,
"auto_managed": True
}
class Core(
@ -234,6 +237,16 @@ class Core(
self._on_set_max_download_speed)
self.config.register_set_function("max_upload_slots_global",
self._on_set_max_upload_slots_global)
self.config.register_set_function("share_ratio_limit",
self._on_set_share_ratio_limit)
self.config.register_set_function("seed_time_ratio_limit",
self._on_set_seed_time_ratio_limit)
self.config.register_set_function("seed_time_limit",
self._on_set_seed_time_limit)
self.config.register_set_function("max_active_downloading",
self._on_set_max_active_downloading)
self.config.register_set_function("max_active_seeding",
self._on_set_max_active_seeding)
self.config.register_change_callback(self._on_config_value_change)
# Start the AlertManager
@ -550,7 +563,7 @@ class Core(
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrents.queue.top(torrent_id):
if self.torrents.queue_top(torrent_id):
self._torrent_queue_changed()
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@ -562,7 +575,7 @@ class Core(
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrents.queue.up(torrent_id):
if self.torrents.queue_up(torrent_id):
self._torrent_queue_changed()
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@ -574,7 +587,7 @@ class Core(
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrents.queue.down(torrent_id):
if self.torrents.queue_down(torrent_id):
self._torrent_queue_changed()
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@ -584,7 +597,7 @@ class Core(
for torrent_id in torrent_ids:
try:
# If the queue method returns True, then we should emit a signal
if self.torrents.queue.bottom(torrent_id):
if self.torrents.queue_bottom(torrent_id):
self._torrent_queue_changed()
except KeyError:
log.warning("torrent_id: %s does not exist in the queue", torrent_id)
@ -755,3 +768,32 @@ class Core(
def _on_set_max_upload_slots_global(self, key, value):
log.debug("max_upload_slots_global set to %s..", value)
self.session.set_max_uploads(value)
def _on_set_share_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.share_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_ratio_limit(self, key, value):
log.debug("%s set to %s..", key, value)
self.settings.seed_time_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_limit(self, key, value):
log.debug("%s set to %s..", key, value)
# This value is stored in minutes in deluge, but libtorrent wants seconds
self.settings.seed_time_limit = int(value * 60)
self.session.set_settings(self.settings)
def _on_set_max_active_downloading(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_downloads: %s", self.settings.active_downloads)
self.settings.active_downloads = value
self.session.set_settings(self.settings)
def _on_set_max_active_seeding(self, key, value):
log.debug("%s set to %s..", key, value)
log.debug("active_seeds: %s", self.settings.active_seeds)
self.settings.active_seeds = value
self.session.set_settings(self.settings)

View File

@ -51,9 +51,7 @@ class Torrent:
log.debug("Creating torrent object %s", str(handle.info_hash()))
# Get the core config
self.config = ConfigManager("core.conf")
# Get a reference to the TorrentQueue
self.torrentqueue = component.get("TorrentQueue")
self.signals = component.get("SignalManager")
# Set the libtorrent handle
@ -73,6 +71,9 @@ class Torrent:
# Default total_uploaded to 0, this may be changed by the state
self.total_uploaded = 0
# Set default auto_managed value
self.set_auto_managed(options["auto_managed"])
# Load values from state if we have it
if state is not None:
# This is for saving the total uploaded between sessions
@ -107,15 +108,12 @@ class Torrent:
self.statusmsg = "OK"
# The torrents state
self.state = ""
#self.state = ""
self.update_state()
# The tracker status
self.tracker_status = ""
# This variable is to prevent a state change to 'Paused' when it should
# be 'Queued'
self.next_pause_is_queued = False
log.debug("Torrent object created.")
def set_tracker_status(self, status):
@ -144,6 +142,11 @@ class Torrent:
def set_save_path(self, save_path):
self.save_path = save_path
def set_auto_managed(self, auto_managed):
self.auto_managed = auto_managed
if not self.handle.is_paused():
self.handle.auto_managed(auto_managed)
def set_file_priorities(self, file_priorities):
log.debug("setting %s's file priorities: %s", self.torrent_id, file_priorities)
if 0 in self.file_priorities:
@ -182,7 +185,7 @@ class Torrent:
# Force a reannounce if there is at least 1 tracker
self.force_reannounce()
def set_state_based_on_ltstate(self):
def update_state(self):
"""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
@ -199,35 +202,26 @@ class Torrent:
self.state = "Seeding"
elif ltstate == LTSTATE["Allocating"]:
self.state = "Allocating"
if self.handle.is_paused() and len(self.handle.status().error) > 0:
# This is an error'd torrent
self.state = "Error"
self.set_status_message(self.handle.status().error)
self.handle.auto_managed(False)
elif self.handle.is_paused() and self.handle.is_auto_managed():
self.state = "Queued"
elif self.handle.is_paused() and not self.handle.is_auto_managed():
self.state = "Paused"
def set_state(self, state):
"""Accepts state strings, ie, "Paused", "Seeding", etc."""
if state not in TORRENT_STATE:
log.debug("Trying to set an invalid state %s", state)
return
if state != self.state:
if state == "Queued" and not self.handle.is_paused():
#component.get("TorrentManager").append_not_state_paused(self.torrent_id)
self.next_pause_is_queued = True
self.handle.pause()
if state == "Error" and not self.handle.is_paused():
self.next_pause_is_queued = True
if state == "Paused":
if self.next_pause_is_queued:
self.state = "Queued"
self.next_pause_is_queued = False
else:
self.state = "Paused"
self.state = state
return
log.debug("Setting %s's state to %s", self.torrent_id, state)
self.state = state
# Update the torrentqueue on any state changes
self.torrentqueue.update_queue()
def set_status_message(self, message):
self.statusmsg = message
@ -288,11 +282,6 @@ class Torrent:
'offset': file.offset
})
return ret
def get_queue_position(self):
# We augment the queue position + 1 so that the user sees a 1 indexed
# list.
return self.torrentqueue[self.torrent_id] + 1
def get_peers(self):
"""Returns a list of peers and various information about them"""
@ -326,7 +315,11 @@ class Torrent:
})
return ret
def get_queue_position(self):
"""Returns the torrents queue position"""
return self.handle.queue_position()
def get_status(self, keys):
"""Returns the status of the torrent based on the keys provided"""
# Create the full dictionary
@ -371,7 +364,11 @@ class Torrent:
"max_download_speed": self.max_download_speed,
"prioritize_first_last": self.prioritize_first_last,
"message": self.statusmsg,
"hash": self.torrent_id
"hash": self.torrent_id,
"active_time": self.status.active_time,
"seeding_time": self.status.seeding_time,
"seed_rank": self.status.seed_rank,
"is_auto_managed": self.auto_managed
}
fns = {
@ -384,9 +381,9 @@ class Torrent:
"eta": self.get_eta,
"ratio": self.get_ratio,
"file_progress": self.handle.file_progress,
"queue": self.get_queue_position,
"queue": self.handle.queue_position,
"is_seed": self.handle.is_seed,
"peers": self.get_peers
"peers": self.get_peers,
}
self.status = None
@ -419,10 +416,9 @@ class Torrent:
def pause(self):
"""Pause this torrent"""
if self.state == "Queued":
self.set_state("Paused")
return True
# Turn off auto-management so the torrent will not be unpaused by lt queueing
self.handle.auto_managed(False)
try:
self.handle.pause()
except Exception, e:
@ -443,55 +439,19 @@ class Torrent:
if self.get_ratio() >= self.config["stop_seed_ratio"]:
self.signals.emit("torrent_resume_at_stop_ratio")
return
# If the torrent is a seed and there are already the max number of seeds
# active, then just change it to a Queued state.
if self.torrentqueue.get_num_seeding() >= self.config["max_active_seeding"]:
self.set_state("Queued")
# Update the queuing order if necessary
self.torrentqueue.update_order()
return True
else:
if self.torrentqueue.get_num_downloading() >= self.config["max_active_downloading"]:
self.set_state("Queued")
# Update the queuing order if necessary
self.torrentqueue.update_order()
return True
if self.auto_managed:
# This torrent is to be auto-managed by lt queueing
self.handle.auto_managed(True)
try:
self.handle.resume()
except:
pass
if self.handle.is_finished():
self.set_state("Seeding")
else:
# Only delete the .fastresume file if we're still downloading stuff
self.delete_fastresume()
self.set_state("Downloading")
return True
elif self.state == "Queued":
if self.handle.is_finished():
if self.torrentqueue.get_num_seeding() < self.config["max_active_seeding"] or\
self.config["max_active_seeding"] == -1:
self.handle.resume()
self.state = "Seeding"
self.torrentqueue.update_order()
else:
return False
else:
if self.torrentqueue.get_num_downloading() < self.config["max_active_downloading"] or\
self.config["max_active_downloading"] == -1:
self.handle.resume()
self.state = "Downloading"
self.torrentqueue.update_order()
else:
return False
def move_storage(self, dest):
"""Move a torrent's storage location"""
@ -566,4 +526,3 @@ class Torrent:
log.debug("Unable to force recheck: %s", e)
return False
return True

View File

@ -43,7 +43,6 @@ import deluge.libtorrent as lt
import deluge.common
import deluge.component as component
from deluge.core.torrentqueue import TorrentQueue
from deluge.configmanager import ConfigManager
from deluge.core.torrent import Torrent
@ -55,7 +54,7 @@ class TorrentState:
total_uploaded,
trackers,
compact,
state,
paused,
save_path,
max_connections,
max_upload_slots,
@ -63,7 +62,8 @@ class TorrentState:
max_download_speed,
prioritize_first_last,
file_priorities,
queue
queue,
auto_managed
):
self.torrent_id = torrent_id
self.total_uploaded = total_uploaded
@ -72,7 +72,7 @@ class TorrentState:
# Options
self.compact = compact
self.state = state
self.paused = paused
self.save_path = save_path
self.max_connections = max_connections
self.max_upload_slots = max_upload_slots
@ -80,6 +80,7 @@ class TorrentState:
self.max_download_speed = max_download_speed
self.prioritize_first_last = prioritize_first_last
self.file_priorities = file_priorities
self.auto_managed = auto_managed
class TorrentManagerState:
def __init__(self):
@ -91,7 +92,7 @@ class TorrentManager(component.Component):
session for use on restart."""
def __init__(self, session, alerts):
component.Component.__init__(self, "TorrentManager", depend=["PluginManager"])
component.Component.__init__(self, "TorrentManager", interval=5000, depend=["PluginManager"])
log.debug("TorrentManager init..")
# Set the libtorrent session
self.session = session
@ -99,8 +100,6 @@ class TorrentManager(component.Component):
self.alerts = alerts
# Get the core config
self.config = ConfigManager("core.conf")
# Create the TorrentQueue object
self.queue = TorrentQueue()
# Create the torrents dict { torrent_id: Torrent }
self.torrents = {}
@ -131,7 +130,8 @@ class TorrentManager(component.Component):
self.on_alert_tracker_warning)
self.alerts.register_handler("storage_moved_alert",
self.on_alert_storage_moved)
self.alerts.register_handler("file_error_alert", self.on_alert_file_error)
self.alerts.register_handler("torrent_resumed_alert",
self.on_alert_torrent_resumed)
def start(self):
# Get the pluginmanager reference
@ -149,14 +149,17 @@ class TorrentManager(component.Component):
# Save state on shutdown
self.save_state()
for key in self.torrents.keys():
if not self.torrents[key].handle.is_paused() and \
not self.torrents[key].handle.is_finished():
if self.torrents[key].compact:
try:
self.torrents[key].pause()
except:
log.warning("Unable to pause torrent %s", key)
self.torrents[key].write_fastresume()
self.torrents[key].handle.pause()
# Wait for all alerts
self.alerts.handle_alerts(True)
def update(self):
if self.config["stop_seed_at_ratio"]:
for torrent in self.torrents:
if torrent.get_ratio() >= self.config["stop_seed_ratio"]:
torrent.pause()
if self.config["remove_seed_at_ratio"]:
self.remove(torrent.torrent_id)
def __getitem__(self, torrent_id):
"""Return the Torrent with torrent_id"""
@ -223,10 +226,16 @@ class TorrentManager(component.Component):
options["file_priorities"] = state.file_priorities
options["compact_allocation"] = state.compact
options["download_location"] = state.save_path
options["auto_managed"] = state.auto_managed
options["add_paused"] = state.paused
add_torrent_params["ti"] =\
self.get_torrent_info_from_file(
os.path.join(self.config["state_location"], state.torrent_id + ".torrent"))
if not add_torrent_params["ti"]:
log.error("Unable to add torrent!")
return
add_torrent_params["resume_data"] = self.get_resume_data_from_file(state.torrent_id)
else:
# We have a torrent_info object so we're not loading from state.
@ -240,6 +249,7 @@ class TorrentManager(component.Component):
"prioritize_first_last_pieces",
"download_location",
"add_paused",
"auto_managed"
]
if options == None:
@ -292,28 +302,15 @@ class TorrentManager(component.Component):
torrent = Torrent(handle, options, state)
# Add the torrent object to the dictionary
self.torrents[torrent.torrent_id] = torrent
if self.config["queue_new_to_top"]:
handle.queue_position_top()
component.resume("AlertManager")
# Add the torrent to the queue
if state is not None:
self.queue.insert(state.queue, torrent.torrent_id)
else:
if self.config["queue_new_to_top"]:
self.queue.insert(0, torrent.torrent_id)
else:
self.queue.append(torrent.torrent_id)
log.debug("state: %s", state)
# Resume the torrent if needed
if state == "Paused" or state == "Error":
torrent.state = "Paused"
elif state == None and not options["add_paused"]:
torrent.handle.resume()
# We set the state based on libtorrent's state
torrent.set_state_based_on_ltstate()
elif state == None and options["add_paused"]:
torrent.set_state = "Paused"
if not options["add_paused"]:
handle.resume()
handle.auto_managed(options["auto_managed"])
if save_state:
# Save the session state
@ -342,7 +339,7 @@ class TorrentManager(component.Component):
return filedump
def remove(self, torrent_id, remove_torrent, remove_data):
def remove(self, torrent_id, remove_torrent=False, remove_data=False):
"""Remove a torrent from the manager"""
try:
# Remove from libtorrent session
@ -372,9 +369,6 @@ class TorrentManager(component.Component):
# Remove the .torrent file in the state
self.torrents[torrent_id].delete_torrentfile()
# Remove the torrent from the queue
self.queue.remove(torrent_id)
# Remove the torrent from deluge's session
try:
del self.torrents[torrent_id]
@ -418,60 +412,44 @@ class TorrentManager(component.Component):
os.path.join(self.config["state_location"], "torrents.state"), "rb")
state = cPickle.load(state_file)
state_file.close()
except IOError:
except (EOFError, IOError):
log.warning("Unable to load state file.")
# Try to add the torrents in the state to the session
resume_torrents = []
# First lets clear the queue and make it the correct length.. This will
# help with inserting values at the right position.
self.queue.set_size(len(state.torrents))
# Reorder the state.torrents list to add torrents with .fastresume files
# first.
fr_first = []
# Reorder the state.torrents list to add torrents in the correct queue
# order.
ordered_state = []
for torrent_state in state.torrents:
if os.path.exists(os.path.join(
self.config["state_location"],
torrent_state.torrent_id, ".fastresume")):
fr_first.insert(0, torrent_state)
else:
fr_first.append(torrent_state)
for t in ordered_state:
if torrent_state.queue < t.queue:
ordered_state.insert(0, torrent_state)
break
ordered_state.append(torrent_state)
for torrent_state in fr_first:
for torrent_state in ordered_state:
try:
# We need to resume all non-add_paused torrents after plugin hook
if torrent_state.state not in ["Paused", "Queued", "Error"]:
resume_torrents.append(torrent_state.torrent_id)
self.add(
state=torrent_state,
save_state=False)
self.add(state=torrent_state, save_state=False)
except AttributeError, e:
log.error("Torrent state file is either corrupt or incompatible!")
add_paused = {}
break
# Run the post_session_load plugin hooks
self.plugins.run_post_session_load()
# Resume any torrents that need to be resumed
for torrent_id in resume_torrents:
self.torrents[torrent_id].handle.resume()
self.torrents[torrent_id].set_state_based_on_ltstate()
def save_state(self):
"""Save the state of the TorrentManager to the torrents.state file"""
state = TorrentManagerState()
# Create the state for each Torrent and append to the list
for torrent in self.torrents.values():
paused = False
if torrent.state == "Paused":
paused = True
torrent_state = TorrentState(
torrent.torrent_id,
torrent.get_status(["total_uploaded"])["total_uploaded"],
torrent.trackers,
torrent.compact,
torrent.state,
paused,
torrent.save_path,
torrent.max_connections,
torrent.max_upload_slots,
@ -479,7 +457,8 @@ class TorrentManager(component.Component):
torrent.max_download_speed,
torrent.prioritize_first_last,
torrent.file_priorities,
torrent.get_status(["queue"])["queue"] - 1 # We subtract 1 due to augmentation
torrent.get_queue_position(),
torrent.auto_managed
)
state.torrents.append(torrent_state)
@ -496,7 +475,39 @@ class TorrentManager(component.Component):
# We return True so that the timer thread will continue
return True
def queue_top(self, torrent_id):
"""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
def queue_up(self, torrent_id):
"""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
def queue_down(self, torrent_id):
"""Queue torrent down one position"""
if self.torrents[torrent_id].get_queue_position() == (len(self.torrents) - 1):
return False
self.torrents[torrent_id].handle.queue_position_down()
return True
def queue_bottom(self, torrent_id):
"""Queue torrent to bottom"""
if self.torrents[torrent_id].get_queue_position() == (len(self.torrents) - 1):
return False
self.torrents[torrent_id].handle.queue_position_bottom()
return True
def on_set_max_connections_per_torrent(self, key, value):
"""Sets the per-torrent connection limit"""
log.debug("max_connections_per_torrent set to %s..", value)
@ -508,7 +519,7 @@ class TorrentManager(component.Component):
log.debug("max_upload_slots_per_torrent set to %s..", value)
for key in self.torrents.keys():
self.torrents[key].set_max_upload_slots(value)
def on_set_max_upload_speed_per_torrent(self, key, value):
log.debug("max_upload_speed_per_torrent set to %s..", value)
for key in self.torrents.keys():
@ -525,20 +536,7 @@ class TorrentManager(component.Component):
# Get the torrent_id
torrent_id = str(alert.handle.info_hash())
log.debug("%s is finished..", torrent_id)
# Queue to bottom if enabled
if alert.msg() == "torrent has finished downloading":
if self.config["queue_finished_to_bottom"]:
self.queue.bottom(torrent_id)
# Set the torrent state if not paused
if not self.torrents[torrent_id].handle.is_paused():
if self.queue.get_num_seeding() < self.config["max_active_seeding"] or\
self.config["max_active_seeding"] == -1:
self.torrents[torrent_id].set_state("Seeding")
else:
self.torrents[torrent_id].set_state("Queued")
self.torrents[torrent_id].update_state()
# Write the fastresume file
self.torrents[torrent_id].write_fastresume()
@ -547,8 +545,7 @@ class TorrentManager(component.Component):
# Get the torrent_id
torrent_id = str(alert.handle.info_hash())
# Set the torrent state
log.debug("Setting state 'Paused'..")
self.torrents[torrent_id].set_state("Paused")
self.torrents[torrent_id].update_state()
component.get("SignalManager").emit("torrent_paused", torrent_id)
# Write the fastresume file
@ -559,12 +556,8 @@ class TorrentManager(component.Component):
# Get the torrent_id
torrent_id = str(alert.handle.info_hash())
# Set the torrent state
if not self.torrents[torrent_id].handle.is_paused():
if self.torrents[torrent_id].handle.is_finished():
self.torrents[torrent_id].set_state("Seeding")
else:
self.torrents[torrent_id].set_state("Downloading")
self.torrents[torrent_id].update_state()
def on_alert_tracker_reply(self, alert):
log.debug("on_alert_tracker_reply")
# Get the torrent_id
@ -622,16 +615,16 @@ class TorrentManager(component.Component):
def on_alert_storage_moved(self, alert):
log.debug("on_alert_storage_moved")
log.debug("save_path: %s", alert.handle.save_path())
# Get the torrent_id
torrent_id = str(alert.handle.info_hash())
try:
log.debug("save_path2: %s", self.torrents[torrent_id].handle.save_path())
self.torrents[torrent_id].set_save_path(alert.handle.save_path())
except KeyError:
log.debug("torrent_id doesn't exist.")
def on_alert_file_error(self, alert):
log.debug("on_alert_file_error")
def on_alert_torrent_resumed(self, alert):
log.debug("on_alert_torrent_resumed")
torrent_id = str(alert.handle.info_hash())
self.torrents[torrent_id].set_state("Error")
self.torrents[torrent_id].set_status_message(str(alert.msg()))
self.torrents[torrent_id].update_state()

View File

@ -1,331 +0,0 @@
#
# torrentqueue.py
#
# Copyright (C) 2007, 2008 Andrew Resch ('andar') <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
import deluge.component as component
import deluge.common
from deluge.configmanager import ConfigManager
from deluge.log import LOG as log
class TorrentQueue(component.Component):
def __init__(self):
component.Component.__init__(self, "TorrentQueue", depend=["TorrentManager"])
# This is a list of torrent_ids in the queueing order
self.queue = []
# These lists keep track of the torrent states
self.seeding = []
self.queued_seeding = []
self.downloading = []
self.queued_downloading = []
self.torrents = component.get("TorrentManager")
self.config = ConfigManager("core.conf")
# Register config set functions
self.config.register_set_function("max_active_seeding",
self._on_set_max_active_seeding, False)
self.config.register_set_function("max_active_downloading",
self._on_set_max_active_downloading, False)
def update(self):
# If we're not checking share ratios, just return
if not self.config["stop_seed_at_ratio"]:
return
stop_ratio = self.config["stop_seed_ratio"]
for torrent_id in self.torrents.get_torrent_list():
if self.torrents[torrent_id].handle.is_finished():
if self.torrents[torrent_id].get_ratio() >= stop_ratio:
# This torrent is at or exceeding the stop ratio so we need to
# pause or remove it from the session.
if self.config["remove_seed_at_ratio"]:
self.torrents.remove(torrent_id, False, False)
else:
self.torrents[torrent_id].pause()
def update_queue(self):
# Updates the queueing order and max active states
# This only gets called when necessary
self.update_state_lists()
self.update_order()
self.update_max_active()
def update_state_lists(self):
# Get ordered lists of torrents
self.seeding = []
self.queued_seeding = []
self.downloading = []
self.queued_downloading = []
for torrent_id in self.torrents.get_torrent_list():
if self.torrents[torrent_id].state == "Seeding":
self.seeding.append((self.queue.index(torrent_id), torrent_id))
elif self.torrents[torrent_id].state == "Downloading":
self.downloading.append((self.queue.index(torrent_id), torrent_id))
elif self.torrents[torrent_id].state == "Queued":
if self.torrents[torrent_id].handle.is_finished():
self.queued_seeding.append((self.queue.index(torrent_id), torrent_id))
else:
self.queued_downloading.append((self.queue.index(torrent_id), torrent_id))
# We need to sort these lists by queue position
self.seeding.sort()
self.downloading.sort()
self.queued_downloading.sort()
self.queued_seeding.sort()
#log.debug("total seeding: %s", len(self.seeding))
#log.debug("total downloading: %s", len(self.downloading))
#log.debug("queued seeding: %s", len(self.queued_seeding))
#log.debug("queued downloading: %s", len(self.queued_downloading))
def update_order(self):
# This will queue/resume torrents if the queueing order changes
#try:
# log.debug("max(seeding): %s", max(self.seeding)[0])
# log.debug("min(queued_seeding): %s", min(self.queued_seeding)[0])
#except:
# pass
#log.debug("queued seeding: %s", self.queued_seeding)
#log.debug("queued downloading: %s", self.queued_downloading)
if self.seeding != [] and self.queued_seeding != []:
if min(self.queued_seeding)[0] < max(self.seeding)[0]:
num_to_queue = max(self.seeding)[0] - min(self.queued_seeding)[0]
log.debug("queueing: %s", self.seeding[-num_to_queue:])
for (pos, torrent_id) in self.seeding[-num_to_queue:]:
self.torrents[torrent_id].set_state("Queued")
if self.downloading != [] and self.queued_downloading != []:
if min(self.queued_downloading)[0] < max(self.downloading)[0]:
num_to_queue = max(self.downloading)[0] - min(self.queued_downloading)[0]
log.debug("queueing: %s", self.downloading[-num_to_queue:])
for (pos, torrent_id) in self.downloading[-num_to_queue:]:
self.torrents[torrent_id].set_state("Queued")
def update_max_active(self):
if self.config["max_active_seeding"] > -1:
log.debug("max_active_seeding: %s", self.config["max_active_seeding"])
if len(self.seeding) > self.config["max_active_seeding"]:
# We need to queue some more torrents because we're over the active limit
num_to_queue = len(self.seeding) - self.config["max_active_seeding"]
for (pos, torrent_id) in self.seeding[-num_to_queue:]:
self.torrents[torrent_id].set_state("Queued")
else:
# We need to unqueue more torrents if possible
num_to_unqueue = self.config["max_active_seeding"] - len(self.seeding)
to_unqueue = []
if num_to_unqueue <= len(self.queued_seeding):
to_unqueue = self.queued_seeding[:num_to_unqueue]
else:
to_unqueue = self.queued_seeding
for (pos, torrent_id) in to_unqueue:
self.torrents[torrent_id].resume()
else:
# The max_active_seeding is set to unlimited, so lets make sure
# all queued seeds are activated.
for (pos, torrent_id) in self.queued_seeding:
self.torrents[torrent_id].resume()
if self.config["max_active_downloading"] > -1:
if len(self.downloading) > self.config["max_active_downloading"]:
num_to_queue = len(self.downloading) - self.config["max_active_downloading"]
for (pos, torrent_id) in self.downloading[-num_to_queue:]:
self.torrents[torrent_id].set_state("Queued")
else:
# We need to unqueue more torrents if possible
num_to_unqueue = self.config["max_active_downloading"] - len(self.downloading)
to_unqueue = []
if num_to_unqueue <= len(self.queued_downloading):
to_unqueue = self.queued_downloading[:num_to_unqueue]
else:
to_unqueue = self.queued_downloading
for (pos, torrent_id) in to_unqueue:
self.torrents[torrent_id].resume()
else:
# Unlimited downloading torrents set
for (pos, torrent_id) in self.queued_downloading:
self.torrents[torrent_id].resume()
def set_size(self, size):
"""Clear and set the self.queue list to the length of size"""
log.debug("Setting queue size to %s..", size)
self.queue = [None] * size
def get_num_seeding(self):
self.update_state_lists()
return len(self.seeding)
def get_num_downloading(self):
self.update_state_lists()
return len(self.downloading)
def __getitem__(self, torrent_id):
"""Return the queue position of the torrent_id"""
try:
return self.queue.index(torrent_id)
except ValueError:
return -1
def append(self, torrent_id):
"""Append torrent_id to the bottom of the queue"""
log.debug("Append torrent %s to queue..", torrent_id)
self.queue.append(torrent_id)
return self.queue.index(torrent_id)
def prepend(self, torrent_id):
"""Prepend torrent_id to the top of the queue"""
log.debug("Prepend torrent %s to queue..", torrent_id)
self.queue.insert(0, torrent_id)
return self.queue.index(torrent_id)
def insert(self, position, torrent_id):
"""Inserts torrent_id at position in queue."""
log.debug("Inserting torrent %s at position %s..", torrent_id, position)
if position < 0:
for q in self.queue:
if q == None:
self.queue[self.queue.index(q)] = torrent_id
return self.queue.index(q)
self.queue.append(torrent_id)
return self.queue.index(torrent_id)
else:
if position > (len(self.queue) - 1):
self.queue.insert(position, torrent_id)
try:
value = self.queue[position]
except KeyError, IndexError:
self.queue.insert(position, torrent_id)
return position
if value == None:
self.queue[position] = torrent_id
else:
self.queue.insert(position, torrent_id)
return position
def remove(self, torrent_id):
"""Removes torrent_id from the list"""
log.debug("Remove torrent %s from queue..", torrent_id)
self.queue.remove(torrent_id)
def up(self, torrent_id):
"""Move torrent_id up one in the queue"""
if torrent_id not in self.queue:
# Raise KeyError if the torrent_id is not in the queue
raise KeyError
log.debug("Move torrent %s up..", torrent_id)
# Get the index of the torrent_id
index = self.queue.index(torrent_id)
# Can't queue up if torrent is already at top
if index is 0:
return False
# Pop and insert the torrent_id at index - 1
self.queue.insert(index - 1, self.queue.pop(index))
self.update_queue()
return True
def top(self, torrent_id):
"""Move torrent_id to top of the queue"""
if torrent_id not in self.queue:
# Raise KeyError if the torrent_id is not in the queue
raise KeyError
log.debug("Move torrent %s to top..", torrent_id)
# Get the index of the torrent_id
index = self.queue.index(torrent_id)
# Can't queue up if torrent is already at top
if index is 0:
return False
self.queue.insert(0, self.queue.pop(index))
self.update_queue()
return True
def down(self, torrent_id):
"""Move torrent_id down one in the queue"""
if torrent_id not in self.queue:
# Raise KeyError if torrent_id is not in the queue
raise KeyError
log.debug("Move torrent %s down..", torrent_id)
# Get the index of the torrent_id
index = self.queue.index(torrent_id)
# Can't queue down of torrent_id is at bottom
if index is len(self.queue) - 1:
return False
# Pop and insert the torrent_id at index + 1
self.queue.insert(index + 1, self.queue.pop(index))
self.update_queue()
return True
def bottom(self, torrent_id):
"""Move torrent_id to bottom of the queue"""
if torrent_id not in self.queue:
# Raise KeyError if torrent_id is not in the queue
raise KeyError
log.debug("Move torrent %s to bottom..", torrent_id)
# Get the index of the torrent_id
index = self.queue.index(torrent_id)
# Can't queue down of torrent_id is at bottom
if index is len(self.queue) - 1:
return False
# Pop and append the torrent_id
self.append(self.queue.pop(index))
self.update_queue()
return True
def _on_set_max_active_seeding(self, key, value):
self.update_queue()
def _on_set_max_active_downloading(self, key, value):
self.update_queue()

View File

@ -194,7 +194,7 @@ class FilesTab(Tab):
state_file = open(os.path.join(config_location, filename), "rb")
state = cPickle.load(state_file)
state_file.close()
except IOError, e:
except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e)
if state == None:

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Mon Jun 2 18:29:37 2008 -->
<!--Generated with glade3 3.4.4 on Thu Jun 12 01:03:18 2008 -->
<glade-interface>
<widget class="GtkDialog" id="pref_dialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@ -2153,40 +2153,6 @@ Disabled</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="column_spacing">10</property>
<child>
<widget class="GtkSpinButton" id="spin_downloading">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">1</property>
<property name="adjustment">-1 -1 9999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_seeding">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">1</property>
<property name="adjustment">-1 -1 9999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label42">
<property name="visible">True</property>
@ -2195,11 +2161,25 @@ Disabled</property>
<property name="label" translatable="yes">Total active downloading:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_downloading">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">1</property>
<property name="adjustment">0 -1 9999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label48">
<property name="visible">True</property>
@ -2208,9 +2188,29 @@ Disabled</property>
<property name="label" translatable="yes">Total active seeding:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_seeding">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">1</property>
<property name="adjustment">0 -1 9999 1 10 10</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
</widget>
</child>
</widget>
@ -2251,13 +2251,93 @@ Disabled</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child>
<widget class="GtkCheckButton" id="chk_finished_bottom">
<widget class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Queue newly finished torrents to bottom</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">10</property>
<child>
<widget class="GtkLabel" id="label53">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Share Ratio Limit:</property>
</widget>
<packing>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label54">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Seed Time Ratio:</property>
</widget>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label55">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Seed Time (m):</property>
</widget>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_share_ratio_limit">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">6</property>
<property name="xalign">1</property>
<property name="adjustment">1.5 -1 100 0.10000000000000001 10 10</property>
<property name="digits">2</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_seed_time_ratio_limit">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">6</property>
<property name="xalign">1</property>
<property name="adjustment">6 -1 100 0.10000000000000001 10 10</property>
<property name="digits">2</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options"></property>
</packing>
</child>
<child>
<widget class="GtkSpinButton" id="spin_seed_time_limit">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">6</property>
<property name="xalign">1</property>
<property name="adjustment">6 -1 100 1 10 10</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"></property>
</packing>
</child>
</widget>
</child>
<child>
@ -2286,7 +2366,7 @@ Disabled</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="xalign">1</property>
<property name="adjustment">1 0.5 100 0.10000000000000001 1 1</property>
<property name="adjustment">2 0.5 100 0.10000000000000001 1 1</property>
<property name="digits">2</property>
<property name="numeric">True</property>
</widget>

View File

@ -189,7 +189,7 @@ class ListView:
state_file = open(os.path.join(config_location, filename), "rb")
state = cPickle.load(state_file)
state_file.close()
except IOError, e:
except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e)
# Keep the state in self.state so we can access it as we add new columns

View File

@ -83,8 +83,6 @@ class MainWindow(component.Component):
except:
pass
# Load the state prior to showing
self.load_window_state()
self.window.show()
def hide(self):

View File

@ -176,7 +176,7 @@ class PeersTab(Tab):
state_file = open(os.path.join(config_location, filename), "rb")
state = cPickle.load(state_file)
state_file.close()
except IOError, e:
except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e)
if state == None:

View File

@ -238,7 +238,10 @@ class Preferences(component.Component):
"spin_seeding": ("value", self.core_config["max_active_seeding"]),
"spin_downloading": ("value", self.core_config["max_active_downloading"]),
"chk_queue_new_top": ("active", self.core_config["queue_new_to_top"]),
"chk_finished_bottom": ("active", self.core_config["queue_finished_to_bottom"]),
"spin_share_ratio_limit": ("value", self.core_config["share_ratio_limit"]),
"spin_seed_time_ratio_limit": \
("value", self.core_config["seed_time_ratio_limit"]),
"spin_seed_time_limit": ("value", self.core_config["seed_time_limit"]),
"chk_seed_ratio": ("active", self.core_config["stop_seed_at_ratio"]),
"spin_share_ratio": ("value", self.core_config["stop_seed_ratio"]),
"chk_remove_ratio": ("active", self.core_config["remove_seed_at_ratio"])
@ -324,10 +327,12 @@ class Preferences(component.Component):
"spin_seeding",
"spin_downloading",
"chk_queue_new_top",
"chk_finished_bottom",
"chk_seed_ratio",
"spin_share_ratio",
"chk_remove_ratio"
"chk_remove_ratio",
"spin_share_ratio_limit",
"spin_seed_time_ratio_limit",
"spin_seed_time_limit"
]
# We don't appear to be connected to a daemon
for key in core_widget_list:
@ -500,14 +505,18 @@ class Preferences(component.Component):
self.glade.get_widget("spin_seeding").get_value_as_int()
new_core_config["max_active_downloading"] = \
self.glade.get_widget("spin_downloading").get_value_as_int()
new_core_config["queue_finished_to_bottom"] = \
self.glade.get_widget("chk_finished_bottom").get_active()
new_core_config["stop_seed_at_ratio"] = \
self.glade.get_widget("chk_seed_ratio").get_active()
new_core_config["remove_seed_at_ratio"] = \
self.glade.get_widget("chk_remove_ratio").get_active()
new_core_config["stop_seed_ratio"] = \
self.glade.get_widget("spin_share_ratio").get_value()
new_core_config["share_ratio_limit"] = \
self.glade.get_widget("spin_share_ratio_limit").get_value()
new_core_config["seed_time_ratio_limit"] = \
self.glade.get_widget("spin_seed_time_ratio_limit").get_value()
new_core_config["seed_time_limit"] = \
self.glade.get_widget("spin_seed_time_limit").get_value()
# GtkUI
for key in new_gtkui_config.keys():

View File

@ -37,6 +37,7 @@ from deluge.ui.client import aclient as client
import deluge.component as component
import deluge.common
from deluge.ui.gtkui.torrentdetails import Tab
from deluge.log import LOG as log
def fpeer_sized(first, second):
return "%s (%s)" % (deluge.common.fsize(first), deluge.common.fsize(second))
@ -79,6 +80,10 @@ class StatisticsTab(Tab):
(glade.get_widget("summary_share_ratio"), fratio, ("ratio",)),
(glade.get_widget("summary_tracker_status"), None, ("tracker_status",)),
(glade.get_widget("summary_next_announce"), deluge.common.ftime, ("next_announce",)),
(glade.get_widget("summary_active_time"), deluge.common.ftime, ("active_time",)),
(glade.get_widget("summary_seed_time"), deluge.common.ftime, ("seeding_time",)),
(glade.get_widget("summary_seed_rank"), str, ("seed_rank",)),
(glade.get_widget("summary_auto_managed"), str, ("is_auto_managed",)),
(glade.get_widget("progressbar"), fpcnt, ("progress",))
]
@ -100,7 +105,8 @@ class StatisticsTab(Tab):
"upload_payload_rate", "num_peers", "num_seeds", "total_peers",
"total_seeds", "eta", "ratio", "next_announce",
"tracker_status", "max_connections", "max_upload_slots",
"max_upload_speed", "max_download_speed"]
"max_upload_speed", "max_download_speed", "active_time",
"seeding_time", "seed_rank", "is_auto_managed"]
client.get_torrent_status(
self._on_get_torrent_status, selected, status_keys)

View File

@ -299,7 +299,7 @@ class TorrentDetails(component.Component):
state_file = open(os.path.join(config_location, filename), "rb")
state = cPickle.load(state_file)
state_file.close()
except IOError, e:
except (EOFError, IOError), e:
log.warning("Unable to load state file: %s", e)
return state

View File

@ -92,7 +92,14 @@ def cell_data_progress(column, cell, model, row, data):
textstr = textstr + " %.2f%%" % value
if cell.get_property("text") != textstr:
cell.set_property("text", textstr)
def cell_data_queue(column, cell, model, row, data):
value = model.get_value(row, data)
if value < 0:
cell.set_property("text", "")
else:
cell.set_property("text", value + 1)
class TorrentView(listview.ListView, component.Component):
"""TorrentView handles the listing of torrents."""
def __init__(self):
@ -116,7 +123,7 @@ class TorrentView(listview.ListView, component.Component):
# Add the columns to the listview
self.add_text_column("torrent_id", hidden=True)
self.add_bool_column("filter", hidden=True)
self.add_text_column("#", col_type=int, status_field=["queue"])
self.add_func_column("#", cell_data_queue, [int], status_field=["queue"])
self.add_texticon_column(_("Name"), status_field=["state", "name"],
function=cell_data_statusicon)
self.add_func_column(_("Size"),

View File

@ -74,6 +74,7 @@ void bind_torrent_status()
.def_readonly("seeding_time", &torrent_status::seeding_time)
.def_readonly("seed_rank", &torrent_status::seed_rank)
.def_readonly("last_scrape", &torrent_status::last_scrape)
.def_readonly("error", &torrent_status::error)
;
enum_<torrent_status::state_t>("states")

View File

@ -207,20 +207,11 @@ namespace libtorrent
int num_blocks;
// the pointers to the block data
boost::shared_array<char*> blocks;
#ifndef NDEBUG
~cached_piece_entry()
{
TORRENT_ASSERT(storage == 0);
}
#endif
};
typedef boost::recursive_mutex mutex_t;
typedef std::list<cached_piece_entry> cache_t;
char* allocate_buffer(mutex_t::scoped_lock& l);
void free_buffer(char* buf, mutex_t::scoped_lock& l);
// cache operations
cache_t::iterator find_cached_piece(
cache_t& cache, disk_io_job const& j
@ -228,7 +219,7 @@ namespace libtorrent
// write cache operations
void flush_oldest_piece(mutex_t::scoped_lock& l);
void flush_expired_pieces(mutex_t::scoped_lock& l);
void flush_expired_pieces();
void flush_and_remove(cache_t::iterator i, mutex_t::scoped_lock& l);
void flush(cache_t::iterator i, mutex_t::scoped_lock& l);
void cache_block(disk_io_job& j, mutex_t::scoped_lock& l);
@ -242,14 +233,18 @@ namespace libtorrent
bool make_room(int num_blocks
, cache_t::iterator ignore
, mutex_t::scoped_lock& l);
int try_read_from_cache(disk_io_job const& j, mutex_t::scoped_lock& l);
int try_read_from_cache(disk_io_job const& j);
mutable mutex_t m_mutex;
// this mutex only protects m_jobs, m_queue_buffer_size
// and m_abort
mutable mutex_t m_queue_mutex;
boost::condition m_signal;
bool m_abort;
std::list<disk_io_job> m_jobs;
size_type m_queue_buffer_size;
// this protects the piece cache and related members
mutable mutex_t m_piece_mutex;
// write cache
cache_t m_pieces;
@ -280,6 +275,8 @@ namespace libtorrent
bool m_coalesce_reads;
bool m_use_read_cache;
// this only protects the pool allocator
mutable mutex_t m_pool_mutex;
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
// memory pool for read and write operations
// and disk cache

View File

@ -129,7 +129,7 @@ namespace libtorrent
, resume_data(0)
, storage_mode(storage_mode_sparse)
, paused(true)
, auto_managed(false)
, auto_managed(true)
, duplicate_is_error(false)
, storage(sc)
, userdata(0)

View File

@ -78,7 +78,7 @@ namespace libtorrent
void disk_io_thread::join()
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_queue_mutex);
disk_io_job j;
j.action = disk_io_job::abort_thread;
m_jobs.insert(m_jobs.begin(), j);
@ -90,7 +90,7 @@ namespace libtorrent
void disk_io_thread::get_cache_info(sha1_hash const& ih, std::vector<cached_piece_info>& ret) const
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
ret.clear();
ret.reserve(m_pieces.size());
for (cache_t::const_iterator i = m_pieces.begin()
@ -111,20 +111,20 @@ namespace libtorrent
cache_status disk_io_thread::status() const
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
return m_cache_stats;
}
void disk_io_thread::set_cache_size(int s)
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
TORRENT_ASSERT(s >= 0);
m_cache_size = s;
}
void disk_io_thread::set_cache_expiry(int ex)
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
TORRENT_ASSERT(ex > 0);
m_cache_expiry = ex;
}
@ -132,7 +132,7 @@ namespace libtorrent
// aborts read operations
void disk_io_thread::stop(boost::intrusive_ptr<piece_manager> s)
{
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_queue_mutex);
// read jobs are aborted, write and move jobs are syncronized
for (std::list<disk_io_job>::iterator i = m_jobs.begin();
i != m_jobs.end();)
@ -200,10 +200,12 @@ namespace libtorrent
return cache.end();
}
void disk_io_thread::flush_expired_pieces(mutex_t::scoped_lock& l)
void disk_io_thread::flush_expired_pieces()
{
ptime now = time_now();
mutex_t::scoped_lock l(m_piece_mutex);
INVARIANT_CHECK;
for (;;)
{
@ -226,15 +228,12 @@ namespace libtorrent
for (int i = 0; i < blocks_in_piece; ++i)
{
if (p.blocks[i] == 0) continue;
free_buffer(p.blocks[i], l);
free_buffer(p.blocks[i]);
p.blocks[i] = 0;
--p.num_blocks;
--m_cache_stats.cache_size;
--m_cache_stats.read_cache_size;
}
l.unlock();
p.storage = 0;
l.lock();
}
bool disk_io_thread::clear_oldest_read_piece(
@ -330,7 +329,7 @@ namespace libtorrent
offset += m_block_size;
buffer_size += block_size;
}
free_buffer(p.blocks[i], l);
free_buffer(p.blocks[i]);
p.blocks[i] = 0;
TORRENT_ASSERT(p.num_blocks > 0);
--p.num_blocks;
@ -343,9 +342,6 @@ namespace libtorrent
for (int i = 0; i < blocks_in_piece; ++i)
TORRENT_ASSERT(p.blocks[i] == 0);
#endif
l.unlock();
p.storage = 0;
l.lock();
}
void disk_io_thread::cache_block(disk_io_job& j, mutex_t::scoped_lock& l)
@ -368,9 +364,6 @@ namespace libtorrent
p.blocks[block] = j.buffer;
++m_cache_stats.cache_size;
m_pieces.push_back(p);
#ifndef NDEBUG
p.storage = 0;
#endif
}
// fills a piece with data from disk, returns the total number of bytes
@ -388,7 +381,7 @@ namespace libtorrent
// stop allocating and don't read more than
// what we've allocated now
if (p.blocks[i]) break;
p.blocks[i] = allocate_buffer(l);
p.blocks[i] = allocate_buffer();
// the allocation failed, break
if (p.blocks[i] == 0) break;
@ -484,9 +477,6 @@ namespace libtorrent
else
m_read_pieces.push_back(p);
#ifndef NDEBUG
p.storage = 0;
#endif
return ret;
}
@ -509,7 +499,7 @@ namespace libtorrent
if (p.blocks[k])
{
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
TORRENT_ASSERT(m_pool.is_from(p.blocks[k]));
TORRENT_ASSERT(is_disk_buffer(p.blocks[k]));
#endif
++blocks;
}
@ -533,7 +523,7 @@ namespace libtorrent
if (p.blocks[k])
{
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
TORRENT_ASSERT(m_pool.is_from(p.blocks[k]));
TORRENT_ASSERT(is_disk_buffer(p.blocks[k]));
#endif
++blocks;
}
@ -551,10 +541,11 @@ namespace libtorrent
}
#endif
int disk_io_thread::try_read_from_cache(disk_io_job const& j, mutex_t::scoped_lock& l)
int disk_io_thread::try_read_from_cache(disk_io_job const& j)
{
TORRENT_ASSERT(j.buffer);
mutex_t::scoped_lock l(m_piece_mutex);
if (!m_use_read_cache) return -2;
cache_t::iterator p
@ -624,7 +615,7 @@ namespace libtorrent
TORRENT_ASSERT(!j.callback);
TORRENT_ASSERT(j.storage);
TORRENT_ASSERT(j.buffer_size <= m_block_size);
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_queue_mutex);
#ifndef NDEBUG
if (j.action == disk_io_job::write)
{
@ -696,7 +687,7 @@ namespace libtorrent
#ifdef TORRENT_DISABLE_POOL_ALLOCATOR
return true;
#else
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_pool_mutex);
return m_pool.is_from(buffer);
#endif
}
@ -704,18 +695,7 @@ namespace libtorrent
char* disk_io_thread::allocate_buffer()
{
mutex_t::scoped_lock l(m_mutex);
return allocate_buffer(l);
}
void disk_io_thread::free_buffer(char* buf)
{
mutex_t::scoped_lock l(m_mutex);
free_buffer(buf, l);
}
char* disk_io_thread::allocate_buffer(mutex_t::scoped_lock& l)
{
mutex_t::scoped_lock l(m_pool_mutex);
#ifdef TORRENT_STATS
++m_allocations;
#endif
@ -726,8 +706,9 @@ namespace libtorrent
#endif
}
void disk_io_thread::free_buffer(char* buf, mutex_t::scoped_lock& l)
void disk_io_thread::free_buffer(char* buf)
{
mutex_t::scoped_lock l(m_pool_mutex);
#ifdef TORRENT_STATS
--m_allocations;
#endif
@ -745,12 +726,15 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
m_log << log_time() << " idle" << std::endl;
#endif
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock jl(m_queue_mutex);
while (m_jobs.empty() && !m_abort)
m_signal.wait(l);
m_signal.wait(jl);
if (m_abort && m_jobs.empty())
{
jl.unlock();
mutex_t::scoped_lock l(m_piece_mutex);
// flush all disk caches
for (cache_t::iterator i = m_pieces.begin()
, end(m_pieces.end()); i != end; ++i)
@ -758,10 +742,8 @@ namespace libtorrent
for (cache_t::iterator i = m_read_pieces.begin()
, end(m_read_pieces.end()); i != end; ++i)
free_piece(*i, l);
l.unlock();
m_pieces.clear();
m_read_pieces.clear();
l.lock();
return;
}
@ -777,40 +759,14 @@ namespace libtorrent
disk_io_job j = m_jobs.front();
m_jobs.pop_front();
if (j.action == disk_io_job::abort_thread)
{
m_abort = true;
for (std::list<disk_io_job>::iterator i = m_jobs.begin();
i != m_jobs.end();)
{
if (i->action == disk_io_job::read)
{
if (i->callback) m_ios.post(bind(i->callback, -1, *i));
m_jobs.erase(i++);
continue;
}
if (i->action == disk_io_job::check_files)
{
if (i->callback) m_ios.post(bind(i->callback
, piece_manager::disk_check_aborted, *i));
m_jobs.erase(i++);
continue;
}
++i;
}
continue;
}
m_queue_buffer_size -= j.buffer_size;
jl.unlock();
flush_expired_pieces(l);
l.unlock();
flush_expired_pieces();
int ret = 0;
TORRENT_ASSERT(j.storage);
TORRENT_ASSERT(j.storage || j.action == disk_io_job::abort_thread);
#ifdef TORRENT_DISK_STATS
ptime start = time_now();
#endif
@ -820,6 +776,31 @@ namespace libtorrent
switch (j.action)
{
case disk_io_job::abort_thread:
{
mutex_t::scoped_lock jl(m_queue_mutex);
m_abort = true;
for (std::list<disk_io_job>::iterator i = m_jobs.begin();
i != m_jobs.end();)
{
if (i->action == disk_io_job::read)
{
if (i->callback) m_ios.post(bind(i->callback, -1, *i));
m_jobs.erase(i++);
continue;
}
if (i->action == disk_io_job::check_files)
{
if (i->callback) m_ios.post(bind(i->callback
, piece_manager::disk_check_aborted, *i));
m_jobs.erase(i++);
continue;
}
++i;
}
break;
}
case disk_io_job::read:
{
std::string const& error_string = j.storage->error();
@ -837,7 +818,6 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
m_log << log_time() << " read " << j.buffer_size << std::endl;
#endif
mutex_t::scoped_lock l(m_mutex);
INVARIANT_CHECK;
TORRENT_ASSERT(j.buffer == 0);
j.buffer = allocate_buffer();
@ -850,7 +830,7 @@ namespace libtorrent
}
disk_buffer_holder read_holder(*this, j.buffer);
ret = try_read_from_cache(j, l);
ret = try_read_from_cache(j);
// -2 means there's no space in the read cache
// or that the read cache is disabled
@ -864,7 +844,6 @@ namespace libtorrent
}
else if (ret == -2)
{
l.unlock();
ret = j.storage->read_impl(j.buffer, j.piece, j.offset
, j.buffer_size);
if (ret < 0)
@ -874,7 +853,6 @@ namespace libtorrent
j.storage->clear_error();
break;
}
l.lock();
++m_cache_stats.blocks_read;
}
read_holder.release();
@ -897,7 +875,7 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
m_log << log_time() << " write " << j.buffer_size << std::endl;
#endif
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
INVARIANT_CHECK;
cache_t::iterator p
= find_cached_piece(m_pieces, j, l);
@ -934,8 +912,7 @@ namespace libtorrent
#ifdef TORRENT_DISK_STATS
m_log << log_time() << " hash" << std::endl;
#endif
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
INVARIANT_CHECK;
cache_t::iterator i
@ -993,8 +970,8 @@ namespace libtorrent
m_log << log_time() << " release" << std::endl;
#endif
TORRENT_ASSERT(j.buffer == 0);
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
INVARIANT_CHECK;
for (cache_t::iterator i = m_pieces.begin(); i != m_pieces.end();)
@ -1009,10 +986,13 @@ namespace libtorrent
++i;
}
}
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
m_pool.release_memory();
#endif
l.unlock();
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
{
mutex_t::scoped_lock l(m_pool_mutex);
m_pool.release_memory();
}
#endif
ret = j.storage->release_files_impl();
if (ret != 0)
{
@ -1028,8 +1008,8 @@ namespace libtorrent
m_log << log_time() << " delete" << std::endl;
#endif
TORRENT_ASSERT(j.buffer == 0);
mutex_t::scoped_lock l(m_mutex);
mutex_t::scoped_lock l(m_piece_mutex);
INVARIANT_CHECK;
cache_t::iterator i = std::remove_if(
@ -1042,15 +1022,18 @@ namespace libtorrent
for (int j = 0; j < blocks_in_piece; ++j)
{
if (k->blocks[j] == 0) continue;
free_buffer(k->blocks[j], l);
free_buffer(k->blocks[j]);
k->blocks[j] = 0;
}
}
m_pieces.erase(i, m_pieces.end());
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
m_pool.release_memory();
#endif
l.unlock();
#ifndef TORRENT_DISABLE_POOL_ALLOCATOR
{
mutex_t::scoped_lock l(m_pool_mutex);
m_pool.release_memory();
}
#endif
ret = j.storage->delete_files_impl();
if (ret != 0)
{
@ -1094,9 +1077,7 @@ namespace libtorrent
// if the check is not done, add it at the end of the job queue
if (ret == piece_manager::need_full_check)
{
mutex_t::scoped_lock l(m_mutex);
m_jobs.push_back(j);
m_jobs.back().callback.swap(handler);
add_job(j, handler);
continue;
}
break;

View File

@ -37,9 +37,6 @@ POSSIBILITY OF SUCH DAMAGE.
#include <algorithm>
#include <numeric>
// non-standard header, is_sorted()
//#include <algo.h>
#include "libtorrent/piece_picker.hpp"
#include "libtorrent/aux_/session_impl.hpp"
#include "libtorrent/bitfield.hpp"
@ -255,6 +252,7 @@ namespace libtorrent
TORRENT_ASSERT(m_num_have >= 0);
TORRENT_ASSERT(m_num_have_filtered >= 0);
TORRENT_ASSERT(m_num_filtered >= 0);
TORRENT_ASSERT(m_seeds >= 0);
if (!m_downloads.empty())
{
@ -446,6 +444,7 @@ namespace libtorrent
float piece_picker::distributed_copies() const
{
TORRENT_ASSERT(m_seeds >= 0);
const float num_pieces = static_cast<float>(m_piece_map.size());
int min_availability = piece_pos::max_peer_count;
@ -768,8 +767,12 @@ namespace libtorrent
int new_priority = p.priority(this);
if (new_priority == prev_priority) return;
if (m_sequential_download >= 0) return;
if (m_dirty) return;
if (m_sequential_download >= 0)
{
m_dirty = true;
return;
}
if (prev_priority == -1)
{
add(index);
@ -784,7 +787,6 @@ namespace libtorrent
{
TORRENT_PIECE_PICKER_INVARIANT_CHECK;
++m_seeds;
if (m_sequential_download >= 0) return;
if (m_seeds == 1)
{
// when m_seeds is increased from 0 to 1
@ -798,12 +800,6 @@ namespace libtorrent
{
TORRENT_PIECE_PICKER_INVARIANT_CHECK;
if (m_sequential_download >= 0)
{
--m_seeds;
return;
}
if (m_seeds > 0)
{
--m_seeds;
@ -816,6 +812,7 @@ namespace libtorrent
}
return;
}
TORRENT_ASSERT(m_seeds == 0);
for (std::vector<piece_pos>::iterator i = m_piece_map.begin()
, end(m_piece_map.end()); i != end; ++i)
@ -835,6 +832,7 @@ namespace libtorrent
if (m_sequential_download >= 0)
{
++p.peer_count;
m_dirty = true;
return;
}
@ -856,7 +854,9 @@ namespace libtorrent
piece_pos& p = m_piece_map[index];
if (m_sequential_download >= 0)
{
TORRENT_ASSERT(p.peer_count > 0);
--p.peer_count;
m_dirty = true;
return;
}
int prev_priority = p.priority(this);
@ -1756,6 +1756,7 @@ namespace libtorrent
void piece_picker::get_availability(std::vector<int>& avail) const
{
TORRENT_ASSERT(m_seeds >= 0);
TORRENT_PIECE_PICKER_INVARIANT_CHECK;
avail.resize(m_piece_map.size());

View File

@ -524,6 +524,12 @@ namespace aux {
m_disk_thread.set_cache_size(s.cache_size);
if (m_settings.cache_expiry != s.cache_expiry)
m_disk_thread.set_cache_size(s.cache_expiry);
// if queuing settings were changed, recalculate
// queued torrents sooner
if ((m_settings.active_downloads != s.active_downloads
|| m_settings.active_seeds != s.active_seeds)
&& m_auto_manage_time_scaler > 2)
m_auto_manage_time_scaler = 2;
m_settings = s;
if (m_settings.connection_speed <= 0) m_settings.connection_speed = 200;
@ -1324,7 +1330,6 @@ namespace aux {
if (t->is_finished())
{
--num_seeds;
--num_downloaders;
}
else
{
@ -1368,9 +1373,8 @@ namespace aux {
, end(seeds.end()); i != end; ++i)
{
torrent* t = *i;
if (num_downloaders > 0 && num_seeds > 0)
if (num_seeds > 0)
{
--num_downloaders;
--num_seeds;
if (t->is_paused()) t->resume();
}

View File

@ -255,7 +255,7 @@ namespace libtorrent
template <class Path>
void recursive_copy(Path const& old_path, Path const& new_path, std::string& error)
{
using boost::filesystem::directory_iterator;
using boost::filesystem::basic_directory_iterator;
#ifndef BOOST_NO_EXCEPTIONS
try {
#endif
@ -263,7 +263,7 @@ namespace libtorrent
if (is_directory(old_path))
{
create_directory(new_path);
for (directory_iterator i(old_path), end; i != end; ++i)
for (basic_directory_iterator<Path> i(old_path), end; i != end; ++i)
{
recursive_copy(i->path(), new_path / i->leaf(), error);
if (!error.empty()) return;
@ -281,13 +281,13 @@ namespace libtorrent
template <class Path>
void recursive_remove(Path const& old_path)
{
using boost::filesystem::directory_iterator;
using boost::filesystem::basic_directory_iterator;
#ifndef BOOST_NO_EXCEPTIONS
try {
#endif
if (is_directory(old_path))
{
for (directory_iterator i(old_path), end; i != end; ++i)
for (basic_directory_iterator<Path> i(old_path), end; i != end; ++i)
recursive_remove(i->path());
remove(old_path);
}