From 1b7a50f88b6a348d43ec38662407d24b9d12cbc1 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sun, 22 Nov 2009 02:35:02 +0000 Subject: [PATCH] Checkpoint: * Custom notification providers working for email. * Configuration through the GtkUI working. --- deluge/event.py | 10 +- .../notifications/notifications/common.py | 39 +- .../notifications/notifications/core.py | 184 ++-- .../notifications/data/config.glade | 823 +++++++++++------- .../notifications/notifications/gtkui.py | 351 ++++++-- .../notifications/notifications/webui.py | 36 +- 6 files changed, 939 insertions(+), 504 deletions(-) diff --git a/deluge/event.py b/deluge/event.py index 67618e9df..9a9017762 100644 --- a/deluge/event.py +++ b/deluge/event.py @@ -17,9 +17,9 @@ # # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. # # In addition, as a special exception, the copyright holders give # permission to link the code of portions of this program with the OpenSSL @@ -41,15 +41,15 @@ and subsequently emitted to the clients. """ -event_list = [] +known_events = {} class DelugeEventMetaClass(type): """ This metaclass simply keeps a list of all events classes created. """ def __init__(cls, name, bases, dct): - event_list.append(name) super(DelugeEventMetaClass, cls).__init__(name, bases, dct) + known_events[name] = cls class DelugeEvent(object): """ diff --git a/deluge/plugins/notifications/notifications/common.py b/deluge/plugins/notifications/notifications/common.py index 39695fab0..0d35caa63 100644 --- a/deluge/plugins/notifications/notifications/common.py +++ b/deluge/plugins/notifications/notifications/common.py @@ -22,9 +22,9 @@ # # You should have received a copy of the GNU General Public License # along with deluge. If not, write to: -# The Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor -# Boston, MA 02110-1301, USA. +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. # # In addition, as a special exception, the copyright holders give # permission to link the code of portions of this program with the OpenSSL @@ -39,4 +39,35 @@ def get_resource(filename): import pkg_resources, os - return pkg_resources.resource_filename("notifications", os.path.join("data", filename)) + return pkg_resources.resource_filename("notifications", + os.path.join("data", filename)) + +DEFAULT_PREFS = { +# Core ------------------------------------------------------------------------- + "smtp_enabled": False, + "smtp_host": "", + "smtp_port": 25, + "smtp_user": "", + "smtp_pass": "", + "smtp_from": "", + "smtp_tls": False, # SSL or TLS + "smtp_recipients": [], +# GTK UI ----------------------------------------------------------------------- + # BLINK + "blink_enabled": False, + # FLASH + "flash_enabled": False, + # POPUP + "popup_enabled": False, + # SOUND + "sound_enabled": False, + "sound_path": "", +# Subscriptions ---------------------------------------------------------------- + "subscriptions": { + "popup": [], + "blink": [], + "sound": [], + "email": [], + } + +} diff --git a/deluge/plugins/notifications/notifications/core.py b/deluge/plugins/notifications/notifications/core.py index 9a1e09359..da81c328d 100644 --- a/deluge/plugins/notifications/notifications/core.py +++ b/deluge/plugins/notifications/notifications/core.py @@ -39,20 +39,16 @@ import smtplib from twisted.internet import defer, threads +from deluge.event import known_events, DelugeEvent from deluge.log import LOG as log from deluge.plugins.pluginbase import CorePluginBase import deluge.component as component import deluge.configmanager from deluge.core.rpcserver import export -# Relative imports -from manager import Notifications -import events +from test import TestEmailNotifications DEFAULT_PREFS = { - # BLINK - "blink_enabled": False, - # EMAIL "smtp_enabled": False, "smtp_host": "", "smtp_port": 25, @@ -61,29 +57,36 @@ DEFAULT_PREFS = { "smtp_from": "", "smtp_tls": False, # SSL or TLS "smtp_recipients": [], - # FLASH - "flash_enabled": False, - # POPUP - "popup_enabled": False, - # SOUND - "sound_enabled": False, - "sound_path": "" + # Subscriptions + "subscriptions": { + "email": [] + } } +class Core(CorePluginBase, component.Component): + def __init__(self, plugin_name): + CorePluginBase.__init__(self, plugin_name) + component.Component.__init__(self, "Notifications") + self.email_message_providers = {} + self.tn = TestEmailNotifications() -class Core(CorePluginBase, Notifications): def enable(self): - Notifications.enable(self) - self.config = deluge.configmanager.ConfigManager("notifications.conf", - DEFAULT_PREFS) + self.config = deluge.configmanager.ConfigManager( + "notifications-core.conf", DEFAULT_PREFS) component.get("EventManager").register_event_handler( "TorrentFinishedEvent", self._on_torrent_finished_event ) log.debug("\n\nENABLING CORE NOTIFICATIONS\n\n") + self.tn.enable() +# import sys +# print '\n\n', [(n, k.__module__) for n, k in known_events.items()] +# print [f for f in sys.modules.keys() if f.startswith("deluge.event")] def disable(self): - Notifications.disable(self) + self.tn.disable() log.debug("\n\nDISABLING CORE NOTIFICATIONS\n\n") + for eventtype in self.email_message_providers.keys(): + self.deregister_email_message_provider(eventtype) def update(self): pass @@ -100,65 +103,87 @@ class Core(CorePluginBase, Notifications): "returns the config dictionary" return self.config.config - # Notification methods @export - def notify_blink(self): - if not self.config["blink_enabled"]: - return defer.succeed("Blink notification not enabled") - return defer.maybeDeferred( - component.get("EventManager").emit, events.NotificationBlinkEvent()) + def get_handled_events(self): + handled_events = [] + for evt in sorted(known_events.keys()): + if known_events[evt].__module__.startswith('deluge.event'): + if evt not in ('TorrentFinishedEvent',): + # Skip the base class for all events + continue + classdoc = known_events[evt].__doc__.strip() + handled_events.append((evt, classdoc)) + log.debug("Handled Notification Events: %s", handled_events) + return handled_events - @export - def notify_email(self, title='', message='', smtp_from='', recipients=[]): + def register_email_message_provider(self, eventtype, handler): + """This is used to register email formatters for custom event types. + + :param event: str, the event name + :param handler: function, to be called when `:param:event` is emitted + + You're handler should return a tuple of (subject, email_contents). + """ + if eventtype not in known_events: + raise Exception("The event \"%s\" is not known" % eventtype) + if known_events[eventtype].__module__.startswith('deluge.event'): + raise Exception("You cannot register email message providers for " + "built-in event types.") + if eventtype not in self.email_message_providers: + def wrapper(*args, **kwargs): + return self._handle_custom_email_message_providers(eventtype, + *args, + **kwargs) + self.email_message_providers[eventtype] = (wrapper, handler) + else: + wrapper, handler = self.email_message_providers[eventtype] + component.get("EventManager").register_event_handler( + eventtype, wrapper + ) + + def deregister_email_message_provider(self, eventtype): + wrapper, handler = self.email_message_providers[eventtype] + component.get("EventManager").deregister_event_handler( + eventtype, wrapper + ) + self.email_message_providers.pop(eventtype) + + def _handle_custom_email_message_providers(self, event, *args, **kwargs): if not self.config['smtp_enabled']: - return defer.succeed("SMTP notification not enabled") - d = threads.deferToThread(self._notify_email, title, message, smtp_from, - recipients) - d.addCallback(self._on_notify_sucess, 'email') - d.addErrback(self._on_notify_failure, 'email') - return d + return defer.succeed("SMTP notification not enabled.") - @export - def notify_flash(self, title='', message=''): - if not self.config["flash_enabled"]: - return defer.succeed("Flash notification not enabled") - d = defer.maybeDeferred(component.get("EventManager").emit, - events.NotificationFlashEvent(title, message)) - d.addCallback(self._on_notify_event_sucess, 'flash') - d.addErrback(self._on_notify_event_failure, 'flash') - return d + log.debug("\n\nCalling CORE's custom email providers for %s: %s %s", + event, args, kwargs) + if event in self.config["subscriptions"]["email"]: + wrapper, handler = self.email_message_providers[event] + log.debug("Found handler: %s", handler) + d = defer.maybeDeferred(handler, *args, **kwargs) + d.addCallback(self._prepare_email) + d.addCallback(self._on_notify_sucess) + d.addErrback(self._on_notify_failure) + return d - @export - def notify_popup(self, title='', message=''): - if not self.config["popup_enabled"]: - return defer.succeed("Popup notification not enabled") - d = defer.maybeDeferred(component.get("EventManager").emit, - events.NotificationPopupEvent(title, message)) - d.addCallback(self._on_notify_event_sucess, 'popup') - d.addErrback(self._on_notify_event_failure, 'popup') - return d - @export - def notify_sound(self, sound_path=''): - if not self.config["sound_enabled"]: - return defer.succeed("Sound notification not enabled") - d = defer.maybeDeferred(component.get("EventManager").emit, - events.NotificationSoundEvent(sound_path)) - d.addCallback(self._on_notify_event_sucess, 'sound') - d.addErrback(self._on_notify_event_failure, 'sound') - return d + def _prepare_email(self, result): + if not self.config['smtp_enabled']: + return defer.succeed("SMTP notification not enabled.") + subject, message = result + log.debug("\n\nSending email with subject: %s: %s", subject, message) + return threads.deferToThread(self._notify_email, subject, message) - def _notify_email(self, title='', message='', smtp_from='', recipients=[]): + + def _notify_email(self, subject='', message=''): + log.debug("Email prepared") config = self.config - to_addrs = '; '.join(config['smtp_recipients']+recipients) + to_addrs = '; '.join(config['smtp_recipients']) headers = """\ From: %(smtp_from)s To: %(smtp_recipients)s -Subject: %(title)s +Subject: %(subject)s -""" % {'smtp_from': smtp_from and smtp_from or config['smtp_from'], - 'title': title, +""" % {'smtp_from': config['smtp_from'], + 'subject': subject, 'smtp_recipients': to_addrs} message = '\r\n'.join((headers + message).splitlines()) @@ -169,7 +194,7 @@ Subject: %(title)s err_msg = _("There was an error sending the notification email:" " %s") % err log.error(err_msg) - raise err + return err security_enabled = config['smtp_tls'] @@ -188,12 +213,12 @@ Subject: %(title)s err_msg = _("The server didn't reply properly to the helo " "greeting: %s") % err log.error(err_msg) - raise err + return err except smtplib.SMTPAuthenticationError, err: err_msg = _("The server didn't accept the username/password " "combination: %s") % err log.error(err_msg) - raise err + return err try: try: @@ -202,7 +227,7 @@ Subject: %(title)s err_msg = _("There was an error sending the notification email:" " %s") % err log.error(err_msg) - raise err + return err finally: if security_enabled: # avoid false failure detection when the server closes @@ -216,12 +241,13 @@ Subject: %(title)s server.quit() return _("Notification email sent.") + def _on_torrent_finished_event(self, torrent_id): - log.debug("\n\nhandler for TorrentFinishedEvent called for CORE") + log.debug("\n\nHandler for TorrentFinishedEvent called for CORE") torrent = component.get("TorrentManager")[torrent_id] torrent_status = torrent.get_status({}) # Email - title = _("Finished Torrent \"%(name)s\"") % torrent_status + subject = _("Finished Torrent \"%(name)s\"") % torrent_status message = _( "This email is to inform you that Deluge has finished " "downloading \"%(name)s\", which includes %(num_files)i files." @@ -230,7 +256,17 @@ Subject: %(title)s "Thank you,\nDeluge." ) % torrent_status - d = defer.maybeDeferred(self.notify_email, title, message) - d.addCallback(self._on_notify_sucess, 'email') - d.addErrback(self._on_notify_failure, 'email') - log.debug("Email notification callback yielded") + d = defer.maybeDeferred(self._prepare_email, [subject, message]) + d.addCallback(self._on_notify_sucess) + d.addErrback(self._on_notify_failure) + 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 1bdd923d3..842b93184 100644 --- a/deluge/plugins/notifications/notifications/data/config.glade +++ b/deluge/plugins/notifications/notifications/data/config.glade @@ -8,390 +8,537 @@ True vertical - + True - 0 - none + queue - + True - 12 + vertical - + True vertical - - Blink tray icon + True - True - False - True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 0 + 10 + 10 + <b><i><big>Notifications</big></i></b> + True + False 0 - - Show popup + True - True - False - True + False 1 + + + 0 + + + + + True + 0 + 2 + 2 + 2 - + True + True - - Play sound + True - True - False - True - + vertical + + + True + 0 + none + + + True + 12 + + + True + vertical + + + Tray icon blinks enabled + True + True + False + True + + + 0 + + + + + Popups enabled + True + True + False + True + + + 1 + + + + + True + + + Sound enabled + True + True + False + True + + + + False + 0 + + + + + True + False + + + 2 + 1 + + + + + 2 + + + + + + + + + True + 5 + <b>UI Notifications</b> + True + + + label_item + + + + + 0 + + + + + True + 0 + none + + + True + 12 + + + True + 7 + 4 + 2 + 2 + + + True + Hostname: + right + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + Port: + right + + + 2 + 3 + 1 + 2 + + + + + True + True + 65535 + + 5 + 25 1 100 1 10 0 + 1 + True + True + + + 3 + 4 + 1 + 2 + + + + + True + Username: + right + + + 2 + 3 + + + + + True + True + + + + 1 + 4 + 2 + 3 + + + + + True + Password: + + + 3 + 4 + + + + + True + True + False + + + + 1 + 4 + 3 + 4 + + + + + True + 0 + none + + + True + 12 + + + True + 2 + + + True + True + automatic + automatic + + + True + True + False + horizontal + + + + + 0 + + + + + True + vertical + 5 + start + + + gtk-add + True + True + True + True + + + + False + False + 0 + + + + + gtk-delete + True + False + True + True + True + + + + False + False + 1 + + + + + False + 3 + 1 + + + + + + + + + True + <b>Recipients</b> + True + + + label_item + + + + + 4 + 6 + 7 + + + + + True + True + False + True + + + True + Server requires TLS/SSL + + + + + 1 + 4 + 5 + 6 + 10 + + + + + True + From: + right + + + 4 + 5 + + + + + True + True + + + + 1 + 4 + 4 + 5 + + + + + Enabled + True + True + False + True + + + + 4 + + + + + + + + + + + + True + 5 + <b>Email Notifications</b> + True + + + label_item + + + + + 1 + + + + + + + True + Settings - False - 0 + False + tab - + True - False + vertical + + + + + + True + True + automatic + automatic + + + True + True + horizontal + + + + + 1 + + + + + True + This configuration does not mean that you'll actually receive notifications for all these events. + fill + True + + + False + 2 + 2 + + - 2 1 + + + True + Subscriptions + + + 1 + False + tab + + + + + + + + + tab + + - - 2 - + + 1 + - - - True - <b>UI Notifications</b> - True - - - label_item - - 0 - - - True - 0 - none - - - True - 12 - - - True - 7 - 4 - 2 - 2 - - - True - Hostname: - right - - - 1 - 2 - - - - - True - True - - - - 1 - 2 - 1 - 2 - - - - - True - Port: - right - - - 2 - 3 - 1 - 2 - - - - - True - True - 65535 - - 5 - 25 1 100 1 10 10 - 1 - True - True - - - 3 - 4 - 1 - 2 - - - - - True - Username: - right - - - 2 - 3 - - - - - True - True - - - - 1 - 4 - 2 - 3 - - - - - True - Password: - - - 3 - 4 - - - - - True - True - False - - - - 1 - 4 - 3 - 4 - - - - - True - 0 - none - - - True - 12 - - - True - 2 - - - True - True - automatic - automatic - - - True - True - False - horizontal - - - - - 0 - - - - - True - vertical - 5 - start - - - gtk-add - True - True - True - True - - - - False - False - 0 - - - - - gtk-delete - True - False - True - True - True - - - - False - False - 1 - - - - - False - 3 - 1 - - - - - - - - - True - <b>Recipients</b> - True - - - label_item - - - - - 4 - 6 - 7 - - - - - True - True - False - True - - - True - Server requires TLS/SSL - - - - - 1 - 4 - 5 - 6 - 10 - - - - - True - From: - right - - - 4 - 5 - - - - - True - True - - - - 1 - 4 - 4 - 5 - - - - - Enabled - True - True - False - True - - - - 4 - - - - - - - - - - - - True - <b>Email Notifications</b> - True - - - label_item - - - - - 1 - - diff --git a/deluge/plugins/notifications/notifications/gtkui.py b/deluge/plugins/notifications/notifications/gtkui.py index 8fdbdde0f..35c276f80 100644 --- a/deluge/plugins/notifications/notifications/gtkui.py +++ b/deluge/plugins/notifications/notifications/gtkui.py @@ -40,11 +40,13 @@ import gtk from twisted.internet import defer +from deluge.event import known_events, DelugeEvent from deluge.log import LOG as log from deluge.ui.client import client from deluge.plugins.pluginbase import GtkPluginBase import deluge.component as component import deluge.common +import deluge.configmanager try: import pygame @@ -60,44 +62,148 @@ except ImportError: # Relative imports from common import get_resource -from manager import Notifications + +DEFAULT_PREFS = { + # BLINK + "blink_enabled": False, + # FLASH + "flash_enabled": False, + # POPUP + "popup_enabled": False, + # SOUND + "sound_enabled": False, + "sound_path": "", + # 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) + + +class GtkUI(GtkPluginBase, component.Component): + def __init__(self, plugin_name): + GtkPluginBase.__init__(self, plugin_name) + component.Component.__init__(self, "Notifications") -class GtkUI(GtkPluginBase, Notifications): def enable(self): - Notifications.enable(self) + self.config = deluge.configmanager.ConfigManager( + "notifications-gtk.conf", DEFAULT_PREFS + ) self.glade = gtk.glade.XML(get_resource("config.glade")) self.glade.get_widget("smtp_port").set_value(25) self.prefs = self.glade.get_widget("prefs_box") self.prefs.show_all() - self.treeview = self.glade.get_widget("smtp_recipients") - treeview_selection = self.treeview.get_selection() - treeview_selection.connect("changed", self.on_treeview_selection_changed) - self.model = gtk.ListStore(str, bool) + + # SMTP Recipients treeview/model + self.recipients_treeview = self.glade.get_widget("smtp_recipients") + treeview_selection = self.recipients_treeview.get_selection() + treeview_selection.connect( + "changed", self.on_recipients_treeview_selection_changed + ) + self.recipients_model = gtk.ListStore(str, bool) renderer = gtk.CellRendererText() - renderer.connect("edited", self.on_cell_edited, self.model) + renderer.connect("edited", self.on_cell_edited, self.recipients_model) renderer.set_data("recipient", RECIPIENT_FIELD) column = gtk.TreeViewColumn("Recipients", renderer, text=RECIPIENT_FIELD, editable=RECIPIENT_EDIT) column.set_expand(True) - self.treeview.append_column(column) - self.treeview.set_model(self.model) + self.recipients_treeview.append_column(column) + self.recipients_treeview.set_model(self.recipients_model) - deluge.common.get_default_download_dir() + # Notification Subscriptions treeview/model + self.subscriptions_treeview = self.glade.get_widget("subscriptions_treeview") + subscriptions_selection = self.subscriptions_treeview.get_selection() + subscriptions_selection.connect( + "changed", self.on_subscriptions_treeview_selection_changed + ) + self.subscriptions_treeview.set_tooltip_column(SUB_EVENT_DOC) + self.subscriptions_model = gtk.ListStore(str, str, bool, bool, bool, bool) + + renderer = gtk.CellRendererText() + renderer.set_data("event", SUB_EVENT) + column = gtk.TreeViewColumn("Event", renderer, text=SUB_EVENT) + column.set_expand(True) + self.subscriptions_treeview.append_column(column) + + renderer = gtk.CellRendererText() + renderer.set_data("event_doc", SUB_EVENT) + column = gtk.TreeViewColumn("Doc", renderer, text=SUB_EVENT_DOC) + column.set_property('visible', False) + self.subscriptions_treeview.append_column(column) + + + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self._on_email_col_toggled) + column = gtk.TreeViewColumn("Email", renderer, active=SUB_NOT_EMAIL) + column.set_clickable(True) +# column.add_attribute(renderer, "active", False) +# column.set_expand(True) + self.subscriptions_treeview.append_column(column) + + renderer = gtk.CellRendererToggle() +# renderer.connect("edited", self.on_cell_edited, self.recipients_model) +# renderer.set_data("popup", SUB_NOT_POPUP) + renderer.set_property('activatable', True) + renderer.connect( 'toggled', self._on_popup_col_toggled) + column = gtk.TreeViewColumn("Popup", renderer, active=SUB_NOT_POPUP) + column.set_clickable(True) +# column.add_attribute(renderer, "active", False) +# column.set_expand(True) + self.subscriptions_treeview.append_column(column) + + renderer = gtk.CellRendererToggle() +# renderer.connect("edited", self.on_cell_edited, self.recipients_model) +# renderer.set_data("blink", SUB_NOT_BLINK) + renderer.set_property('activatable', True) + renderer.connect( 'toggled', self._on_blink_col_toggled) + column = gtk.TreeViewColumn("Blink", renderer, active=SUB_NOT_BLINK) + column.set_clickable(True) +# column.add_attribute(renderer, "active", False) +# column.set_expand(True) + self.subscriptions_treeview.append_column(column) + + renderer = gtk.CellRendererToggle() +# renderer.connect("edited", self.on_cell_edited, self.recipients_model) + renderer.set_property('activatable', True) + renderer.connect('toggled', self._on_sound_col_toggled) +# renderer.set_data("sound", SUB_NOT_SOUND) + column = gtk.TreeViewColumn("Sound", renderer, active=SUB_NOT_SOUND) + column.set_clickable(True) +# column.add_attribute(renderer, "active", False) +# column.set_expand(True) + 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.treeview), + self.recipients_treeview), 'on_delete_button_clicked': (self.on_delete_button_clicked, - self.treeview), + 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) +# 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", @@ -106,38 +212,93 @@ class GtkUI(GtkPluginBase, Notifications): if not POPUP_AVAILABLE: self.glade.get_widget("popup_enabled").set_property('sensitive', False) - else: - client.register_event_handler("NotificationPopupEvent", - self.notify_popup) - - client.register_event_handler("NotificationBlinkEvent", - self.notify_blink) - - self.tray = component.get("SystemTray") if not SOUND_AVAILABLE: self.glade.get_widget("sound_enabled").set_property('sensitive', False) self.glade.get_widget('sound_path').set_property('sensitive', False) - else: - client.register_event_handler("NotificationSoundEvent", - self.notify_sound) + + 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) - # Force config populate - client.notifications.get_config().addCallback(self.cb_get_config) def disable(self): - Notifications.disable(self) component.get("Preferences").remove_page("Notifications") component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs) component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs) + def popuplate_subscriptions(self, handled_events, email_subscriptions=[]): + subscriptions_dict = self.config['subscriptions'] + self.subscriptions_model.clear() +# self.handled_events = handled_events + for event_name, event_doc in handled_events: + self.subscriptions_model.set( + self.subscriptions_model.append(), + SUB_EVENT, event_name, + SUB_EVENT_DOC, event_doc, + SUB_NOT_EMAIL, event_name in email_subscriptions, + SUB_NOT_POPUP, event_name in subscriptions_dict["popup"], + SUB_NOT_BLINK, event_name in subscriptions_dict['blink'], + SUB_NOT_SOUND, event_name in subscriptions_dict['sound'] + ) + + def on_apply_prefs(self): log.debug("applying prefs for Notifications") - config = { + + current_popup_subscriptions = [] + current_blink_subscriptions = [] + current_sound_subscriptions = [] + current_email_subscriptions = [] + for event, doc, email, popup, blink, sound in self.subscriptions_model: + if email: + current_email_subscriptions.append(event) + if popup: + current_popup_subscriptions.append(event) + if blink: + current_blink_subscriptions.append(event) + if sound: + current_sound_subscriptions.append(event) +# saved_popup_subscriptions = self.config['subscriptions']['popup'] +# saved_blink_subscriptions = self.config['subscriptions']['blink'] +# saved_sound_subscriptions = self.config['subscriptions']['sound'] +# for event in current_popup_subscriptions: +# saved_popup_subscriptions.remove(event) +# for event in saved_blink_subscriptions: +# # De register +# pass +# for event in current_blink_subscriptions: +# saved_blink_subscriptions.remove(event) +# for event in saved_blink_subscriptions: +# # De register +# pass +# for event in current_sound_subscriptions: +# saved_sound_subscriptions.remove(event) +# for event in saved_sound_subscriptions: +# # De register +# pass + + 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(), + "subscriptions": { + "popup": current_popup_subscriptions, + "blink": current_blink_subscriptions, + "sound": current_sound_subscriptions + } + }) + self.config.save() + + core_config = { "smtp_enabled": self.glade.get_widget("smtp_enabled").get_active(), "smtp_host": self.glade.get_widget("smtp_host").get_text(), "smtp_port": self.glade.get_widget("smtp_port").get_value(), @@ -145,45 +306,42 @@ class GtkUI(GtkPluginBase, Notifications): "smtp_pass": self.glade.get_widget("smtp_pass").get_text(), "smtp_from": self.glade.get_widget("smtp_from").get_text(), "smtp_tls": self.glade.get_widget("smtp_tls").get_active(), - "smtp_recipients": [dest[0] for dest in self.model if + "smtp_recipients": [dest[0] for dest in self.recipients_model if dest[0]!='USER@HOST'], - "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(), - "popup_enabled": self.glade.get_widget("popup_enabled").get_active() + "subscriptions": {"email": current_email_subscriptions} } - - client.notifications.set_config(config) + client.notifications.set_config(core_config) def on_show_prefs(self): client.notifications.get_config().addCallback(self.cb_get_config) - def cb_get_config(self, config): + def cb_get_config(self, core_config): "callback for on show_prefs" - self.config = config - self.glade.get_widget("smtp_host").set_text(config["smtp_host"]) - self.glade.get_widget("smtp_port").set_value(config["smtp_port"]) - self.glade.get_widget("smtp_user").set_text(config["smtp_user"]) - self.glade.get_widget("smtp_pass").set_text(config["smtp_pass"]) - self.glade.get_widget("smtp_from").set_text(config["smtp_from"]) - self.glade.get_widget("smtp_tls").set_active(config["smtp_tls"]) - self.model.clear() - for recipient in config['smtp_recipients']: - self.model.set(self.model.append(), - RECIPIENT_FIELD, recipient, - RECIPIENT_EDIT, False) - self.glade.get_widget("smtp_enabled").set_active(config['smtp_enabled']) + self.glade.get_widget("smtp_host").set_text(core_config["smtp_host"]) + self.glade.get_widget("smtp_port").set_value(core_config["smtp_port"]) + self.glade.get_widget("smtp_user").set_text(core_config["smtp_user"]) + self.glade.get_widget("smtp_pass").set_text(core_config["smtp_pass"]) + self.glade.get_widget("smtp_from").set_text(core_config["smtp_from"]) + self.glade.get_widget("smtp_tls").set_active(core_config["smtp_tls"]) + self.recipients_model.clear() + for recipient in core_config['smtp_recipients']: + self.recipients_model.set(self.recipients_model.append(), + RECIPIENT_FIELD, recipient, + RECIPIENT_EDIT, False) + self.glade.get_widget("smtp_enabled").set_active( + core_config['smtp_enabled'] + ) self.glade.get_widget("sound_enabled").set_active( - config['sound_enabled'] + self.config['sound_enabled'] ) self.glade.get_widget("popup_enabled").set_active( - config['popup_enabled'] + self.config['popup_enabled'] ) self.glade.get_widget("blink_enabled").set_active( - config['blink_enabled'] + self.config['blink_enabled'] ) - if config['sound_path']: - sound_path = config['sound_path'] + if self.config['sound_path']: + sound_path = self.config['sound_path'] else: sound_path = deluge.common.get_default_download_dir() self.glade.get_widget("sound_path").set_filename(sound_path) @@ -191,6 +349,11 @@ class GtkUI(GtkPluginBase, Notifications): self.on_enabled_toggled(self.glade.get_widget("smtp_enabled")) self.on_sound_enabled_toggled(self.glade.get_widget('sound_enabled')) + client.notifications.get_handled_events().addCallback( + self.popuplate_subscriptions, core_config['subscriptions']['email'] + ) + + def on_add_button_clicked(self, widget, treeview): model = treeview.get_model() model.set(model.append(), @@ -210,7 +373,7 @@ class GtkUI(GtkPluginBase, Notifications): path = model.get_path(iter)[0] model.set(iter, RECIPIENT_FIELD, new_text) - def on_treeview_selection_changed(self, selection): + def on_recipients_treeview_selection_changed(self, selection): model, selected_connection_iter = selection.get_selected() if selected_connection_iter: self.glade.get_widget("delete_button").set_property('sensitive', @@ -218,6 +381,16 @@ class GtkUI(GtkPluginBase, Notifications): else: self.glade.get_widget("delete_button").set_property('sensitive', False) + + def on_subscriptions_treeview_selection_changed(self, selection): + model, selected_connection_iter = selection.get_selected() + if selected_connection_iter: + self.glade.get_widget("delete_button").set_property('sensitive', + True) + else: + self.glade.get_widget("delete_button").set_property('sensitive', + False) + def on_enabled_toggled(self, widget): if widget.get_active(): for widget in ('smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', @@ -237,24 +410,14 @@ class GtkUI(GtkPluginBase, Notifications): else: self.glade.get_widget('sound_path').set_property('sensitive', False) - def notify_blink(self): - defer.maybeDeferred(self.tray.blink, True) - return defer.succeed("blink notification shown.") - - def notify_email(self, title='', message='', smtp_from='', recipients=[]): - d = client.notifications.notify_email(title, message, smtp_from, - recipients) - d.addCallback(self._on_notify_sucess, "email") - d.addCallback(self._on_notify_failure, "email") + # Notification methods + 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 - def notify_flash(self, title='', message=''): - d = client.notifications.notify_flash(title, message) - d.addCallback(self._on_notify_sucess, "flash") - d.addCallback(self._on_notify_failure, "flash") - return d - - def notify_popup(self, title='', message=''): + def popup(self, title='', message=''): if not self.config['popup_enabled']: return defer.succeed(_("Popup notification is not enabled.")) if not POPUP_AVAILABLE: @@ -271,7 +434,7 @@ class GtkUI(GtkPluginBase, Notifications): return defer.fail(err_msg) return defer.succeed(_("Notification popup shown")) - def notify_sound(self, sound_path=''): + def play_sound(self, sound_path=''): if not self.config['sound_enabled']: return defer.succeed(_("Sound notification not enabled")) if not SOUND_AVAILABLE: @@ -295,15 +458,16 @@ class GtkUI(GtkPluginBase, Notifications): log.info(msg) return defer.succeed(msg) + # Internal methods def _on_torrent_finished_event(self, torrent_id): log.debug("\n\nhandler for TorrentFinishedEvent GTKUI called") # Blink - d0 = defer.maybeDeferred(self.notify_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.notify_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") @@ -311,6 +475,7 @@ class GtkUI(GtkPluginBase, Notifications): d2 = client.core.get_torrent_status(torrent_id, ["name", "num_files"]) d2.addCallback(self._on_torrent_finished_event_got_torrent_status) d2.addErrback(self._on_torrent_finished_event_torrent_status_failure) + return defer.succeed("\n\nGtkUI on torrent finished") def _on_torrent_finished_event_torrent_status_failure(self, failure): log.debug("Failed to get torrent status to be able to show the popup") @@ -320,8 +485,36 @@ class GtkUI(GtkPluginBase, Notifications): title = _("Finished Torrent") message = _("The torrent \"%(name)s\" including %(num_files)i " "has finished downloading.") % torrent_status - d2 = defer.maybeDeferred(self.notify_popup, title, message) - d2.addCallback(self._on_notify_sucess, 'popup') - d2.addErrback(self._on_notify_failure, 'popup') - log.debug("Popup notification callback yielded") + d = defer.maybeDeferred(self.popup, title, message) + d.addCallback(self._on_notify_sucess, 'popup') + d.addErrback(self._on_notify_failure, 'popup') + return d + + def _on_notify_sucess(self, result, kind): + log.debug("\n\nNotification success using %s: %s", kind, result) + return result + + def _on_notify_failure(self, failure, kind): + log.debug("\n\nNotification failure using %s: %s", kind, failure) + return failure + + def _on_email_col_toggled(self, cell, path): + self.subscriptions_model[path][SUB_NOT_EMAIL] = \ + not self.subscriptions_model[path][SUB_NOT_EMAIL] + return + + def _on_popup_col_toggled(self, cell, path): + self.subscriptions_model[path][SUB_NOT_POPUP] = \ + not self.subscriptions_model[path][SUB_NOT_POPUP] + return + + def _on_blink_col_toggled(self, cell, path): + self.subscriptions_model[path][SUB_NOT_BLINK] = \ + not self.subscriptions_model[path][SUB_NOT_BLINK] + return + + def _on_sound_col_toggled(self, cell, path): + self.subscriptions_model[path][SUB_NOT_SOUND] = \ + not self.subscriptions_model[path][SUB_NOT_SOUND] + return diff --git a/deluge/plugins/notifications/notifications/webui.py b/deluge/plugins/notifications/notifications/webui.py index 3464ece8f..0a3e07a5b 100644 --- a/deluge/plugins/notifications/notifications/webui.py +++ b/deluge/plugins/notifications/notifications/webui.py @@ -37,21 +37,49 @@ # statement from all source files in the program, then also delete it here. # +from twisted.internet import defer from deluge.log import LOG as log from deluge.ui.client import client from deluge import component from deluge.plugins.pluginbase import WebPluginBase +import deluge.configmanager # Relative imports from common import get_resource -from manager import Notifications -class WebUI(WebPluginBase, Notifications): +DEFAULT_PREFS = { + # FLASH + "flash_enabled": False, + # Subscriptions + "subscriptions": { + "flash": [] + } +} + +class WebUI(WebPluginBase, component.Component): scripts = [get_resource("notifications.js")] + def __init__(self, plugin_name): + WebPluginBase.__init__(self, plugin_name) + component.Component.__init__(self, "Notifications") + def enable(self): - Notifications.enable(self) + self.config = deluge.configmanager.ConfigManager( + "notifications-web.conf", DEFAULT_PREFS + ) + log.debug("Enabling Web UI notifications") def disable(self): - Notifications.disable(self) + log.debug("Disabling Web UI notifications") + + def flash(self, title, message): + return defer.succeed("Web Flash Notifications not implemented yet") + + def _on_notify_sucess(self, result, kind): + log.debug("\n\nNotification success using %s: %s", kind, result) + return result + + def _on_notify_failure(self, failure, kind): + log.debug("\n\nNotification failure using %s: %s", kind, failure) + return failure