diff --git a/deluge/core/filtermanager.py b/deluge/core/filtermanager.py index e04cb9dbb..c52fec29b 100644 --- a/deluge/core/filtermanager.py +++ b/deluge/core/filtermanager.py @@ -78,6 +78,12 @@ def filter_one_keyword(torrent_ids, keyword): yield torrent_id break +def filter_by_name(torrent_ids, search_string): + all_torrents = component.get("TorrentManager").torrents + for torrent_id in torrent_ids: + if search_string[0].lower() in all_torrents[torrent_id].filename.lower(): + yield torrent_id + def tracker_error_filter(torrent_ids, values): filtered_torrent_ids = [] tm = component.get("TorrentManager") @@ -108,6 +114,7 @@ class FilterManager(component.Component): self.torrents = core.torrentmanager self.registered_filters = {} self.register_filter("keyword", filter_keywords) + self.register_filter("name", filter_by_name) self.tree_fields = {} self.register_tree_field("state", self._init_state_tree) diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index 24c894dbb..d176ff01b 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -246,14 +246,20 @@ class DelugeRPCProtocol(Protocol): Sends an error response with the contents of the exception that was raised. """ exceptionType, exceptionValue, exceptionTraceback = sys.exc_info() - self.sendData(( - RPC_ERROR, - request_id, - exceptionType.__name__, - exceptionValue._args, - exceptionValue._kwargs, - "".join(traceback.format_tb(exceptionTraceback)) - )) + try: + self.sendData(( + RPC_ERROR, + request_id, + exceptionType.__name__, + exceptionValue._args, + exceptionValue._kwargs, + "".join(traceback.format_tb(exceptionTraceback)) + )) + except Exception, err: + log.error("An exception occurred while sending RPC_ERROR to " + "client. Error to send(exception goes next): %s", + "".join(traceback.format_tb(exceptionTraceback))) + log.exception(err) if method == "daemon.info": # This is a special case and used in the initial connection process diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 2f77b9f30..db3952142 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -189,14 +189,14 @@ class Torrent(object): else: self.owner = owner - # Keep trac of last seen complete + # Keep track of last seen complete if state: self._last_seen_complete = state.last_seen_complete or 0.0 else: self._last_seen_complete = 0.0 # Keep track if we're forcing a recheck of the torrent so that we can - # repause it after its done if necessary + # re-pause it after its done if necessary self.forcing_recheck = False self.forcing_recheck_paused = False @@ -359,7 +359,7 @@ class Torrent(object): # 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 + # Force a re-announce if there is at least 1 tracker self.force_reannounce() self.tracker_host = None diff --git a/deluge/plugins/autoadd/autoadd/core.py b/deluge/plugins/autoadd/autoadd/core.py index bfea91e17..e0b3f5b53 100644 --- a/deluge/plugins/autoadd/autoadd/core.py +++ b/deluge/plugins/autoadd/autoadd/core.py @@ -226,9 +226,10 @@ class Core(CorePluginBase): if filename in self.invalid_torrents: self.invalid_torrents[filename] += 1 if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS: - log.warning("Maximum attepts reached while trying " - "to add the torrent file with the path" - " %s", filepath) + log.warning( + "Maximum attempts reached while trying to add the " + "torrent file with the path %s", filepath + ) os.rename(filepath, filepath + ".invalid") del self.invalid_torrents[filename] else: @@ -261,16 +262,34 @@ class Core(CorePluginBase): os.rename(filepath, filepath + watchdir['append_extension']) elif watchdir.get('copy_torrent_toggle'): copy_torrent_path = watchdir['copy_torrent'] + copy_torrent_file = os.path.join(copy_torrent_path, filename) log.debug("Moving added torrent file \"%s\" to \"%s\"", os.path.basename(filepath), copy_torrent_path) - os.rename( - filepath, os.path.join(copy_torrent_path, filename) - ) + try: + os.rename(filepath, copy_torrent_file) + except OSError, why: + if why.errno == 18: + # This can happen for different mount points + from shutil import copyfile + try: + copyfile(filepath, copy_torrent_file) + os.remove(filepath) + except OSError: + # Last Resort! + try: + open(copy_torrent_file, 'wb').write( + open(filepath, 'rb').read() + ) + os.remove(filepath) + except OSError, why: + raise why + else: + raise why else: os.remove(filepath) def on_update_watchdir_error(self, failure, watchdir_id): - """Disables any watch folders with unhandled exceptions.""" + """Disables any watch folders with un-handled exceptions.""" self.disable_watchdir(watchdir_id) log.error("Disabling '%s', error during update: %s", self.watchdirs[watchdir_id]["path"], failure) diff --git a/deluge/plugins/notifications/notifications/data/config.glade b/deluge/plugins/notifications/notifications/data/config.glade index f1603505d..4da1de6bd 100644 --- a/deluge/plugins/notifications/notifications/data/config.glade +++ b/deluge/plugins/notifications/notifications/data/config.glade @@ -213,7 +213,7 @@ 65535 5 - 25 1 100 1 10 0 + 25 1 65535 0 10 0 1 True True @@ -292,6 +292,7 @@ True automatic automatic + in True diff --git a/deluge/ui/gtkui/glade/main_window.glade b/deluge/ui/gtkui/glade/main_window.glade index 8beb2ed36..cb0db23b8 100644 --- a/deluge/ui/gtkui/glade/main_window.glade +++ b/deluge/ui/gtkui/glade/main_window.glade @@ -384,174 +384,217 @@ - + True False - + True - False False - Add torrent - False - Add Torrent - True - gtk-add - + + + True + False + False + True + Add torrent + False + Add Torrent + True + gtk-add + + + + False + True + + + + + True + False + False + True + Remove torrent + False + Remove Torrent + gtk-remove + + + + False + True + + + + + True + False + + + False + + + + + True + False + False + True + Pause the selected torrents + False + Pause + True + gtk-media-pause + + + + False + True + + + + + True + False + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + Resume the selected torrents + False + Resume + gtk-media-play + + + + False + True + + + + + True + False + + + False + + + + + True + False + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + Queue Torrent Up + False + Queue Up + gtk-go-up + + + + False + True + + + + + True + False + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + Queue Torrent Down + False + Queue Down + gtk-go-down + + + + False + True + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + + + + + True + False + True + Preferences + False + Preferences + True + gtk-preferences + + + + False + True + + + + + True + False + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + Connection Manager + False + Connection Manager + gtk-network + + + + False + True + + - False - True + True + True + 0 - + True - False - False - Remove torrent - False - Remove Torrent - gtk-remove - + True + Search torrents by name + + True + False + gtk-clear + False + True + False + True + Clear the search + + False - True - - - - - True - False - - - False - - - - - True - False - False - Pause the selected torrents - False - Pause - True - gtk-media-pause - - - - False - True - - - - - True - False - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Resume the selected torrents - False - Resume - gtk-media-play - - - - False - True - - - - - True - False - - - False - - - - - True - False - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue Torrent Up - False - Queue Up - gtk-go-up - - - - False - True - - - - - True - False - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Queue Torrent Down - False - Queue Down - gtk-go-down - - - - False - True - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - False - - - - - True - False - Preferences - False - Preferences - True - gtk-preferences - - - - False - True - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Connection Manager - False - Connection Manager - gtk-network - - - - False - True + False + 5 + 1 False - True + False 1 @@ -787,152 +830,6 @@ - - True - False - - - gtk-open - True - False - False - True - True - - - - - - True - False - - - - - _Expand All - True - False - False - True - False - - - - True - False - gtk-zoom-fit - 1 - - - - - - - True - False - - - - - _Do Not Download - True - False - False - True - False - - - - True - False - gtk-no - 1 - - - - - - - _Normal Priority - True - False - False - True - False - - - - True - False - gtk-yes - 1 - - - - - - - _High Priority - True - False - False - True - False - - - - True - False - gtk-go-up - 1 - - - - - - - Hi_ghest Priority - True - False - False - True - False - - - - True - False - gtk-goto-top - 1 - - - - - - - True - False - - - _Add Peer - True - False - Add a peer by its IP - False - True - False - - - - True - False - gtk-add - 1 - - - - - False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -2877,6 +2774,7 @@ False False True + False @@ -2949,6 +2847,7 @@ False False True + diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index ab23f45ad..9221d3bf7 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -46,6 +46,8 @@ import logging import warnings from urlparse import urlparse +from twisted.internet import reactor + import deluge.common import deluge.component as component from deluge.ui.client import client @@ -270,6 +272,7 @@ class TorrentView(listview.ListView, component.Component): # Set filter to None for now self.filter = None + self.search_pending = None ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the @@ -287,6 +290,15 @@ class TorrentView(listview.ListView, component.Component): self.treeview.connect("key-press-event", self.on_key_press_event) self.treeview.connect("columns-changed", self.on_columns_changed_event) + self.search_torrents_entry = self.window.main_glade.get_widget("search_torrents_entry") + self.search_torrents_entry.connect( + "icon-press", self.on_search_torrents_entry_icon_press + ) + self.search_torrents_entry.connect( + "changed", self.on_search_torrents_entry_changed + ) + + client.register_event_handler("TorrentStateChangedEvent", self.on_torrentstatechanged_event) client.register_event_handler("TorrentAddedEvent", self.on_torrentadded_event) client.register_event_handler("TorrentRemovedEvent", self.on_torrentremoved_event) @@ -319,6 +331,8 @@ class TorrentView(listview.ListView, component.Component): # We need to clear the liststore self.liststore.clear() self.prev_status = {} + self.filter = None + self.search_torrents_entry.set_text("") def shutdown(self): """Called when GtkUi is exiting""" @@ -335,7 +349,10 @@ class TorrentView(listview.ListView, component.Component): """Sets filters for the torrentview.. see: core.get_torrents_status """ + search_filter = self.filter and self.filter.get('name', None) or None self.filter = dict(filter_dict) #copied version of filter_dict. + if search_filter and 'name' not in filter_dict: + self.filter['name'] = search_filter self.update() def set_columns_to_update(self, columns=None): @@ -379,6 +396,9 @@ class TorrentView(listview.ListView, component.Component): def update(self): if self.got_state: + if self.search_pending is not None and self.search_pending.active(): + # An update request is scheduled, let's wait for that one + return # Send a status request gobject.idle_add(self.send_status_request) @@ -600,3 +620,33 @@ class TorrentView(listview.ListView, component.Component): torrentmenu = component.get("MenuBar").torrentmenu torrentmenu.popup(None, None, None, 3, event.time) return True + + def on_search_torrents_entry_icon_press(self, entry, icon, event): + if icon != gtk.ENTRY_ICON_SECONDARY: + return + + if self.search_pending and self.search_pending.active(): + self.search_pending.cancel() + + entry.set_text("") + if self.filter and 'name' in self.filter: + self.filter.pop('name', None) + self.search_pending = reactor.callLater(0.7, self.update) + + def on_search_torrents_entry_changed(self, widget): + search_string = widget.get_text().lower() + + if self.search_pending and self.search_pending.active(): + self.search_pending.cancel() + + if not search_string: + if self.filter and 'name' in self.filter: + self.filter.pop('name', None) + self.search_pending = reactor.callLater(0.7, self.update) + return + + if self.filter is None: + self.filter = {} + + self.filter['name'] = search_string + self.search_pending = reactor.callLater(0.7, self.update)