diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | deluge/core/alertmanager.py | 3 | ||||
-rw-r--r-- | deluge/core/rpcserver.py | 2 | ||||
-rw-r--r-- | deluge/core/torrentmanager.py | 22 | ||||
-rw-r--r-- | deluge/main.py | 51 | ||||
-rw-r--r-- | deluge/plugins/Label/deluge/plugins/label/gtkui/__init__.py | 3 | ||||
-rw-r--r-- | deluge/plugins/Notifications/deluge/plugins/notifications/gtkui.py | 2 | ||||
-rw-r--r-- | deluge/ui/gtkui/addtorrentdialog.py | 9 | ||||
-rw-r--r-- | deluge/ui/gtkui/listview.py | 55 | ||||
-rw-r--r-- | deluge/ui/gtkui/mainwindow.py | 16 | ||||
-rw-r--r-- | deluge/ui/gtkui/torrentview.py | 97 | ||||
-rw-r--r-- | deluge/ui/sessionproxy.py | 2 | ||||
-rw-r--r-- | deluge/ui/web/auth.py | 1 | ||||
-rw-r--r-- | deluge/ui/web/js/deluge-all/preferences/ProxyField.js | 15 |
14 files changed, 169 insertions, 110 deletions
@@ -104,6 +104,7 @@ * Fix the Open Folder option not working with non-ascii paths * Fix the daemon starting with config dir containing spaces * Fix Windows tray submenu items requiring right-click instead of left-click + * Fix issue with adding some torrents with illegal characters via url in gtk client ==== OS X ==== * Fix Open File/Folder option diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 970a1506e..bcc60fedf 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -46,6 +46,7 @@ from twisted.internet import reactor import deluge.component as component from deluge._libtorrent import lt +from deluge.common import decode_string log = logging.getLogger(__name__) @@ -123,7 +124,7 @@ class AlertManager(component.Component): alert_type = type(alert).__name__ # Display the alert message if log.isEnabledFor(logging.DEBUG): - log.debug("%s: %s", alert_type, alert.message()) + log.debug("%s: %s", alert_type, decode_string(alert.message())) # Call any handlers for this alert type if alert_type in self.handlers: for handler in self.handlers[alert_type]: diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index 3ecbd39e8..649c2ce29 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -509,7 +509,7 @@ class RPCServer(component.Component): """ log.debug("intevents: %s", self.factory.interested_events) # Find sessions interested in this event - for session_id, interest in self.factory.interested_events.iteritems(): + for session_id, interest in self.factory.interested_events.items(): if event.name in interest: log.debug("Emit Event: %s %s", event.name, event.args) # This session is interested so send a RPC_EVENT diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index 3efc9a400..6dafd1939 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -57,7 +57,7 @@ from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.core.torrent import Torrent from deluge.core.torrent import TorrentOptions import deluge.core.oldstateupgrader -from deluge.common import utf8_encoded +from deluge.common import utf8_encoded, decode_string log = logging.getLogger(__name__) @@ -390,7 +390,8 @@ class TorrentManager(component.Component): resume_data = self.legacy_get_resume_data_from_file(state.torrent_id) self.legacy_delete_resume_data(state.torrent_id) - add_torrent_params["resume_data"] = resume_data + if resume_data: + add_torrent_params["resume_data"] = resume_data else: # We have a torrent_info object or magnet uri so we're not loading from state. if torrent_info: @@ -970,15 +971,14 @@ class TorrentManager(component.Component): def on_alert_tracker_reply(self, alert): if log.isEnabledFor(logging.DEBUG): - log.debug("on_alert_tracker_reply: %s", alert.message().decode("utf8")) + log.debug("on_alert_tracker_reply: %s", decode_string(alert.message())) try: torrent = self.torrents[str(alert.handle.info_hash())] except: return # Set the tracker status for the torrent - if alert.message() != "Got peers from DHT": - torrent.set_tracker_status(_("Announce OK")) + torrent.set_tracker_status(_("Announce OK")) # Check to see if we got any peer information from the tracker if alert.handle.status().num_complete == -1 or \ @@ -1003,7 +1003,7 @@ class TorrentManager(component.Component): torrent = self.torrents[str(alert.handle.info_hash())] except: return - tracker_status = '%s: %s' % (_("Warning"), str(alert.message())) + tracker_status = '%s: %s' % (_("Warning"), decode_string(alert.message())) # Set the tracker status for the torrent torrent.set_tracker_status(tracker_status) @@ -1013,7 +1013,7 @@ class TorrentManager(component.Component): torrent = self.torrents[str(alert.handle.info_hash())] except: return - tracker_status = "%s: %s" % (_("Error"), alert.msg) + tracker_status = "%s: %s" % (_("Error"), decode_string(alert.msg)) torrent.set_tracker_status(tracker_status) def on_alert_storage_moved(self, alert): @@ -1073,11 +1073,11 @@ class TorrentManager(component.Component): self.waiting_on_resume_data[torrent_id].callback(None) def on_alert_save_resume_data_failed(self, alert): - log.debug("on_alert_save_resume_data_failed: %s", alert.message()) + log.debug("on_alert_save_resume_data_failed: %s", decode_string(alert.message())) torrent_id = str(alert.handle.info_hash()) if torrent_id in self.waiting_on_resume_data: - self.waiting_on_resume_data[torrent_id].errback(Exception(alert.message())) + self.waiting_on_resume_data[torrent_id].errback(Exception(decode_string(alert.message()))) def on_alert_file_renamed(self, alert): log.debug("on_alert_file_renamed") @@ -1107,7 +1107,7 @@ class TorrentManager(component.Component): torrent.write_torrentfile() def on_alert_file_error(self, alert): - log.debug("on_alert_file_error: %s", alert.message()) + log.debug("on_alert_file_error: %s", decode_string(alert.message())) try: torrent = self.torrents[str(alert.handle.info_hash())] except: @@ -1115,7 +1115,7 @@ class TorrentManager(component.Component): torrent.update_state() def on_alert_file_completed(self, alert): - log.debug("file_completed_alert: %s", alert.message()) + log.debug("file_completed_alert: %s", decode_string(alert.message())) try: torrent_id = str(alert.handle.info_hash()) except: diff --git a/deluge/main.py b/deluge/main.py index a1525df1c..c68ab3d5e 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -247,27 +247,32 @@ this should be an IP address", metavar="IFACE", open_logfile() + def run_daemon(options, args): + try: + from deluge.core.daemon import Daemon + Daemon(options, args) + except deluge.error.DaemonRunningError, e: + log.error(e) + log.error("You cannot run multiple daemons with the same config directory set.") + log.error("If you believe this is an error, you can force a start by deleting %s.", deluge.configmanager.get_config_dir("deluged.pid")) + sys.exit(1) + except Exception, e: + log.exception(e) + sys.exit(1) + if options.profile: - import hotshot - hsp = hotshot.Profile(deluge.configmanager.get_config_dir("deluged.profile")) - hsp.start() - try: - from deluge.core.daemon import Daemon - Daemon(options, args) - except deluge.error.DaemonRunningError, e: - log.error(e) - log.error("You cannot run multiple daemons with the same config directory set.") - log.error("If you believe this is an error, you can force a start by deleting %s.", deluge.configmanager.get_config_dir("deluged.pid")) - sys.exit(1) - except Exception, e: - log.exception(e) - sys.exit(1) - finally: - if options.profile: - hsp.stop() - hsp.close() - import hotshot.stats - stats = hotshot.stats.load(deluge.configmanager.get_config_dir("deluged.profile")) - stats.strip_dirs() - stats.sort_stats("time", "calls") - stats.print_stats(400) + import cProfile + profiler = cProfile.Profile() + profile_output = deluge.configmanager.get_config_dir("deluged.profile") + + # Twisted catches signals to terminate + def save_profile_stats(): + profiler.dump_stats(profile_output) + print "Profile stats saved to %s" % profile_output + + from twisted.internet import reactor + reactor.addSystemEventTrigger("before", "shutdown", save_profile_stats) + print "Running with profiler..." + profiler.runcall(run_daemon, options, args) + else: + run_daemon(options, args) diff --git a/deluge/plugins/Label/deluge/plugins/label/gtkui/__init__.py b/deluge/plugins/Label/deluge/plugins/label/gtkui/__init__.py index 8ee0d3c59..bf69e289b 100644 --- a/deluge/plugins/Label/deluge/plugins/label/gtkui/__init__.py +++ b/deluge/plugins/Label/deluge/plugins/label/gtkui/__init__.py @@ -74,11 +74,8 @@ class GtkUI(GtkPluginBase): self.sidebar_menu.unload() del self.sidebar_menu - - component.get("TorrentView").remove_column(_("Label")) log.debug(1.1) - component.get("TorrentView").create_model_filter() #todo:improve. except Exception, e: log.debug(e) diff --git a/deluge/plugins/Notifications/deluge/plugins/notifications/gtkui.py b/deluge/plugins/Notifications/deluge/plugins/notifications/gtkui.py index b3e5acbb3..44558fda4 100644 --- a/deluge/plugins/Notifications/deluge/plugins/notifications/gtkui.py +++ b/deluge/plugins/Notifications/deluge/plugins/notifications/gtkui.py @@ -242,7 +242,7 @@ class GtkUiNotifications(CustomNotifications): log.debug("Handler for TorrentFinishedEvent GTKUI called. " "Got Torrent Status") title = _("Finished Torrent") - message = _("The torrent \"%(name)s\" including %(num_files)i " + message = _("The torrent \"%(name)s\" including %(num_files)i file(s) " "has finished downloading.") % torrent_status return title, message diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index 06bdd6c90..13ac640d6 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -180,7 +180,11 @@ class AddTorrentDialog(component.Component): self.builder.get_object("button_move_completed_location").hide() self.builder.get_object("entry_move_completed_path").show() - self.dialog.set_transient_for(component.get("MainWindow").window) + if component.get("MainWindow").is_on_active_workspace(): + self.dialog.set_transient_for(component.get("MainWindow").window) + else: + self.dialog.set_transient_for(None) + self.dialog.present() if focus: self.dialog.window.focus() @@ -690,8 +694,7 @@ class AddTorrentDialog(component.Component): # Create a tmp file path import tempfile - import os.path - tmp_file = os.path.join(tempfile.gettempdir(), url.split("/")[-1]) + (tmp_handle, tmp_file) = tempfile.mkstemp() def on_part(data, current_length, total_length): if total_length: diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 204f09bdb..120dda3e7 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -237,17 +237,30 @@ class ListView: model_filter = self.liststore.filter_new() model_filter.set_visible_column( self.columns["filter"].column_indices[0]) - sort_info = None - if self.model_filter: - sort_info = self.model_filter.get_sort_column_id() - self.model_filter = gtk.TreeModelSort(model_filter) - if sort_info and sort_info[0] and sort_info[1] > -1: - self.model_filter.set_sort_column_id(sort_info[0], sort_info[1]) - self.set_sort_functions() self.model_filter.connect("sort-column-changed", self.on_model_sort_changed) self.model_filter.connect("row-inserted", self.on_model_row_inserted) self.treeview.set_model(self.model_filter) + self.set_sort_functions() + self.set_model_sort() + + def set_model_sort(self): + column_state = self.get_sort_column_from_state() + if column_state: + self.treeview.get_model().set_sort_column_id(column_state.sort, column_state.sort_order) + # Using the default sort column + elif self.default_sort_column_id: + self.model_filter.set_sort_column_id(self.default_sort_column_id, gtk.SORT_ASCENDING) + self.model_filter.set_default_sort_func(None) + + def get_sort_column_from_state(self): + """Find the first (should only be one) state with sort enabled""" + if self.state is None: + return None + for column_state in self.state: + if column_state.sort is not None and column_state.sort > -1: + return column_state + return None def on_model_sort_changed(self, model): if self.unique_column_id: @@ -281,7 +294,6 @@ class ListView: return cmp(model[iter1][data], model[iter2][data]) def set_sort_functions(self): - self.model_filter.set_default_sort_func(None) for column in self.columns.values(): sort_func = column.sort_func or self.generic_sort_func self.model_filter.set_sort_func( @@ -297,9 +309,10 @@ class ListView: position = index break sort = None - sort_id, order = self.model_filter.get_sort_column_id() - if self.get_column_name(sort_id) == column.get_title(): - sort = sort_id + if self.model_filter: + sort_id, order = self.model_filter.get_sort_column_id() + if self.get_column_name(sort_id) == column.get_title(): + sort = sort_id return ListViewColumnState(column.get_title(), position, column.get_width(), column.get_visible(), @@ -449,10 +462,6 @@ class ListView: self.liststore.foreach(copy_row, (new_list, self.columns)) self.liststore = new_list - # Create the model - self.create_model_filter() - - return def remove_column(self, header): """Removes the column with the name 'header' from the listview""" @@ -481,10 +490,11 @@ class ListView: # Create a new liststore self.create_new_liststore() + # Create new model for the treeview + self.create_model_filter() # Re-create the menu self.create_checklist_menu() - return def add_column(self, header, render, col_types, hidden, position, @@ -523,6 +533,10 @@ class ListView: # Create a new list with the added column self.create_new_liststore() + # Happens only on columns added after the torrent list has been loaded + if self.model_filter: + self.create_model_filter() + column = self.TreeviewColumn(header) if column_type == "text": @@ -575,11 +589,6 @@ class ListView: if tooltip: column.get_widget().set_tooltip_markup(tooltip) - if default_sort: - if self.model_filter.get_sort_column_id()[0] is None: - self.model_filter.set_sort_column_id(column_indices[sortid], - gtk.SORT_ASCENDING) - # Check for loaded state and apply column_in_state = False if self.state != None: @@ -591,10 +600,6 @@ class ListView: column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(column_state.width) - if column_state.sort is not None and column_state.sort > -1: - self.model_filter.set_sort_column_id( - column_state.sort, column_state.sort_order - ) column.set_visible(column_state.visible) position = column_state.position break diff --git a/deluge/ui/gtkui/mainwindow.py b/deluge/ui/gtkui/mainwindow.py index d491441d0..4d07a5750 100644 --- a/deluge/ui/gtkui/mainwindow.py +++ b/deluge/ui/gtkui/mainwindow.py @@ -42,6 +42,11 @@ import gtk import logging import urllib +try: + import wnck +except ImportError: + wnck = None + from deluge.ui.client import client import deluge.component as component from deluge.configmanager import ConfigManager @@ -76,6 +81,8 @@ class _GtkBuilderSignalsHolder(object): class MainWindow(component.Component): def __init__(self): + if wnck: + self.screen = wnck.screen_get_default() component.Component.__init__(self, "MainWindow", interval=2) self.config = ConfigManager("gtkui.conf") self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder() @@ -167,7 +174,6 @@ class MainWindow(component.Component): component.resume("TorrentDetails") except: pass - self.window.show() @@ -324,3 +330,11 @@ class MainWindow(component.Component): def on_torrentfinished_event(self, torrent_id): from deluge.ui.gtkui.notification import Notification Notification().notify(torrent_id) + + def is_on_active_workspace(self): + # Returns True if mainwindow is on active workspace or wnck module not available + if not wnck: + return True + for win in self.screen.get_windows(): + if win.get_xid() == self.window.window.xid: + return win.is_on_workspace(win.get_screen().get_active_workspace()) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 79fd99831..b75f593fc 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -452,18 +452,12 @@ class TorrentView(listview.ListView, component.Component): {}, status_fields).addCallback(self._on_session_state) def _on_session_state(self, state): - self.treeview.freeze_child_notify() - model = self.treeview.get_model() - for torrent_id in state: - self.add_row(torrent_id, update=False) - self.mark_dirty(torrent_id) - self.treeview.set_model(model) - self.treeview.thaw_child_notify() + self.add_rows(state) self.got_state = True # Update the view right away with our status self.status = state self.set_columns_to_update() - self.update_view() + self.update_view(load_new_list=True) def stop(self): """Stops the torrentview""" @@ -551,43 +545,66 @@ class TorrentView(listview.ListView, component.Component): # Send a status request gobject.idle_add(self.send_status_request) - def update_view(self, columns=None): - """Update the view. If columns is not None, it will attempt to only - update those columns selected. - """ + def update_view(self, load_new_list=False): + """Update the torrent view model with data we've received.""" filter_column = self.columns["filter"].column_indices[0] - # Update the torrent view model with data we've received status = self.status + if not load_new_list: + # Freeze notications while updating + self.treeview.freeze_child_notify() + + # Get the columns to update from one of the torrents + if status: + torrent_id = status.keys()[0] + fields_to_update = [] + for column in self.columns_to_update: + column_index = self.get_column_index(column) + for i, status_field in enumerate(self.columns[column].status_field): + # Only use columns that the torrent has in the state + if status_field in status[torrent_id]: + fields_to_update.append((column_index[i], status_field)) + for row in self.liststore: torrent_id = row[self.columns["torrent_id"].column_indices[0]] + # We expect the torrent_id to be in status and prev_status, + # as it will be as long as the list isn't changed by the user - if not torrent_id in status.keys(): - row[filter_column] = False - else: - row[filter_column] = True - if torrent_id in self.prev_status and status[torrent_id] == self.prev_status[torrent_id]: - # The status dict is the same, so do not update + torrent_id_in_status = False + try: + torrent_status = status[torrent_id] + torrent_id_in_status = True + if torrent_status == self.prev_status[torrent_id]: + # The status dict is the same, so do nothing to update for this torrent continue + except KeyError, e: + pass - # Set values for each column in the row - for column in self.columns_to_update: - column_index = self.get_column_index(column) - for i, status_field in enumerate(self.columns[column].status_field): - if status_field in status[torrent_id]: - try: - # Only update if different - row_value = status[torrent_id][status_field] - if row[column_index[i]] != row_value: - row[column_index[i]] = row_value - except Exception, e: - log.debug("Error while updating row for column " - "index %d, status field %r, value %r:" - " %s", column_index[0], status_field, - row_value, e) + if not torrent_id_in_status: + if row[filter_column] is True: + row[filter_column] = False + else: + if row[filter_column] is False: + row[filter_column] = True + + # Find the fields to update + to_update = [] + for i, status_field in fields_to_update: + row_value = status[torrent_id][status_field] + if row[i] != row_value: + to_update.append(i) + to_update.append(row_value) + # Update fields in the liststore + if to_update: + self.liststore.set(row.iter, *to_update) + + if load_new_list: + # Create the model filter. This sets the model for the treeview and enables sorting. + self.create_model_filter() + else: + self.treeview.thaw_child_notify() component.get("MenuBar").update_menu() - self.prev_status = status def _on_get_torrents_status(self, status): @@ -602,6 +619,16 @@ class TorrentView(listview.ListView, component.Component): return gobject.idle_add(self.update_view) + def add_rows(self, state): + """Adds all the torrents from state to self.liststore""" + torrent_id_column = self.columns["torrent_id"].column_indices[0] + dirty_column = self.columns["dirty"].column_indices[0] + filter_column = self.columns["filter"].column_indices[0] + for i, torrent_id in enumerate(state): + # Insert a new row to the liststore + row = self.liststore.append() + self.liststore.set(row, torrent_id_column, torrent_id, dirty_column, True, filter_column, True) + def add_row(self, torrent_id, update=True): """Adds a new torrent row to the treeview""" # Make sure this torrent isn't already in the list diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py index 676dfabb7..0b5234704 100644 --- a/deluge/ui/sessionproxy.py +++ b/deluge/ui/sessionproxy.py @@ -65,11 +65,11 @@ class SessionProxy(component.Component): # Holds the time of the last key update.. {torrent_id: {key1, time, ...}, ...} self.cache_times = {} + def start(self): client.register_event_handler("TorrentStateChangedEvent", self.on_torrent_state_changed) client.register_event_handler("TorrentRemovedEvent", self.on_torrent_removed) client.register_event_handler("TorrentAddedEvent", self.on_torrent_added) - def start(self): def on_get_session_state(torrent_ids): for torrent_id in torrent_ids: # Let's at least store the torrent ids with empty statuses diff --git a/deluge/ui/web/auth.py b/deluge/ui/web/auth.py index 5cd345e2c..e26eab486 100644 --- a/deluge/ui/web/auth.py +++ b/deluge/ui/web/auth.py @@ -322,4 +322,5 @@ class Auth(JSONComponent): if self.check_password(password): return self._create_session(__request__) else: + log.error('Login failed (ClientIP %s)', __request__.getClientIP()) return False diff --git a/deluge/ui/web/js/deluge-all/preferences/ProxyField.js b/deluge/ui/web/js/deluge-all/preferences/ProxyField.js index 811372128..fe969b9ba 100644 --- a/deluge/ui/web/js/deluge-all/preferences/ProxyField.js +++ b/deluge/ui/web/js/deluge-all/preferences/ProxyField.js @@ -1,6 +1,6 @@ /*! * Deluge.preferences.ProxyField.js - * + * * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com> * * This program is free software; you can redistribute it and/or modify @@ -58,19 +58,23 @@ Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, { [3, _('Socksv5 with Auth')], [4, _('HTTP')], [5, _('HTTP with Auth')] - ] - }), + ] + }), editable: false, triggerAction: 'all', valueField: 'id', displayField: 'text' }); + this.proxyType.on('change', this.onFieldChange, this); + this.proxyType.on('select', this.onTypeSelect, this); + this.hostname = this.add({ xtype: 'textfield', name: 'hostname', fieldLabel: _('Host'), width: 220 }); + this.hostname.on('change', this.onFieldChange, this); this.port = this.add({ xtype: 'spinnerfield', @@ -81,6 +85,7 @@ Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, { minValue: -1, maxValue: 99999 }); + this.port.on('change', this.onFieldChange, this); this.username = this.add({ xtype: 'textfield', @@ -88,6 +93,7 @@ Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, { fieldLabel: _('Username'), width: 220 }); + this.username.on('change', this.onFieldChange, this); this.password = this.add({ xtype: 'textfield', @@ -96,9 +102,8 @@ Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, { inputType: 'password', width: 220 }); + this.password.on('change', this.onFieldChange, this); - this.proxyType.on('change', this.onFieldChange, this); - this.proxyType.on('select', this.onTypeSelect, this); this.setting = false; }, |