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 @@
-
-
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)