diff --git a/ChangeLog b/ChangeLog index 54111bc71..6e9e52bb9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,9 +10,12 @@ * #1247: Fix deluge-gtk from hanging on shutdown * #995: Rewrote tracker_icons * Make the distinction between adding to the session new unmanaged torrents and torrents loaded from state. This will break backwards compatability. + * Pass a copy of an event instead of passing the event arguments to the event handlers. This will break backwards compatability. + * Allow changing ownership of torrents. ==== GtkUI ==== * Fix uncaught exception when closing deluge in classic mode + * Allow changing ownership of torrents ==== WebUI ==== * Migrate to ExtJS 3.1 diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py index 74dcefb9c..08dd0e8c7 100644 --- a/deluge/core/authmanager.py +++ b/deluge/core/authmanager.py @@ -74,18 +74,16 @@ class AuthManager(component.Component): :returns: int, the auth level for this user :rtype: int + :raises AuthenticationRequired: if aditional details are required to authenticate :raises BadLoginError: if the username does not exist or password does not match """ if not username: - raise AuthenticationRequired("Username and Password are required.", - username) + raise AuthenticationRequired( + "Username and Password are required.", username + ) - if username and username not in self.__auth: - # Let's try to re-load the file.. Maybe it's been updated - self.__load_auth_file() - if username not in self.__auth: - raise BadLoginError("Username does not exist") + self.__test_existing_account(username) if self.__auth[username][0] == password: # Return the users auth level @@ -95,6 +93,21 @@ class AuthManager(component.Component): else: raise BadLoginError("Password does not match") + def get_known_accounts(self): + """ + Returns a list of known deluge usernames. + """ + self.__load_auth_file() + return self.__auth.keys() + + + def __test_existing_account(self, username): + if username not in self.__auth: + # Let's try to re-load the file.. Maybe it's been updated + self.__load_auth_file() + if username not in self.__auth: + raise BadLoginError("Username does not exist") + def __create_localclient_account(self): """ Returns the string. diff --git a/deluge/core/core.py b/deluge/core/core.py index d0c80fd09..88eda2e13 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -55,6 +55,7 @@ import deluge.common import deluge.component as component from deluge.event import * from deluge.error import * +from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.core.torrentmanager import TorrentManager from deluge.core.pluginmanager import PluginManager from deluge.core.alertmanager import AlertManager @@ -579,6 +580,25 @@ class Core(component.Component): """Sets the path for the torrent to be moved when completed""" return self.torrentmanager[torrent_id].set_move_completed_path(value) + @export(AUTH_LEVEL_ADMIN) + def set_torrents_owner(self, torrent_ids, username): + """Set's the torrent owner. + + :param torrent_id: the torrent_id of the torrent to remove + :type torrent_id: string + :param username: the new owner username + :type username: string + + :raises DelugeError: if the username is not known + """ + if username not in self.authmanager.get_known_accounts(): + raise DelugeError("Username \"%s\" is not known." % username) + if isinstance(torrent_ids, basestring): + torrent_ids = [torrent_ids] + for torrent_id in torrent_ids: + self.torrentmanager[torrent_id].set_owner(username) + return None + @export def get_path_size(self, path): """Returns the size of the file or folder 'path' and -1 if the path is @@ -801,3 +821,7 @@ class Core(component.Component): """ return lt.version + + @export(AUTH_LEVEL_ADMIN) + def get_known_accounts(self): + return self.authmanager.get_known_accounts() diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 21a184e63..b3a2564cc 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -211,12 +211,13 @@ class Torrent(object): for (key, value) in options.items(): if OPTIONS_FUNCS.has_key(key): OPTIONS_FUNCS[key](value) - self.options.update(options) def get_options(self): return self.options + def set_owner(self, account): + self.owner = account def set_max_connections(self, max_connections): self.options["max_connections"] = int(max_connections) diff --git a/deluge/ui/gtkui/menubar.py b/deluge/ui/gtkui/menubar.py index 901e6b3c6..88b19c9a4 100644 --- a/deluge/ui/gtkui/menubar.py +++ b/deluge/ui/gtkui/menubar.py @@ -100,6 +100,10 @@ class MenuBar(component.Component): self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu") self.menu_torrent = self.window.main_glade.get_widget("menu_torrent") + self.menuitem_change_owner = gtk.MenuItem(_("Change Ownership")) + self.torrentmenu_glade.get_widget("options_torrent_menu").append(self.menuitem_change_owner) + + # Attach the torrent_menu to the Torrent file menu self.menu_torrent.set_submenu(self.torrentmenu) @@ -199,10 +203,21 @@ class MenuBar(component.Component): if not self.config["classic_mode"]: self.window.main_glade.get_widget("separatormenuitem").show() self.window.main_glade.get_widget("menuitem_quitdaemon").show() + # Show the Torrent menu because we're connected to a host self.menu_torrent.show() + # Hide the change owner submenu until we get the accounts back from the + # demon. + self.menuitem_change_owner.set_visible(False) + + # Get Known accounts to allow chaning ownership + client.core.get_known_accounts().addCallback(self._on_known_accounts) + def stop(self): + log.debug("MenuBar stopping") + self.menuitem_change_owner.remove_submenu() + for widget in self.change_sensitivity: self.window.main_glade.get_widget(widget).set_sensitive(False) @@ -212,6 +227,7 @@ class MenuBar(component.Component): self.window.main_glade.get_widget("separatormenuitem").hide() self.window.main_glade.get_widget("menuitem_quitdaemon").hide() + def update_menu(self): selected = component.get('TorrentView').get_selected_torrents() if not selected or len(selected) == 0: @@ -465,3 +481,54 @@ class MenuBar(component.Component): for item in items: getattr(self.window.main_glade.get_widget(item), attr)() + + def _on_known_accounts(self, known_accounts): + log.debug("_on_known_accounts: %s", known_accounts) + if len(known_accounts) <= 1: + return + + self.menuitem_change_owner.set_visible(True) + + self.change_owner_submenu = gtk.Menu() + self.change_owner_submenu_items = {} + maingroup = gtk.RadioMenuItem(None, None) + + self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup) + + for account in known_accounts: + self.change_owner_submenu_items[account] = item = gtk.RadioMenuItem(maingroup, account) + self.change_owner_submenu.append(item) + item.connect("toggled", self._on_change_owner_toggled, account) + + self.change_owner_submenu.show_all() + self.change_owner_submenu_items[None].set_active(True) + self.change_owner_submenu_items[None].hide() + self.menuitem_change_owner.connect("activate", self._on_change_owner_submenu_active) + self.menuitem_change_owner.set_submenu(self.change_owner_submenu) + + def _on_known_accounts_fail(self, reason): + self.menuitem_change_owner.set_visible(False) + + def _on_change_owner_submenu_active(self, widget): + log.debug("_on_change_owner_submenu_active") + selected = component.get("TorrentView").get_selected_torrents() + if len(selected) > 1: + self.change_owner_submenu_items[None].set_active(True) + return + + torrent_owner = component.get("TorrentView").get_torrent_status(selected[0])["owner"] + for account, item in self.change_owner_submenu_items.iteritems(): + item.set_active(account == torrent_owner) + + def _on_change_owner_toggled(self, widget, account): + log.debug("_on_change_owner_toggled") + update_torrents = [] + selected = component.get("TorrentView").get_selected_torrents() + for torrent_id in selected: + torrent_status = component.get("TorrentView").get_torrent_status(torrent_id) + if torrent_status["owner"] != account: + update_torrents.append(torrent_id) + if update_torrents: + log.debug("Setting torrent owner \"%s\" on %s", account, update_torrents) + client.core.set_torrents_owner(update_torrents, account) + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index bd0d970c6..0393325cd 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -197,7 +197,8 @@ class TorrentView(listview.ListView, component.Component): # Register the columns menu with the listview so it gets updated # accordingly. self.register_checklist_menu( - self.window.main_glade.get_widget("menu_columns")) + self.window.main_glade.get_widget("menu_columns") + ) # Add the columns to the listview self.add_text_column("torrent_id", hidden=True) @@ -253,15 +254,14 @@ class TorrentView(listview.ListView, component.Component): ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. - self.treeview.connect("button-press-event", - self.on_button_press_event) + self.treeview.connect("button-press-event", self.on_button_press_event) # Connect to the 'key-press-event' to know when the bring up the # torrent menu popup via keypress. self.treeview.connect("key-release-event", self.on_key_press_event) # Connect to the 'changed' event of TreeViewSelection to get selection # changes. self.treeview.get_selection().connect("changed", - self.on_selection_changed) + self.on_selection_changed) self.treeview.connect("drag-drop", self.on_drag_drop) self.treeview.connect("key-press-event", self.on_key_press_event)