diff --git a/deluge/plugins/notifications/notifications/common.py b/deluge/plugins/notifications/notifications/common.py index 83480422c..528f29821 100644 --- a/deluge/plugins/notifications/notifications/common.py +++ b/deluge/plugins/notifications/notifications/common.py @@ -68,19 +68,19 @@ class CustomNotifications(component.Component): self.__deregister_custom_provider(kind, eventtype) - def handle_custom_email_notification(self, result): + def handle_custom_email_notification(self, result, eventtype): raise NotImplementedError("%s does not implement this function" % self.__class__.__name__) - def handle_custom_popup_notification(self, result): + def handle_custom_popup_notification(self, result, eventtype): raise NotImplementedError("%s does not implement this function" % self.__class__.__name__) - def handle_custom_blink_notification(self, result): + def handle_custom_blink_notification(self, result, eventtype): raise NotImplementedError("%s does not implement this function" % self.__class__.__name__) - def handle_custom_sound_notification(self, result): + def handle_custom_sound_notification(self, result, eventtype): raise NotImplementedError("%s does not implement this function" % self.__class__.__name__) @@ -164,15 +164,15 @@ class CustomNotifications(component.Component): self.__deregister_custom_provider('sound', eventtype) def __handle_custom_providers(self, kind, eventtype, *args, **kwargs): - log.debug("\n\nCalling CORE's custom email providers for %s: %s %s", - eventtype, args, kwargs) + log.debug("\n\nCalling CORE's custom %s providers for %s: %s %s", + kind, eventtype, args, kwargs) if eventtype in self.config["subscriptions"][kind]: wrapper, handler = self.custom_notifications[kind][eventtype] log.debug("Found handler for kind %s: %s", kind, handler) custom_notif_func = getattr(self, 'handle_custom_%s_notification' % kind) d = defer.maybeDeferred(handler, *args, **kwargs) - d.addCallback(custom_notif_func) + d.addCallback(custom_notif_func, eventtype) d.addCallback(self._on_notify_sucess, kind) d.addErrback(self._on_notify_failure, kind) return d diff --git a/deluge/plugins/notifications/notifications/core.py b/deluge/plugins/notifications/notifications/core.py index b44029a0f..c9b7d5d77 100644 --- a/deluge/plugins/notifications/notifications/core.py +++ b/deluge/plugins/notifications/notifications/core.py @@ -112,7 +112,7 @@ class Core(CorePluginBase, CustomNotifications): log.debug("Handled Notification Events: %s", handled_events) return handled_events - def handle_custom_email_notification(self, result): + def handle_custom_email_notification(self, result, eventtype): if not self.config['smtp_enabled']: return defer.succeed("SMTP notification not enabled.") subject, message = result @@ -204,17 +204,9 @@ Subject: %(subject)s "Thank you,\nDeluge." ) % torrent_status - d = defer.maybeDeferred(self._prepare_email, [subject, message]) - d.addCallback(self._on_notify_sucess) - d.addErrback(self._on_notify_failure) + d = defer.maybeDeferred(self.handle_custom_email_notification, + [subject, message], + "TorrentFinishedEvent") + d.addCallback(self._on_notify_sucess, 'email') + d.addErrback(self._on_notify_failure, 'email') return d - - -# def _on_notify_sucess(self, result): -# log.debug("\n\nEMAIL Notification success: %s", result) -# return result -# -# -# def _on_notify_failure(self, failure): -# log.debug("\n\nEMAIL Notification failure: %s", failure) -# return failure diff --git a/deluge/plugins/notifications/notifications/data/config.glade b/deluge/plugins/notifications/notifications/data/config.glade index 842b93184..d4651af30 100644 --- a/deluge/plugins/notifications/notifications/data/config.glade +++ b/deluge/plugins/notifications/notifications/data/config.glade @@ -121,6 +121,7 @@ True False + 2 @@ -517,11 +518,83 @@ - + + True + vertical + + + True + True + automatic + automatic + + + True + True + + + + + 0 + + + + + True + 10 + end + + + gtk-revert-to-saved + True + False + True + True + True + + + + False + False + 0 + + + + + gtk-edit + True + False + True + True + True + + + + False + False + 1 + + + + + False + 5 + 1 + + + + + 2 + - + + True + Sound Customization + + 2 + False tab diff --git a/deluge/plugins/notifications/notifications/gtkui.py b/deluge/plugins/notifications/notifications/gtkui.py index 1d9167ec3..ad18b1d5c 100644 --- a/deluge/plugins/notifications/notifications/gtkui.py +++ b/deluge/plugins/notifications/notifications/gtkui.py @@ -37,6 +37,7 @@ # statement from all source files in the program, then also delete it here. # +from os.path import basename import gtk from twisted.internet import defer @@ -74,17 +75,19 @@ DEFAULT_PREFS = { # SOUND "sound_enabled": False, "sound_path": "", + "custom_sounds": {}, # Subscriptions "subscriptions": { "popup": [], "blink": [], "sound": [], - } + }, } RECIPIENT_FIELD, RECIPIENT_EDIT = range(2) (SUB_EVENT, SUB_EVENT_DOC, SUB_NOT_EMAIL, SUB_NOT_POPUP, SUB_NOT_BLINK, SUB_NOT_SOUND) = range(6) +SND_EVENT, SND_EVENT_DOC, SND_NAME, SND_PATH = range(4) class GtkUI(GtkPluginBase, CustomNotifications): @@ -102,6 +105,61 @@ class GtkUI(GtkPluginBase, CustomNotifications): self.prefs = self.glade.get_widget("prefs_box") self.prefs.show_all() + self.build_recipients_model_populate_treeview() + self.build_sounds_model_populate_treeview() + self.build_notifications_model_populate_treeview() + + + client.notifications.get_handled_events().addCallback( + self.popuplate_what_needs_handled_events + ) + + self.glade.signal_autoconnect({ + 'on_add_button_clicked': (self.on_add_button_clicked, + self.recipients_treeview), + 'on_delete_button_clicked': (self.on_delete_button_clicked, + self.recipients_treeview), + 'on_enabled_toggled': self.on_enabled_toggled, + 'on_sound_enabled_toggled': self.on_sound_enabled_toggled, + 'on_sounds_edit_button_clicked': self.on_sounds_edit_button_clicked, + 'on_sounds_revert_button_clicked': \ + self.on_sounds_revert_button_clicked, + 'on_sound_path_update_preview': self.on_sound_path_update_preview + }) + +# component.get("Preferences").add_page("Notifications", self.prefs) + prefs = component.get("Preferences") + parent = self.prefs.get_parent() + if parent: + parent.remove(self.prefs) + index = prefs.notebook.append_page(self.prefs) + prefs.liststore.append([index, "Notifications"]) + + component.get("PluginManager").register_hook("on_apply_prefs", + self.on_apply_prefs) + component.get("PluginManager").register_hook("on_show_prefs", + self.on_show_prefs) + + if not POPUP_AVAILABLE: + self.glade.get_widget("popup_enabled").set_property('sensitive', + False) + if not SOUND_AVAILABLE: + self.glade.get_widget("sound_enabled").set_property('sensitive', + False) + self.glade.get_widget('sound_path').set_property('sensitive', False) + + self.systray = component.get("SystemTray") + if not hasattr(self.systray, 'tray'): + # Tray is not beeing used + self.glade.get_widget('blink_enabled').set_property('sensitive', + False) + + client.register_event_handler("TorrentFinishedEvent", + self._on_torrent_finished_event) + + self.tn.enable() + + def build_recipients_model_populate_treeview(self): # SMTP Recipients treeview/model self.recipients_treeview = self.glade.get_widget("smtp_recipients") treeview_selection = self.recipients_treeview.get_selection() @@ -120,6 +178,45 @@ class GtkUI(GtkPluginBase, CustomNotifications): self.recipients_treeview.append_column(column) self.recipients_treeview.set_model(self.recipients_model) + def build_sounds_model_populate_treeview(self): + # Sound customisation treeview/model + self.sounds_treeview = self.glade.get_widget('sounds_treeview') + sounds_selection = self.sounds_treeview.get_selection() + sounds_selection.connect( + "changed", self.on_sounds_treeview_selection_changed + ) + + self.sounds_treeview.set_tooltip_column(SND_EVENT_DOC) + self.sounds_model = gtk.ListStore(str, str, str, str) + + renderer = gtk.CellRendererText() + renderer.set_data("event", SND_EVENT) + column = gtk.TreeViewColumn("Event", renderer, text=SND_EVENT) + column.set_expand(True) + self.sounds_treeview.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_data("event_doc", SND_EVENT_DOC) + column = gtk.TreeViewColumn("Doc", renderer, text=SND_EVENT_DOC) + column.set_property('visible', False) + self.sounds_treeview.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_data("sound_name", SND_NAME) + column = gtk.TreeViewColumn("Name", renderer, text=SND_NAME) + self.sounds_treeview.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_data("sound_path", SND_PATH) + column = gtk.TreeViewColumn("Path", renderer, text=SND_PATH) + column.set_property('visible', False) + self.sounds_treeview.append_column(column) + + self.sounds_treeview.connect("button-press-event", + self.on_sounds_treeview_clicked) + self.sounds_treeview.set_model(self.sounds_model) + + def build_notifications_model_populate_treeview(self): # Notification Subscriptions treeview/model self.subscriptions_treeview = self.glade.get_widget("subscriptions_treeview") subscriptions_selection = self.subscriptions_treeview.get_selection() @@ -185,52 +282,6 @@ class GtkUI(GtkPluginBase, CustomNotifications): self.subscriptions_treeview.append_column(column) self.subscriptions_treeview.set_model(self.subscriptions_model) - client.notifications.get_handled_events().addCallback( - self.popuplate_subscriptions - ) - - self.glade.signal_autoconnect({ - 'on_add_button_clicked': (self.on_add_button_clicked, - self.recipients_treeview), - 'on_delete_button_clicked': (self.on_delete_button_clicked, - self.recipients_treeview), - 'on_enabled_toggled': self.on_enabled_toggled, - 'on_sound_enabled_toggled': self.on_sound_enabled_toggled - }) - -# component.get("Preferences").add_page("Notifications", self.prefs) - prefs = component.get("Preferences") - parent = self.prefs.get_parent() - if parent: - parent.remove(self.prefs) - index = prefs.notebook.append_page(self.prefs) - prefs.liststore.append([index, "Notifications"]) - - component.get("PluginManager").register_hook("on_apply_prefs", - self.on_apply_prefs) - component.get("PluginManager").register_hook("on_show_prefs", - self.on_show_prefs) - - if not POPUP_AVAILABLE: - self.glade.get_widget("popup_enabled").set_property('sensitive', - False) - if not SOUND_AVAILABLE: - self.glade.get_widget("sound_enabled").set_property('sensitive', - False) - self.glade.get_widget('sound_path').set_property('sensitive', False) - - self.systray = component.get("SystemTray") - if not hasattr(self.systray, 'tray'): - # Tray is not beeing used - self.glade.get_widget('blink_enabled').set_property('sensitive', - False) - - - client.register_event_handler("TorrentFinishedEvent", - self._on_torrent_finished_event) - - self.tn.enable() - def disable(self): self.tn.disable() CustomNotifications.disable(self) @@ -240,7 +291,27 @@ class GtkUI(GtkPluginBase, CustomNotifications): component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs) - def popuplate_subscriptions(self, handled_events, email_subscriptions=[]): + def popuplate_what_needs_handled_events(self, handled_events, + email_subscriptions=[]): + self.populate_subscriptions(handled_events, email_subscriptions) + self.populate_sounds(handled_events) + + def populate_sounds(self, handled_events): + self.sounds_model.clear() + for event_name, event_doc in handled_events: + if event_name in self.config['custom_sounds']: + snd_path = self.config['custom_sounds'][event_name] + else: + snd_path = self.config['sound_path'] + self.sounds_model.set( + self.sounds_model.append(), + SND_EVENT, event_name, + SND_EVENT_DOC, event_doc, + SND_NAME, basename(snd_path), + SND_PATH, snd_path + ) + + def populate_subscriptions(self, handled_events, email_subscriptions=[]): subscriptions_dict = self.config['subscriptions'] self.subscriptions_model.clear() # self.handled_events = handled_events @@ -273,16 +344,27 @@ class GtkUI(GtkPluginBase, CustomNotifications): if sound: current_sound_subscriptions.append(event) + default_sound_file = self.glade.get_widget("sound_path").get_filename() + log.debug("Default sound file: %s", default_sound_file) + custom_sounds = {} + for event_name, event_doc, filename, filepath in self.sounds_model: + log.debug("Custom sound for event \"%s\": %s", event_name, filename) + if filepath == default_sound_file: + continue + custom_sounds[event_name] = filepath + log.debug(custom_sounds) + self.config.config.update({ "popup_enabled": self.glade.get_widget("popup_enabled").get_active(), "blink_enabled": self.glade.get_widget("blink_enabled").get_active(), "sound_enabled": self.glade.get_widget("sound_enabled").get_active(), - "sound_path": self.glade.get_widget("sound_path").get_filename(), + "sound_path": default_sound_file, "subscriptions": { "popup": current_popup_subscriptions, "blink": current_blink_subscriptions, "sound": current_sound_subscriptions - } + }, + "custom_sounds": custom_sounds }) self.config.save() @@ -298,7 +380,9 @@ class GtkUI(GtkPluginBase, CustomNotifications): dest[0]!='USER@HOST'], "subscriptions": {"email": current_email_subscriptions} } + client.notifications.set_config(core_config) + client.notifications.get_config().addCallback(self.cb_get_config) def on_show_prefs(self): client.notifications.get_config().addCallback(self.cb_get_config) @@ -338,9 +422,14 @@ class GtkUI(GtkPluginBase, CustomNotifications): self.on_sound_enabled_toggled(self.glade.get_widget('sound_enabled')) client.notifications.get_handled_events().addCallback( - self.popuplate_subscriptions, core_config['subscriptions']['email'] + self.popuplate_what_needs_handled_events, + core_config['subscriptions']['email'] ) + def on_sound_path_update_preview(self, filechooser): + client.notifications.get_handled_events().addCallback( + self.populate_sounds + ) def on_add_button_clicked(self, widget, treeview): model = treeview.get_model() @@ -379,6 +468,61 @@ class GtkUI(GtkPluginBase, CustomNotifications): self.glade.get_widget("delete_button").set_property('sensitive', False) + def on_sounds_treeview_selection_changed(self, selection): + model, iter = selection.get_selected() + if iter: + self.glade.get_widget("sounds_edit_button").set_property( + 'sensitive', True) + path = model.get(iter, SND_PATH)[0] + log.debug("Sound selection changed: %s", path) + if path != self.config['sound_path']: + self.glade.get_widget("sounds_revert_button").set_property( + 'sensitive', True) + else: + self.glade.get_widget("sounds_revert_button").set_property( + 'sensitive', False) + else: + self.glade.get_widget("sounds_edit_button").set_property( + 'sensitive', False) + self.glade.get_widget("sounds_revert_button").set_property( + 'sensitive', False) + def on_sounds_revert_button_clicked(self, widget): + log.debug("on_sounds_revert_button_clicked") + selection = self.sounds_treeview.get_selection() + model, iter = selection.get_selected() + if iter: + log.debug("on_sounds_revert_button_clicked: got iter") + model.set(iter, + SND_PATH, self.config['sound_path'], + SND_NAME, basename(self.config['sound_path'])) + + def on_sounds_edit_button_clicked(self, widget): + log.debug("on_sounds_edit_button_clicked") + selection = self.sounds_treeview.get_selection() + model, iter = selection.get_selected() + if iter: + path = model.get(iter, SND_PATH)[0] + dialog = gtk.FileChooserDialog( + title=_("Choose Sound File"), + buttons=(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL, + gtk.STOCK_OPEN, + gtk.RESPONSE_OK) + ) + dialog.set_filename(path) + def update_model(response): + if response == gtk.RESPONSE_OK: + new_filename = dialog.get_filename() + dialog.destroy() + print new_filename + model.set(iter, + SND_PATH, new_filename, + SND_NAME, basename(new_filename)) + d = defer.maybeDeferred(dialog.run) + d.addCallback(update_model) + + log.debug("dialog should have been shown") + def on_enabled_toggled(self, widget): if widget.get_active(): for widget in ('smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', @@ -398,12 +542,16 @@ class GtkUI(GtkPluginBase, CustomNotifications): else: self.glade.get_widget('sound_path').set_property('sensitive', False) + def on_sounds_treeview_clicked(self, widget, event): + if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: + selection = self.sounds_treeview.get_selection() + print "Double clicked on treeview", selection + + def __blink(self): - d = defer.maybeDeferred(self.systray.blink, True) - d.addCallback(self._on_notify_sucess, "blink") - d.addCallback(self._on_notify_failure, "blink") - return d + self.systray.blink(True) + return defer.succeed(_("Notification Blink shown")) def __popup(self, title='', message=''): if not self.config['popup_enabled']: @@ -450,12 +598,12 @@ class GtkUI(GtkPluginBase, CustomNotifications): def _on_torrent_finished_event(self, torrent_id): log.debug("\n\nhandler for TorrentFinishedEvent GTKUI called") # Blink - d0 = defer.maybeDeferred(self.blink) + d0 = defer.maybeDeferred(self.__blink) d0.addCallback(self._on_notify_sucess, 'blink') d0.addErrback(self._on_notify_failure, 'blink') log.debug("Blink notification callback yielded") # Sound - d1 = defer.maybeDeferred(self.play_sound) + d1 = defer.maybeDeferred(self.__play_sound) d1.addCallback(self._on_notify_sucess, 'sound') d1.addErrback(self._on_notify_failure, 'sound') log.debug("Sound notification callback yielded") @@ -473,7 +621,7 @@ class GtkUI(GtkPluginBase, CustomNotifications): title = _("Finished Torrent") message = _("The torrent \"%(name)s\" including %(num_files)i " "has finished downloading.") % torrent_status - d = defer.maybeDeferred(self.popup, title, message) + d = defer.maybeDeferred(self.__popup, title, message) d.addCallback(self._on_notify_sucess, 'popup') d.addErrback(self._on_notify_failure, 'popup') return d @@ -498,18 +646,21 @@ class GtkUI(GtkPluginBase, CustomNotifications): not self.subscriptions_model[path][SUB_NOT_SOUND] return - def handle_custom_popup_notification(self, result): + def handle_custom_popup_notification(self, result, eventtype): title, message = result return defer.maybeDeferred(self.__popup, title, message) - def handle_custom_blink_notification(self, result): + def handle_custom_blink_notification(self, result, eventtype): if result: return defer.maybeDeferred(self.__blink) return defer.succeed("Won't blink. The returned value from the custom " "handler was: %s", result) - def handle_custom_sound_notification(self, result): + def handle_custom_sound_notification(self, result, eventtype): if isinstance(result, basestring): + if not result and eventtype in self.config['custom_sounds']: + return defer.maybeDeferred( + self.__play_sound, self.config['custom_sounds'][eventtype]) return defer.maybeDeferred(self.__play_sound, result) return defer.succeed("Won't play sound. The returned value from the " "custom handler was: %s", result) diff --git a/deluge/plugins/notifications/notifications/test.py b/deluge/plugins/notifications/notifications/test.py index 6931c243a..a5722914e 100644 --- a/deluge/plugins/notifications/notifications/test.py +++ b/deluge/plugins/notifications/notifications/test.py @@ -50,20 +50,18 @@ class TestEmailNotifications(component.Component): self.custom_sound_message_provider ) - self.lc.start(15, False) + self.lc.start(60, False) def disable(self): log.debug("\n\nDisabling %s", self.__class__.__name__) self.lc.stop() def update(self): - log.debug("\n\nUpdating %s", self.__class__.__name__) - self.events.append(self.events.pop(0)) # Re-Queue - self.n += 1 - try: + if self.__imp == 'core': + log.debug("\n\nUpdating %s", self.__class__.__name__) + self.events.append(self.events.pop(0)) # Re-Queue + self.n += 1 component.get("EventManager").emit(self.events[0]) - except KeyError: - pass def custom_email_message_provider(self, *evt_args, **evt_kwargs): log.debug("Running custom email message provider: %s %s", evt_args, evt_kwargs) @@ -83,4 +81,5 @@ class TestEmailNotifications(component.Component): def custom_sound_message_provider(self, *evt_args, **evt_kwargs): log.debug("Running custom sound message provider: %s %s", evt_args, evt_kwargs) + return '' return '/usr/share/kde4/apps/korganizer/sounds/alert.wav'