diff --git a/ChangeLog b/ChangeLog index bc20a0b86..f5ffc3d77 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Deluge 1.0.6 (In Development) + Core: + * Fix #475 catch unicode decoding errors + Deluge 1.0.5 (09 November 2008) GtkUI: * Increase the per-torrent stop share ratio max to 99999.0 diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c76dd0a2d..9acd2d4c0 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -2,19 +2,19 @@ # torrentmanager.py # # Copyright (C) 2007, 2008 Andrew Resch ('andar') -# +# # 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 3 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., @@ -60,7 +60,7 @@ class TorrentState: total_uploaded=0, trackers=None, compact=False, - paused=False, + paused=False, save_path=None, max_connections=-1, max_upload_slots=-1, @@ -105,7 +105,7 @@ class TorrentManager(component.Component): """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.""" - + def __init__(self, session, alerts): component.Component.__init__(self, "TorrentManager", interval=5000, depend=["PluginManager"]) log.debug("TorrentManager init..") @@ -118,12 +118,12 @@ class TorrentManager(component.Component): # Create the torrents dict { torrent_id: Torrent } self.torrents = {} - + # This is a list of torrent_id when we shutdown the torrentmanager. # We use this list to determine if all active torrents have been paused # and that their resume data has been written. self.shutdown_torrent_pause_list = [] - + # Register set functions self.config.register_set_function("max_connections_per_torrent", self.on_set_max_connections_per_torrent) @@ -133,9 +133,9 @@ class TorrentManager(component.Component): self.on_set_max_upload_speed_per_torrent) self.config.register_set_function("max_download_speed_per_torrent", self.on_set_max_download_speed_per_torrent) - + # Register alert functions - self.alerts.register_handler("torrent_finished_alert", + self.alerts.register_handler("torrent_finished_alert", self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) @@ -152,15 +152,15 @@ class TorrentManager(component.Component): self.on_alert_tracker_error) self.alerts.register_handler("storage_moved_alert", self.on_alert_storage_moved) - self.alerts.register_handler("torrent_resumed_alert", + self.alerts.register_handler("torrent_resumed_alert", self.on_alert_torrent_resumed) self.alerts.register_handler("state_changed_alert", self.on_alert_state_changed) - + def start(self): # Get the pluginmanager reference self.plugins = component.get("PluginManager") - + self.signals = component.get("SignalManager") # Run the old state upgrader before loading state @@ -172,11 +172,11 @@ class TorrentManager(component.Component): # Save the state every 5 minutes self.save_state_timer = gobject.timeout_add(300000, self.save_state) self.save_resume_data_timer = gobject.timeout_add(290000, self.save_resume_data) - + def stop(self): # Save state on shutdown self.save_state() - + component.pause("AlertManager") for key in self.torrents.keys(): if not self.torrents[key].handle.is_paused(): @@ -184,11 +184,11 @@ class TorrentManager(component.Component): self.torrents[key].handle.auto_managed(False) self.torrents[key].handle.pause() self.shutdown_torrent_pause_list.append(key) - while self.shutdown_torrent_pause_list: + while self.shutdown_torrent_pause_list: time.sleep(0.1) # Wait for all alerts self.alerts.handle_alerts(True) - + def update(self): for torrent_id, torrent in self.torrents.items(): if self.config["stop_seed_at_ratio"] or torrent.stop_at_ratio: @@ -205,11 +205,11 @@ class TorrentManager(component.Component): def __getitem__(self, torrent_id): """Return the Torrent with torrent_id""" return self.torrents[torrent_id] - + def get_torrent_list(self): """Returns a list of torrent_ids""" return self.torrents.keys() - + def get_torrent_info_from_file(self, filepath): """Returns a torrent_info for the file specified or None""" torrent_info = None @@ -221,16 +221,16 @@ class TorrentManager(component.Component): _file.close() except (IOError, RuntimeError), e: log.warning("Unable to open %s: %s", filepath, e) - + return torrent_info - + def get_resume_data_from_file(self, torrent_id): """Returns an entry with the resume data or None""" fastresume = "" try: _file = open( os.path.join( - self.config["state_location"], + self.config["state_location"], torrent_id + ".fastresume"), "rb") fastresume = _file.read() @@ -239,24 +239,24 @@ class TorrentManager(component.Component): log.debug("Unable to load .fastresume: %s", e) return str(fastresume) - + def add(self, torrent_info=None, state=None, options=None, save_state=True, filedump=None, filename=None): """Add a torrent to the manager and returns it's torrent_id""" - + if torrent_info is None and state is None and filedump is None: log.debug("You must specify a valid torrent_info or a torrent state object!") return log.debug("torrentmanager.add") add_torrent_params = {} - + if filedump is not None: try: torrent_info = lt.torrent_info(lt.bdecode(filedump)) except Exception, e: log.error("Unable to decode torrent file!: %s", e) - + if torrent_info is None: # We have no torrent_info so we need to add the torrent with information # from the state object. @@ -273,14 +273,14 @@ class TorrentManager(component.Component): 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. @@ -304,44 +304,50 @@ class TorrentManager(component.Component): else: for key in options_keys: if not options.has_key(key): - options[key] = self.config[key] + options[key] = self.config[key] add_torrent_params["ti"] = torrent_info add_torrent_params["resume_data"] = "" - + #log.info("Adding torrent: %s", filename) log.debug("options: %s", options) - + # Set the right storage_mode if options["compact_allocation"]: storage_mode = lt.storage_mode_t(2) else: storage_mode = lt.storage_mode_t(1) - + + try: + # Try to encode this as utf8 if needed + options["download_location"] = options["download_location"].encode("utf8") + except UnicodeDecodeError: + pass + # Fill in the rest of the add_torrent_params dictionary - add_torrent_params["save_path"] = options["download_location"].encode("utf8") + add_torrent_params["save_path"] = options["download_location"] add_torrent_params["storage_mode"] = storage_mode add_torrent_params["paused"] = True add_torrent_params["auto_managed"] = False add_torrent_params["duplicate_is_error"] = True - + # We need to pause the AlertManager momentarily to prevent alerts # for this torrent being generated before a Torrent object is created. component.pause("AlertManager") - + handle = None try: handle = self.session.add_torrent(add_torrent_params) except RuntimeError, e: log.warning("Error adding torrent: %s", e) - + if not handle or not handle.is_valid(): 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())) # Create a Torrent object torrent = Torrent(handle, options, state, filename) @@ -349,7 +355,7 @@ class TorrentManager(component.Component): self.torrents[torrent.torrent_id] = torrent if self.config["queue_new_to_top"]: handle.queue_position_top() - + component.resume("AlertManager") # Resume the torrent if needed @@ -360,7 +366,7 @@ class TorrentManager(component.Component): # Write the .torrent file to the state directory if filedump: try: - save_file = open(os.path.join(self.config["state_location"], + save_file = open(os.path.join(self.config["state_location"], torrent.torrent_id + ".torrent"), "wb") save_file.write(filedump) @@ -383,10 +389,10 @@ class TorrentManager(component.Component): if save_state: # Save the session state self.save_state() - + # Emit the torrent_added signal self.signals.emit("torrent_added", torrent.torrent_id) - + return torrent.torrent_id def load_torrent(self, torrent_id): @@ -397,16 +403,16 @@ class TorrentManager(component.Component): log.debug("Attempting to open %s for add.", torrent_id) _file = open( os.path.join( - self.config["state_location"], torrent_id + ".torrent"), + self.config["state_location"], torrent_id + ".torrent"), "rb") filedump = lt.bdecode(_file.read()) _file.close() except (IOError, RuntimeError), e: log.warning("Unable to open %s: %s", torrent_id, e) return False - + return filedump - + def remove(self, torrent_id, remove_torrent=False, remove_data=False): """Remove a torrent from the manager""" try: @@ -415,17 +421,17 @@ class TorrentManager(component.Component): # Remove data if set if remove_data: option = 1 - self.session.remove_torrent(self.torrents[torrent_id].handle, + self.session.remove_torrent(self.torrents[torrent_id].handle, option) except (RuntimeError, KeyError), e: log.warning("Error removing torrent: %s", e) return False - + # Remove the .torrent file if requested if remove_torrent: try: torrent_file = os.path.join( - self.config["torrentfiles_location"], + self.config["torrentfiles_location"], self.torrents[torrent_id].filename) os.remove(torrent_file) except Exception, e: @@ -433,24 +439,24 @@ class TorrentManager(component.Component): # Remove the .fastresume if it exists self.torrents[torrent_id].delete_fastresume() - + # Remove the .torrent file in the state self.torrents[torrent_id].delete_torrentfile() - + # Remove the torrent from deluge's session try: del self.torrents[torrent_id] except KeyError, ValueError: return False - + # Save the session state self.save_state() - + # Emit the signal to the clients self.signals.emit("torrent_removed", torrent_id) - + return True - + def pause_all(self): """Pauses all torrents.. Returns a list of torrents paused.""" torrent_was_paused = False @@ -460,7 +466,7 @@ class TorrentManager(component.Component): torrent_was_paused = True except: log.warning("Unable to pause torrent %s", key) - + return torrent_was_paused def resume_all(self): @@ -473,7 +479,7 @@ class TorrentManager(component.Component): log.warning("Unable to resume torrent %s", key) return torrent_was_resumed - + def load_state(self): """Load the state of the TorrentManager from the torrents.state file""" state = TorrentManagerState() @@ -486,7 +492,7 @@ class TorrentManager(component.Component): state_file.close() except (EOFError, IOError, Exception), e: log.warning("Unable to load state file: %s", e) - + # Try to use an old state try: if dir(state.torrents[0]) != dir(TorrentState()): @@ -495,7 +501,7 @@ class TorrentManager(component.Component): setattr(s, attr, getattr(TorrentState(), attr, None)) except Exception, e: log.warning("Unable to update state file to a compatible version: %s", e) - + # Reorder the state.torrents list to add torrents in the correct queue # order. ordered_state = [] @@ -506,14 +512,14 @@ class TorrentManager(component.Component): break if torrent_state not in ordered_state: ordered_state.append(torrent_state) - + for torrent_state in ordered_state: try: self.add(state=torrent_state, save_state=False) except AttributeError, e: log.error("Torrent state file is either corrupt or incompatible!") break - + # Run the post_session_load plugin hooks self.plugins.run_post_session_load() @@ -525,14 +531,14 @@ class TorrentManager(component.Component): paused = False if torrent.state == "Paused": paused = True - + torrent_state = TorrentState( torrent.torrent_id, torrent.filename, - torrent.get_status(["total_uploaded"])["total_uploaded"], + torrent.get_status(["total_uploaded"])["total_uploaded"], torrent.trackers, - torrent.compact, - paused, + torrent.compact, + paused, torrent.save_path, torrent.max_connections, torrent.max_upload_slots, @@ -548,18 +554,18 @@ class TorrentManager(component.Component): torrent.remove_at_ratio ) state.torrents.append(torrent_state) - + # Pickle the TorrentManagerState object try: log.debug("Saving torrent state file.") state_file = open( - os.path.join(self.config["state_location"], "torrents.state"), + os.path.join(self.config["state_location"], "torrents.state"), "wb") cPickle.dump(state, state_file) state_file.close() except IOError: log.warning("Unable to save state file.") - + # We return True so that the timer thread will continue return True @@ -567,7 +573,7 @@ class TorrentManager(component.Component): """Saves resume data for all the torrents""" for torrent in self.torrents.values(): torrent.write_fastresume() - + def queue_top(self, torrent_id): """Queue torrent to top""" if self.torrents[torrent_id].get_queue_position() == 0: @@ -588,7 +594,7 @@ class TorrentManager(component.Component): """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 @@ -599,13 +605,13 @@ class TorrentManager(component.Component): 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) for key in self.torrents.keys(): self.torrents[key].set_max_connections(value) - + def on_set_max_upload_slots_per_torrent(self, key, value): """Sets the per-torrent upload slot limit""" log.debug("max_upload_slots_per_torrent set to %s..", value) @@ -616,7 +622,7 @@ class TorrentManager(component.Component): log.debug("max_upload_speed_per_torrent set to %s..", value) for key in self.torrents.keys(): self.torrents[key].set_max_upload_speed(value) - + def on_set_max_download_speed_per_torrent(self, key, value): log.debug("max_download_speed_per_torrent set to %s..", value) for key in self.torrents.keys(): @@ -638,7 +644,7 @@ class TorrentManager(component.Component): torrent.update_state() torrent.write_fastresume() component.get("SignalManager").emit("torrent_finished", torrent_id) - + def on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id @@ -646,20 +652,20 @@ class TorrentManager(component.Component): # Set the torrent state self.torrents[torrent_id].update_state() component.get("SignalManager").emit("torrent_paused", torrent_id) - + # Write the fastresume file self.torrents[torrent_id].write_fastresume() - + if torrent_id in self.shutdown_torrent_pause_list: self.shutdown_torrent_pause_list.remove(torrent_id) - + def on_alert_torrent_checked(self, alert): log.debug("on_alert_torrent_checked") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) # Set the torrent state self.torrents[torrent_id].update_state() - + def on_alert_tracker_reply(self, alert): log.debug("on_alert_tracker_reply") # Get the torrent_id @@ -670,7 +676,7 @@ class TorrentManager(component.Component): self.torrents[torrent_id].set_tracker_status(_("Announce OK")) except KeyError: log.debug("torrent_id doesn't exist.") - + # Check to see if we got any peer information from the tracker if alert.handle.status().num_complete == -1 or \ alert.handle.status().num_incomplete == -1: @@ -685,7 +691,7 @@ class TorrentManager(component.Component): except RuntimeError: log.debug("Invalid torrent handle.") return - + # Set the tracker status for the torrent try: self.torrents[torrent_id].set_tracker_status(_("Announce Sent")) @@ -703,7 +709,7 @@ class TorrentManager(component.Component): self.torrents[torrent_id].set_tracker_status(tracker_status) except KeyError: log.debug("torrent_id doesn't exist.") - + def on_alert_tracker_warning(self, alert): log.debug("on_alert_tracker_warning") # Get the torrent_id @@ -723,7 +729,7 @@ class TorrentManager(component.Component): torrent.set_tracker_status(tracker_status) except KeyError: log.debug("torrent_id doesn't exist.") - + def on_alert_storage_moved(self, alert): log.debug("on_alert_storage_moved") log.debug("save_path: %s", alert.handle.save_path()) @@ -740,10 +746,9 @@ class TorrentManager(component.Component): torrent = self.torrents[str(alert.handle.info_hash())] torrent.is_finished = torrent.handle.is_seed() torrent.update_state() - + def on_alert_state_changed(self, alert): log.debug("on_alert_state_changed") torrent_id = str(alert.handle.info_hash()) self.torrents[torrent_id].update_state() component.get("SignalManager").emit("torrent_state_changed", torrent_id) -