diff --git a/deluge/common.py b/deluge/common.py index 308dfdfba..22f1526cb 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -39,18 +39,26 @@ import pkg_resources import xdg, xdg.BaseDirectory -TORRENT_STATE = [ - "Queued", - "Checking", - "Connecting", - "Downloading Metadata", - "Downloading", - "Finished", - "Seeding", - "Allocating", - "Paused" -] +LT_TORRENT_STATE = { + "Queued": 0, + "Checking": 1, + "Connecting": 2, + "Downloading Metadata": 3, + "Downloading": 4, + "Finished": 5, + "Seeding": 6, + "Allocating": 7, + "Paused": 8 +} +TORRENT_STATE = { + "Allocating": 0, + "Checking": 1, + "Downloading": 2, + "Seeding": 3, + "Paused": 4, + "Error": 5 +} def get_version(): """Returns the program version from the egg metadata""" return pkg_resources.require("Deluge")[0].version.split("r")[0] diff --git a/deluge/core/core.py b/deluge/core/core.py index b830b0a39..f6117d346 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -344,17 +344,17 @@ class Core( def export_force_reannounce(self, torrent_id): log.debug("Forcing reannouncment to trackers of torrent %s", torrent_id) - self.torrents.force_reannounce(torrent_id) + self.torrents[torrent_id].force_reannounce() def export_pause_torrent(self, torrent_id): log.debug("Pausing torrent %s", torrent_id) - if not self.torrents.pause(torrent_id): + if not self.torrents[torrent_id].pause(): log.warning("Error pausing torrent %s", torrent_id) - def export_move_torrent(self, torrent_id, folder): - log.debug("Moving torrent %s to %s", torrent_id, folder) - if not self.torrents.move(torrent_id, folder): - log.warning("Error moving torrent %s to %s", torrent_id, folder) + def export_move_torrent(self, torrent_id, dest): + log.debug("Moving torrent %s to %s", torrent_id, dest) + if not self.torrents[torrent_id].move(dest): + log.warning("Error moving torrent %s to %s", torrent_id, dest) def export_pause_all_torrents(self): """Pause all torrents in the session""" @@ -369,7 +369,7 @@ class Core( def export_resume_torrent(self, torrent_id): log.debug("Resuming torrent %s", torrent_id) - if self.torrents.resume(torrent_id): + if self.torrents[torrent_id].resume(): self.torrent_resumed(torrent_id) def export_get_torrent_status(self, torrent_id, keys): @@ -477,7 +477,7 @@ class Core( def export_set_torrent_trackers(self, torrent_id, trackers): """Sets a torrents tracker list. trackers will be [{"url", "tier"}]""" - return self.torrents.set_trackers(torrent_id, trackers) + return self.torrents[torrent_id].set_trackers(trackers) # Signals def torrent_added(self, torrent_id): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 0b0aba982..0d74758a0 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -33,13 +33,23 @@ """Internal Torrent class""" +import os + +import deluge.libtorrent as lt import deluge.common +from deluge.configmanager import ConfigManager +from deluge.log import LOG as log + +TORRENT_STATE = deluge.common.TORRENT_STATE class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ def __init__(self, filename, handle, compact, save_path, total_uploaded=0, trackers=None): + # Get the core config + self.config = ConfigManager("core.conf") + # Set the filename self.filename = filename # Set the libtorrent handle @@ -52,7 +62,22 @@ class Torrent: self.compact = compact # Where the torrent is being saved to self.save_path = save_path + # The state of the torrent + self.state = None + # Holds status info so that we don't need to keep getting it from lt + self.status = self.handle.status() + self.torrent_info = self.handle.torrent_info() + + # Set the initial state + if self.status.state == deluge.common.LT_TORRENT_STATE["Allocating"]: + self.set_state("Allocating") + elif self.status.state == deluge.common.LT_TORRENT_STATE["Checking"]: + self.set_state("Checking") + else: + self.set_state("Paused") + + # Various torrent options self.max_connections = -1 self.max_upload_slots = -1 self.max_upload_speed = -1 @@ -73,10 +98,6 @@ class Torrent: self.trackers.append(tracker) else: self.trackers = trackers - - # Holds status info so that we don't need to keep getting it from lt - self.status = None - self.torrent_info = None # Files dictionary self.files = self.get_files() @@ -114,8 +135,21 @@ class Torrent: def set_file_priorities(self, file_priorities): self.file_priorities = file_priorities self.handle.prioritize_files(file_priorities) - - def get_state(self): + + def set_state(self, state): + """Accepts state strings, ie, "Paused", "Seeding", etc.""" + + # Only set 'Downloading' or 'Seeding' state if not paused + if state == "Downloading" or state == "Seeding": + if self.handle.is_paused(): + state = "Paused" + + try: + self.state = TORRENT_STATE[state] + except: + pass + + def get_save_info(self): """Returns the state of this torrent for saving to the session state""" status = self.handle.status() return (self.torrent_id, self.filename, self.compact, status.paused, @@ -188,11 +222,6 @@ class Torrent: # Adjust progress to be 0-100 value progress = self.status.progress * 100 - # Set the state to 'Paused' if the torrent is paused. - state = self.status.state - if self.status.paused: - state = deluge.common.TORRENT_STATE.index("Paused") - # Adjust status.distributed_copies to return a non-negative value distributed_copies = self.status.distributed_copies if distributed_copies < 0: @@ -207,7 +236,7 @@ class Torrent: "distributed_copies": distributed_copies, "total_done": self.status.total_done, "total_uploaded": self.total_uploaded + self.status.total_payload_upload, - "state": int(state), + "state": self.state, "paused": self.status.paused, "progress": progress, "next_announce": self.status.next_announce.seconds, @@ -242,3 +271,139 @@ class Torrent: status_dict[key] = full_status[key] return status_dict + + def pause(self): + """Pause this torrent""" + try: + self.handle.pause() + except Exception, e: + log.debug("Unable to pause torrent: %s", e) + return False + + return True + + def resume(self): + """Resumes this torrent""" + if self.state != TORRENT_STATE["Paused"]: + return False + + try: + self.handle.resume() + except: + return False + + # Set the state + if self.handle.is_seed(): + self.set_state("Seeding") + else: + self.set_state("Downloading") + + status = self.get_status(["total_done", "total_wanted"]) + + # Only delete the .fastresume file if we're still downloading stuff + if status["total_done"] < status["total_wanted"]: + self.delete_fastresume() + return True + + def move_storage(self, dest): + """Move a torrent's storage location""" + try: + self.handle.move_storage(dest) + except: + return False + + return True + + def write_fastresume(self): + """Writes the .fastresume file for the torrent""" + resume_data = lt.bencode(self.handle.write_resume_data()) + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + self.filename) + log.debug("Saving fastresume file: %s", path) + try: + fastresume = open(path, "wb") + fastresume.write(resume_data) + fastresume.close() + except IOError: + log.warning("Error trying to save fastresume file") + + def delete_fastresume(self): + """Deletes the .fastresume file""" + path = "%s/%s.fastresume" % ( + self.config["torrentfiles_location"], + self.filename) + log.debug("Deleting fastresume file: %s", path) + try: + os.remove(path) + except Exception, e: + log.warning("Unable to delete the fastresume file: %s", e) + + def force_reannounce(self): + """Force a tracker reannounce""" + try: + self.handle.force_reannounce() + except Exception, e: + log.debug("Unable to force reannounce: %s", e) + return False + + return True + + def scrape_tracker(self): + """Scrape the tracker""" + try: + self.handle.scrape_tracker() + except Exception, e: + log.debug("Unable to scrape tracker: %s", e) + return False + + return True + + def set_trackers(self, trackers): + """Sets trackers""" + if trackers == None: + trackers = [] + + log.debug("Setting trackers for %s: %s", self.torrent_id, trackers) + tracker_list = [] + + for tracker in trackers: + new_entry = lt.announce_entry(tracker["url"]) + new_entry.tier = tracker["tier"] + tracker_list.append(new_entry) + + self.handle.replace_trackers(tracker_list) + + # Print out the trackers + for t in self.handle.trackers(): + log.debug("tier: %s tracker: %s", t.tier, t.url) + # Set the tracker list in the torrent object + self.trackers = trackers + if len(trackers) > 0: + # Force a reannounce if there is at least 1 tracker + self.force_reannounce() + + def save_torrent_file(self, filedump=None): + """Saves a torrent file""" + log.debug("Attempting to save torrent file: %s", self.filename) + # Test if the torrentfiles_location is accessible + if os.access( + os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ + is False: + # The directory probably doesn't exist, so lets create it + try: + os.makedirs(os.path.join(self.config["torrentfiles_location"])) + except IOError, e: + log.warning("Unable to create torrent files directory: %s", e) + + # Write the .torrent file to the torrent directory + try: + save_file = open(os.path.join(self.config["torrentfiles_location"], + self.filename), + "wb") + if filedump == None: + filedump = self.handle.torrent_info().create_torrent() + save_file.write(lt.bencode(filedump)) + save_file.close() + except IOError, e: + log.warning("Unable to save torrent file: %s", e) diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index f0c154a4d..e893b56ee 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -93,6 +93,8 @@ class TorrentManager(component.Component): self.on_alert_torrent_finished) self.alerts.register_handler("torrent_paused_alert", self.on_alert_torrent_paused) + self.alerts.register_handler("torrent_checked_alert", + self.on_alert_torrent_checked) self.alerts.register_handler("tracker_reply_alert", self.on_alert_tracker_reply) self.alerts.register_handler("tracker_announce_alert", @@ -116,7 +118,7 @@ class TorrentManager(component.Component): # Pause all torrents and save the .fastresume files self.pause_all() for key in self.torrents.keys(): - self.write_fastresume(key) + self.torrents[key].write_fastresume() def shutdown(self): self.stop() @@ -228,7 +230,7 @@ class TorrentManager(component.Component): # Set the trackers if trackers != None: - self.set_trackers(str(handle.info_hash()), trackers) + torrent.set_trackers(trackers) # Set per-torrent options torrent.set_max_connections(options["max_connections_per_torrent"]) @@ -250,34 +252,11 @@ class TorrentManager(component.Component): handle.resume() # Save the torrent file - self.save_torrent(filename, filedump) + torrent.save_torrent_file(filedump) # Save the session state self.save_state() return torrent.torrent_id - - def save_torrent(self, filename, filedump): - """Saves a torrent file""" - log.debug("Attempting to save torrent file: %s", filename) - # Test if the torrentfiles_location is accessible - if os.access( - os.path.join(self.config["torrentfiles_location"]), os.F_OK) \ - is False: - # The directory probably doesn't exist, so lets create it - try: - os.makedirs(os.path.join(self.config["torrentfiles_location"])) - except IOError, e: - log.warning("Unable to create torrent files directory: %s", e) - - # Write the .torrent file to the torrent directory - try: - save_file = open(os.path.join(self.config["torrentfiles_location"], - filename), - "wb") - save_file.write(lt.bencode(filedump)) - save_file.close() - except IOError, e: - log.warning("Unable to save torrent file: %s", e) def load_torrent(self, filename): """Load a torrent file and return it's torrent info""" @@ -322,7 +301,7 @@ class TorrentManager(component.Component): log.warning("Unable to remove .torrent file: %s", e) # Remove the .fastresume if it exists - self.delete_fastresume(torrent_id) + self.torrents[torrent_id].delete_fastresume() # Remove the torrent from deluge's session try: @@ -333,24 +312,6 @@ class TorrentManager(component.Component): # Save the session state self.save_state() return True - - def pause(self, torrent_id): - """Pause a torrent""" - try: - self.torrents[torrent_id].handle.pause() - except: - return False - - return True - - def move(self, torrent_id, folder): - """Move a torrent""" - try: - self.torrents[torrent_id].handle.move_storage(folder) - except: - return False - - return True def pause_all(self): """Pauses all torrents.. Returns a list of torrents paused.""" @@ -363,24 +324,9 @@ class TorrentManager(component.Component): log.warning("Unable to pause torrent %s", key) return torrent_was_paused - - def resume(self, torrent_id): - """Resume a torrent""" - try: - self.torrents[torrent_id].handle.resume() - except: - return False - - status = self.torrents[torrent_id].get_status( - ["total_done", "total_wanted"]) - - # Only delete the .fastresume file if we're still downloading stuff - if status["total_done"] < status["total_wanted"]: - self.delete_fastresume(torrent_id) - return True def resume_all(self): - """Resumes all torrents.. Returns a list of torrents resumed""" + """Resumes all torrents.. Returns True if at least 1 torrent is resumed""" torrent_was_resumed = False for key in self.torrents.keys(): if self.resume(key): @@ -389,50 +335,7 @@ class TorrentManager(component.Component): log.warning("Unable to resume torrent %s", key) return torrent_was_resumed - - def set_trackers(self, torrent_id, trackers): - """Sets trackers""" - if trackers == None: - trackers = [] - - log.debug("Setting trackers for %s: %s", torrent_id, trackers) - tracker_list = [] - - for tracker in trackers: - new_entry = lt.announce_entry(tracker["url"]) - new_entry.tier = tracker["tier"] - tracker_list.append(new_entry) - - self.torrents[torrent_id].handle.replace_trackers(tracker_list) - # Print out the trackers - for t in self.torrents[torrent_id].handle.trackers(): - log.debug("tier: %s tracker: %s", t.tier, t.url) - # Set the tracker list in the torrent object - self.torrents[torrent_id].trackers = trackers - if len(trackers) > 0: - # Force a reannounce if there is at least 1 tracker - self.force_reannounce(torrent_id) - - def force_reannounce(self, torrent_id): - """Force a tracker reannounce""" - try: - self.torrents[torrent_id].handle.force_reannounce() - except Exception, e: - log.debug("Unable to force reannounce: %s", e) - return False - - return True - - def scrape_tracker(self, torrent_id): - """Scrape the tracker""" - try: - self.torrents[torrent_id].handle.scrape_tracker() - except Exception, e: - log.debug("Unable to scrape tracker: %s", e) - return False - - return True - + def force_recheck(self, torrent_id): """Forces a re-check of the torrent's data""" log.debug("Doing a forced recheck on %s", torrent_id) @@ -443,7 +346,7 @@ class TorrentManager(component.Component): if os.access(os.path.join(self.config["torrentfiles_location"] +\ "/" + torrent.filename), os.F_OK) is False: torrent_info = torrent.handle.get_torrent_info().create_torrent() - self.save_torrent(torrent.filename, torrent_info) + torrent.save_torrent_file() # We start by removing it from the lt session try: @@ -453,7 +356,7 @@ class TorrentManager(component.Component): return False # Remove the fastresume file if there - self.delete_fastresume(torrent_id) + torrent.delete_fastresume() # Load the torrent info from file if needed if torrent_info == None: @@ -481,6 +384,9 @@ class TorrentManager(component.Component): # The torrent was not added to the session return False + # Set the state to Checking + torrent.set_state("Checking") + return True def load_state(self): @@ -508,7 +414,7 @@ class TorrentManager(component.Component): state = TorrentManagerState() # Create the state for each Torrent and append to the list for torrent in self.torrents.values(): - torrent_state = TorrentState(*torrent.get_state()) + torrent_state = TorrentState(*torrent.get_save_info()) state.torrents.append(torrent_state) # Pickle the TorrentManagerState object @@ -524,33 +430,6 @@ class TorrentManager(component.Component): # We return True so that the timer thread will continue return True - def delete_fastresume(self, torrent_id): - """Deletes the .fastresume file""" - torrent = self.torrents[torrent_id] - path = "%s/%s.fastresume" % ( - self.config["torrentfiles_location"], - torrent.filename) - log.debug("Deleting fastresume file: %s", path) - try: - os.remove(path) - except Exception, e: - log.warning("Unable to delete the fastresume file: %s", e) - - def write_fastresume(self, torrent_id): - """Writes the .fastresume file for the torrent""" - torrent = self.torrents[torrent_id] - resume_data = lt.bencode(torrent.handle.write_resume_data()) - path = "%s/%s.fastresume" % ( - self.config["torrentfiles_location"], - torrent.filename) - log.debug("Saving fastresume file: %s", path) - try: - fastresume = open(path,"wb") - fastresume.write(resume_data) - fastresume.close() - except IOError: - log.warning("Error trying to save fastresume file") - 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) @@ -571,16 +450,27 @@ class TorrentManager(component.Component): # Get the torrent_id torrent_id = str(alert.handle.info_hash()) log.debug("%s is finished..", torrent_id) + # Set the torrent state + self.torrents[torrent_id].set_state("Seeding") # Write the fastresume file - self.write_fastresume(torrent_id) + self.torrents[torrent_id].write_fastresume() def on_alert_torrent_paused(self, alert): log.debug("on_alert_torrent_paused") # Get the torrent_id torrent_id = str(alert.handle.info_hash()) + # Set the torrent state + self.torrents[torrent_id].set_state("Paused") # Write the fastresume file - self.write_fastresume(torrent_id) + self.torrents[torrent_id].write_fastresume() + 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].set_state("Downloading") + def on_alert_tracker_reply(self, alert): log.debug("on_alert_tracker_reply") # Get the torrent_id diff --git a/deluge/ui/gtkui/sidebar.py b/deluge/ui/gtkui/sidebar.py index 901c2a22d..3e8ad2379 100644 --- a/deluge/ui/gtkui/sidebar.py +++ b/deluge/ui/gtkui/sidebar.py @@ -38,6 +38,8 @@ import deluge.component as component import deluge.common from deluge.log import LOG as log +TORRENT_STATE = deluge.common.TORRENT_STATE + class SideBar(component.Component): def __init__(self): component.Component.__init__(self, "SideBar") @@ -102,13 +104,11 @@ class SideBar(component.Component): component.get("TorrentView").set_filter(None, None) if value == "Downloading": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Downloading")) - + TORRENT_STATE["Downloading"]) if value == "Seeding": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Seeding")) - + TORRENT_STATE["Seeding"]) if value == "Paused": component.get("TorrentView").set_filter("state", - deluge.common.TORRENT_STATE.index("Paused")) + TORRENT_STATE["Paused"]) diff --git a/deluge/ui/gtkui/toolbar.py b/deluge/ui/gtkui/toolbar.py index dffea8ee2..09132c22f 100644 --- a/deluge/ui/gtkui/toolbar.py +++ b/deluge/ui/gtkui/toolbar.py @@ -42,9 +42,6 @@ from deluge.common import TORRENT_STATE import deluge.ui.client as client class ToolBar(component.Component): - STATE_FINISHED = TORRENT_STATE.index("Finished") - STATE_SEEDING = TORRENT_STATE.index("Seeding") - STATE_PAUSED = TORRENT_STATE.index("Paused") def __init__(self): component.Component.__init__(self, "ToolBar") log.debug("ToolBar Init..") @@ -175,7 +172,7 @@ class ToolBar(component.Component): except KeyError, e: log.debug("Error getting torrent state: %s", e) continue - if status == self.STATE_PAUSED: + if status == TORRENT_STATE["Paused"]: resume = True else: pause = True diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 3b920da28..1c881758f 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -48,6 +48,8 @@ import deluge.ui.client as client from deluge.log import LOG as log import deluge.ui.gtkui.listview as listview +TORRENT_STATE = deluge.common.TORRENT_STATE + # Status icons.. Create them from file only once to avoid constantly # re-creating them. icon_downloading = gtk.gdk.pixbuf_new_from_file( @@ -56,18 +58,17 @@ icon_seeding = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("seeding16.png")) icon_inactive = gtk.gdk.pixbuf_new_from_file( deluge.common.get_pixmap("inactive16.png")) +icon_alert = gtk.gdk.pixbuf_new_from_file( + deluge.common.get_pixmap("alert16.png")) # Holds the info for which status icon to display based on state ICON_STATE = [ icon_inactive, - icon_downloading, - icon_downloading, - icon_downloading, + icon_inactive, icon_downloading, icon_seeding, - icon_seeding, - icon_downloading, - icon_inactive + icon_inactive, + icon_alert ] def cell_data_statusicon(column, cell, model, row, data): @@ -78,23 +79,16 @@ def cell_data_statusicon(column, cell, model, row, data): def cell_data_progress(column, cell, model, row, data): """Display progress bar with text""" - # Translated state strings - TORRENT_STATE = [ - _("Queued"), - _("Checking"), - _("Connecting"), - _("Downloading Metadata"), - _("Downloading"), - _("Finished"), - _("Seeding"), - _("Allocating"), - _("Paused") - ] (value, text) = model.get(row, *data) if cell.get_property("value") != value: cell.set_property("value", value) - textstr = "%s" % TORRENT_STATE[text] - if TORRENT_STATE[text] != "Seeding" and TORRENT_STATE[text] != "Finished": + state_str = "" + for key in TORRENT_STATE.keys(): + if TORRENT_STATE[key] == text: + state_str = key + break + textstr = "%s" % state_str + if state_str != "Seeding" and state_str != "Finished" and value < 100: textstr = textstr + " %.2f%%" % value if cell.get_property("text") != textstr: cell.set_property("text", textstr)