From 6bced0fb8c97561be6cee2456e8df68436f59e7e Mon Sep 17 00:00:00 2001 From: Andrew Resch Date: Mon, 12 Nov 2007 13:28:56 +0000 Subject: [PATCH] EditTrackerDialog now works. Save trackers in torrent state. Load trackers on torrent add from state. --- TODO | 12 +- deluge/core/core.py | 4 + deluge/core/torrent.py | 22 ++- deluge/core/torrentmanager.py | 47 ++++-- deluge/ui/client.py | 7 + deluge/ui/gtkui/edittrackersdialog.py | 134 ++++++++++++++++- deluge/ui/gtkui/glade/edit_trackers.glade | 168 ++++++++++++++++++++-- deluge/ui/gtkui/menubar.py | 4 +- deluge/ui/gtkui/torrentview.py | 10 +- 9 files changed, 373 insertions(+), 35 deletions(-) diff --git a/TODO b/TODO index 4dceb5879..75c9bf8cd 100644 --- a/TODO +++ b/TODO @@ -2,20 +2,22 @@ * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. * Figure out easy way for user-made plugins to add i18n support. -* Change the menubar.py gtkui component to menus.py and add support for plugins - to add menuitems to the torrentmenu in an easy way. * Restart daemon function * Docstrings! * Implement caching in client.py * Create a new add torrent dialog -* Create edit trackers dialog * Implement open folder * Maybe add pop-up menus to the status bar items * Address issue where torrents will redownload if the storage is moved outside of deluge. * Hide open folder if not localhost -* Modify sensitivity of torrent/tray menu based on connection state * Add classic/normal mode to preferences * Implement 'Classic' mode * Add remove torrent dialog and ability to remove data - +* Tray tooltip +* Add DBUS to gtkui so we can add torrents to existing session +* Add LSD +* Add torrentfiles location config option +* Add autoload folder +* Add wizard +* Add a health indication to the statusbar diff --git a/deluge/core/core.py b/deluge/core/core.py index 2c6f99e2c..4c088e8ae 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -387,6 +387,10 @@ class Core( def export_force_recheck(self, torrent_id): """Forces a data recheck on torrent_id""" return self.torrents.force_recheck(torrent_id) + + 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) # Signals def torrent_added(self, torrent_id): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 5b01a1395..18139ede7 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -38,7 +38,8 @@ import deluge.common class Torrent: """Torrent holds information about torrents added to the libtorrent session. """ - def __init__(self, filename, handle, compact, save_path, total_uploaded=0): + def __init__(self, filename, handle, compact, save_path, total_uploaded=0, + trackers=None): # Set the filename self.filename = filename # Set the libtorrent handle @@ -53,7 +54,18 @@ class Torrent: self.save_path = save_path # The tracker status self.tracker_status = "" - + # Tracker list + if trackers == None: + self.trackers = [] + # Create a list of trackers + for value in self.handle.trackers(): + tracker = {} + tracker["url"] = value.url + tracker["tier"] = value.tier + self.trackers.append(tracker) + else: + self.trackers = trackers + def set_tracker_status(self, status): """Sets the tracker status""" self.tracker_status = status @@ -62,7 +74,8 @@ class Torrent: """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, - self.save_path, self.total_uploaded + status.total_payload_upload) + self.save_path, self.total_uploaded + status.total_payload_upload, + self.trackers) def get_eta(self): """Returns the ETA in seconds for this torrent""" @@ -135,7 +148,7 @@ class Torrent: distributed_copies = status.distributed_copies if distributed_copies < 0: distributed_copies = 0.0 - + full_status = { "name": self.handle.torrent_info().name(), "total_size": self.handle.torrent_info().total_size(), @@ -161,6 +174,7 @@ class Torrent: "eta": self.get_eta(), "ratio": self.get_ratio(), "tracker": status.current_tracker, + "trackers": self.trackers, "tracker_status": self.tracker_status, "save_path": self.save_path, "files": self.get_files() diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 76217276a..6fbd6e0f6 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -48,13 +48,14 @@ from deluge.log import LOG as log class TorrentState: def __init__(self, torrent_id, filename, compact, paused, save_path, - total_uploaded): + total_uploaded, trackers): self.torrent_id = torrent_id self.filename = filename self.compact = compact self.paused = paused self.save_path = save_path self.total_uploaded = total_uploaded + self.trackers = trackers class TorrentManagerState: def __init__(self): @@ -121,7 +122,7 @@ class TorrentManager: return self.torrents.keys() def add(self, filename, filedump=None, compact=None, paused=False, - save_path=None, total_uploaded=0): + save_path=None, total_uploaded=0, trackers=None): """Add a torrent to the manager and returns it's torrent_id""" log.info("Adding torrent: %s", filename) @@ -167,14 +168,14 @@ class TorrentManager: storage_mode = lt.storage_mode_t(1) else: storage_mode = lt.storage_mode_t(2) - + try: handle = self.session.add_torrent( lt.torrent_info(filedump), str(save_path), resume_data=fastresume, storage_mode=storage_mode, - paused=paused) + paused=True) except RuntimeError, e: log.warning("Error adding torrent: %s", e) @@ -184,15 +185,23 @@ class TorrentManager: # Create a Torrent object torrent = Torrent(filename, handle, compact, - save_path, total_uploaded) + save_path, total_uploaded, trackers) # Add the torrent object to the dictionary self.torrents[torrent.torrent_id] = torrent - + + # Set the trackers + if trackers != None: + self.set_trackers(str(handle.info_hash()), trackers) + # Set per-torrent limits handle.set_max_connections(self.max_connections) handle.set_max_uploads(self.max_uploads) + # Resume the torrent if needed + if paused == False: + handle.resume() + # Save the torrent file self.save_torrent(filename, filedump) @@ -304,7 +313,28 @@ class TorrentManager: log.warning("Unable to resume torrent %s", key) return torrent_was_resumed - + + def set_trackers(self, torrent_id, trackers): + """Sets trackers""" + if trackers == [] or trackers == None: + return + log.debug("Setting trackers for %s", torrent_id) + 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 + # Force a reannounce + self.force_reannounce(torrent_id) + def force_reannounce(self, torrent_id): """Force a tracker reannounce""" try: @@ -381,7 +411,8 @@ class TorrentManager: for torrent_state in state.torrents: self.add(torrent_state.filename, compact=torrent_state.compact, paused=torrent_state.paused, save_path=torrent_state.save_path, - total_uploaded=torrent_state.total_uploaded) + total_uploaded=torrent_state.total_uploaded, + trackers=torrent_state.trackers) def save_state(self): """Save the state of the TorrentManager to the torrents.state file""" diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 329a995b2..40edfff4b 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -337,6 +337,13 @@ def force_recheck(torrent_ids): except (AttributeError, socket.error): set_core_uri(None) +def set_torrent_trackers(torrent_id, trackers): + """Sets the torrents trackers""" + try: + get_core().set_torrent_trackers(torrent_id, trackers) + except (AttributeError, socket.error): + set_core_uri(None) + def open_url_in_browser(url): """Opens link in the desktop's default browser""" def start_browser(): diff --git a/deluge/ui/gtkui/edittrackersdialog.py b/deluge/ui/gtkui/edittrackersdialog.py index b25f73049..87d100e92 100644 --- a/deluge/ui/gtkui/edittrackersdialog.py +++ b/deluge/ui/gtkui/edittrackersdialog.py @@ -41,11 +41,16 @@ from deluge.log import LOG as log class EditTrackersDialog: def __init__(self, torrent_id, parent=None): + self.torrent_id = torrent_id self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/edit_trackers.glade")) self.dialog = self.glade.get_widget("edit_trackers_dialog") + self.treeview = self.glade.get_widget("tracker_treeview") + self.add_tracker_dialog = self.glade.get_widget("add_tracker_dialog") + self.add_tracker_dialog.set_transient_for(self.dialog) + self.dialog.set_icon(deluge.common.get_logo(32)) if parent != None: @@ -58,21 +63,148 @@ class EditTrackersDialog: "on_button_remove_clicked": self.on_button_remove_clicked, "on_button_down_clicked": self.on_button_down_clicked, "on_button_ok_clicked": self.on_button_ok_clicked, - "on_button_cancel_clicked": self.on_button_cancel_clicked + "on_button_cancel_clicked": self.on_button_cancel_clicked, + "on_button_add_ok_clicked": self.on_button_add_ok_clicked, + "on_button_add_cancel_clicked": self.on_button_add_cancel_clicked }) + + # Create a liststore for tier, url + self.liststore = gtk.ListStore(int, str) + + # Create the columns + self.treeview.append_column( + gtk.TreeViewColumn(_("Tier"), gtk.CellRendererText(), text=0)) + self.treeview.append_column( + gtk.TreeViewColumn(_("Tracker"), gtk.CellRendererText(), text=1)) + self.treeview.set_model(self.liststore) + self.liststore.set_sort_column_id(0, gtk.SORT_ASCENDING) + def run(self): + # Make sure we have a torrent_id.. if not just return + if self.torrent_id == None: + return + + # Get the trackers for this torrent + trackers = client.get_torrent_status(self.torrent_id, ["trackers"]) + for tracker in trackers["trackers"]: + self.add_tracker(tracker["tier"], tracker["url"]) + self.dialog.show() + + def add_tracker(self, tier, url): + """Adds a tracker to the list""" + self.liststore.append([tier, url]) + + def get_selected(self): + """Returns the selected tracker""" + return self.treeview.get_selection().get_selected()[1] def on_button_up_clicked(self, widget): log.debug("on_button_up_clicked") + selected = self.get_selected() + num_rows = self.liststore.iter_n_children(None) + if selected != None and num_rows > 1: + tier = self.liststore.get_value(selected, 0) + new_tier = tier - 1 + # Return if the tier is already at the top + if tier == 0: + return + # Change the tier of the tracker we're surplanting + def change_tier(model, path, iter, data): + t = model.get_value(iter, 0) + if t == data: + model.set_value(iter, 0, data + 1) + self.liststore.foreach(change_tier, new_tier) + + # Now change the tier for this tracker + self.liststore.set_value(selected, 0, new_tier) + def on_button_add_clicked(self, widget): log.debug("on_button_add_clicked") + # Show the add tracker dialog + self.add_tracker_dialog.show() + self.glade.get_widget("entry_tracker").grab_focus() + def on_button_remove_clicked(self, widget): log.debug("on_button_remove_clicked") + selected = self.get_selected() + if selected != None: + self.liststore.remove(selected) + def on_button_down_clicked(self, widget): log.debug("on_button_down_clicked") + selected = self.get_selected() + num_rows = self.liststore.iter_n_children(None) + if selected != None and num_rows > 1: + tier = self.liststore.get_value(selected, 0) + new_tier = tier + 1 + # This tracker is on the bottom already + if new_tier == num_rows: + return + # Change the tier of the tracker we're surplanting + def change_tier(model, path, iter, data): + t = model.get_value(iter, 0) + if t == data: + model.set_value(iter, 0, data - 1) + self.liststore.foreach(change_tier, new_tier) + # Now change the tier for this tracker + self.liststore.set_value(selected, 0, new_tier) + def on_button_ok_clicked(self, widget): log.debug("on_button_ok_clicked") + self.trackers = [] + def each(model, path, iter, data): + tracker = {} + tracker["tier"] = model.get_value(iter, 0) + tracker["url"] = model.get_value(iter, 1) + self.trackers.append(tracker) + self.liststore.foreach(each, None) + # Set the torrens trackers + client.set_torrent_trackers(self.torrent_id, self.trackers) + self.dialog.destroy() + def on_button_cancel_clicked(self, widget): log.debug("on_button_cancel_clicked") + self.dialog.destroy() + + def on_button_add_ok_clicked(self, widget): + log.debug("on_button_add_ok_clicked") + tracker = self.glade.get_widget("entry_tracker").get_text() + if tracker[:7] != "http://" or tracker[:8] != "https://": + # Bad url.. lets prepend http:// + tracker = "http://" + tracker + + # Figure out what tier number to use.. it's going to be the highest+1 + # Also check for duplicates + self.highest_tier = 0 + self.duplicate = False + def tier_count(model, path, iter, data): + tier = model.get_value(iter, 0) + if tier > self.highest_tier: + self.highest_tier = tier + tracker = model.get_value(iter, 1) + if data == tracker: + # We already have this tracker in the list + self.duplicate = True + + # Check if there are any entries + if self.liststore.iter_n_children(None) > 0: + self.liststore.foreach(tier_count, tracker) + else: + self.highest_tier = -1 + + # If not a duplicate, then add it to the list + if not self.duplicate: + # Add the tracker to the list + self.add_tracker(self.highest_tier + 1, tracker) + + # Clear the entry widget and hide the dialog + self.glade.get_widget("entry_tracker").set_text("") + self.add_tracker_dialog.hide() + + def on_button_add_cancel_clicked(self, widget): + log.debug("on_button_add_cancel_clicked") + # Clear the entry widget and hide the dialog + self.glade.get_widget("entry_tracker").set_text("") + self.add_tracker_dialog.hide() diff --git a/deluge/ui/gtkui/glade/edit_trackers.glade b/deluge/ui/gtkui/glade/edit_trackers.glade index 800c30e5a..9e6fc8f71 100644 --- a/deluge/ui/gtkui/glade/edit_trackers.glade +++ b/deluge/ui/gtkui/glade/edit_trackers.glade @@ -1,6 +1,6 @@ - + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -70,20 +70,12 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN - + True + True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - GTK_RESIZE_QUEUE - GTK_SHADOW_NONE - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - - @@ -93,7 +85,7 @@ True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 - GTK_BUTTONBOX_END + GTK_BUTTONBOX_CENTER True @@ -160,8 +152,6 @@ - False - False 1 @@ -211,4 +201,152 @@ + + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + Add Tracker + GTK_WIN_POS_CENTER_ON_PARENT + True + GDK_WINDOW_TYPE_HINT_DIALOG + False + False + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 2 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-add + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + <b>Add Tracker</b> + True + + + False + False + 1 + + + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 5 + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Tracker: + + + False + False + + + + + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + + + 1 + + + + + False + False + 2 + + + + + 1 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_BUTTONBOX_END + + + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-cancel + True + 0 + + + + + + True + True + True + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + gtk-ok + True + 0 + + + + 1 + + + + + False + GTK_PACK_END + + + + + diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 079d7d762..79a7851ae 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -184,7 +184,9 @@ class MenuBar(component.Component): def on_menuitem_edittrackers_activate(self, data=None): log.debug("on_menuitem_edittrackers_activate") from edittrackersdialog import EditTrackersDialog - dialog = EditTrackersDialog(None, component.get("MainWindow").window) + dialog = EditTrackersDialog( + component.get("TorrentView").get_selected_torrent(), + component.get("MainWindow").window) dialog.run() def on_menuitem_remove_activate(self, data=None): diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index bed6d2be2..5c5d9525a 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -271,7 +271,15 @@ class TorrentView(listview.ListView, component.Component): self.update() break row = self.liststore.iter_next(row) - + + def get_selected_torrent(self): + """Returns a torrent_id or None. If multiple torrents are selected, + it will return the torrent_id of the first one.""" + selected = self.get_selected_torrents() + if selected == None: + return selected + return selected[0] + def get_selected_torrents(self): """Returns a list of selected torrents or None""" torrent_ids = []