diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index e95eb258c..a852820b7 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -98,6 +98,14 @@ class TorrentOptions(dict): self["file_priorities"] = [] self["mapped_files"] = {} + +class TorrentError(object): + def __init__(self, error_message, was_paused=False, restart_to_resume=False): + self.error_message = error_message + self.was_paused = was_paused + self.restart_to_resume = restart_to_resume + + class Torrent(object): """Torrent holds information about torrents added to the libtorrent session. """ @@ -188,11 +196,14 @@ class Torrent(object): # Various torrent options self.handle.resolve_countries(True) - self.set_options(self.options) + # Details of torrent forced into error state (i.e. not by libtorrent). + self.forced_error = None # Status message holds error info about the torrent self.statusmsg = "OK" + self.set_options(self.options) + # The torrents state self.update_state() @@ -391,7 +402,11 @@ class Torrent(object): # First we check for an error from libtorrent, and set the state to that # if any occurred. - if len(self.handle.status().error) > 0: + if self.forced_error: + self.state = "Error" + log.debug("Torrent Error state message: %s", self.forced_error.error_message) + self.set_status_message("Error: " + self.forced_error.error_message) + elif len(self.handle.status().error) > 0: # This is an error'd torrent self.state = "Error" self.set_status_message(self.handle.status().error) @@ -427,6 +442,37 @@ class Torrent(object): def set_status_message(self, message): self.statusmsg = message + def force_error_state(self, message, restart_to_resume=True): + """Forces the torrent into an error state. + + For setting an error state not covered by libtorrent. + + Args: + message (str): The error status message. + restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting + session can resume. + """ + status = self.handle.status() + self.handle.auto_managed(False) + self.forced_error = TorrentError(message, status.paused, restart_to_resume) + if not status.paused: + self.handle.pause() + self.update_state() + + def clear_forced_error_state(self, update_state=True): + if not self.forced_error: + return + + if self.forced_error.restart_to_resume: + log.error("Restart deluge to clear this torrent error") + + if not self.forced_error.was_paused and self.options["auto_managed"]: + self.handle.auto_managed(True) + self.forced_error = None + self.set_status_message("OK") + if update_state: + self.update_state() + def get_eta(self): """Returns the ETA in seconds for this torrent""" if self.status == None: @@ -782,6 +828,8 @@ class Torrent(object): def pause(self): """Pause this torrent""" + if self.state == "Error": + return False # Turn off auto-management so the torrent will not be unpaused by lt queueing self.handle.auto_managed(False) if self.handle.is_paused(): @@ -806,6 +854,8 @@ class Torrent(object): if self.handle.is_paused() and self.handle.is_auto_managed(): log.debug("Torrent is being auto-managed, cannot resume!") return + elif self.forced_error and self.forced_error.was_paused: + log.debug("Skip resuming Error state torrent that was originally paused.") else: # Reset the status message just in case of resuming an Error'd torrent self.set_status_message("OK") @@ -829,6 +879,9 @@ class Torrent(object): return True + if self.forced_error and not self.forced_error.restart_to_resume: + self.clear_forced_error_state() + def connect_peer(self, ip, port): """adds manual peer""" try: @@ -874,8 +927,12 @@ class Torrent(object): def save_resume_data(self): """Signals libtorrent to build resume data for this torrent, it gets returned in a libtorrent alert""" - self.handle.save_resume_data() - self.waiting_on_resume_data = True + # Don't generate fastresume data if torrent is in a Deluge Error state. + if self.forced_error: + log.debug("Skipped creating resume_data while in Error state") + else: + self.handle.save_resume_data() + self.waiting_on_resume_data = True def on_metadata_received(self): if self.options["prioritize_first_last_pieces"]: @@ -933,7 +990,11 @@ class Torrent(object): def force_recheck(self): """Forces a recheck of the torrents pieces""" self.forcing_recheck = True - self.forcing_recheck_paused = self.handle.is_paused() + if self.forced_error: + self.forcing_recheck_paused = self.forced_error.was_paused + self.clear_forced_error_state(update_state=False) + else: + self.forcing_recheck_paused = self.handle.is_paused() # Store trackers for paused torrents to prevent unwanted announce before pausing again. if self.forcing_recheck_paused: self.set_trackers(None, reannounce=False) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 23bb90add..55dbaa2d6 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -205,6 +205,10 @@ class TorrentManager(component.Component): self.alerts.register_handler("fastresume_rejected_alert", self.on_alert_fastresume_rejected) + # Define timers + self.save_state_timer = LoopingCall(self.save_state) + self.save_resume_data_timer = LoopingCall(self.save_resume_data) + def start(self): # Get the pluginmanager reference self.plugins = component.get("CorePluginManager") @@ -212,14 +216,12 @@ class TorrentManager(component.Component): # Run the old state upgrader before loading state deluge.core.oldstateupgrader.OldStateUpgrader() - # Try to load the state from file + # Try to load the state from file. self.load_state() - # Save the state every 5 minutes - self.save_state_timer = LoopingCall(self.save_state) + # Save the state and resume data every ~3 minutes. self.save_state_timer.start(200, False) - self.save_resume_data_timer = LoopingCall(self.save_resume_data) - self.save_resume_data_timer.start(190) + self.save_resume_data_timer.start(190, False) def stop(self): # Stop timers @@ -682,6 +684,8 @@ class TorrentManager(component.Component): for torrent in self.torrents.values(): if self.session.is_paused(): paused = torrent.handle.is_paused() + elif torrent.forced_error: + paused = torrent.forced_error.was_paused elif torrent.state == "Paused": paused = True else: @@ -1147,9 +1151,7 @@ class TorrentManager(component.Component): else: error_msg = "Problem with resume data: %s" % alert_msg.split(":", 1)[1].strip() - torrent.set_status_message("Error: " + error_msg) - torrent.pause() - torrent.update_state() + torrent.force_error_state(error_msg, restart_to_resume=True) def on_alert_file_renamed(self, alert): log.debug("on_alert_file_renamed")