diff --git a/deluge/plugins/notifications/notifications/common.py b/deluge/plugins/notifications/notifications/common.py index 0d35caa63..83480422c 100644 --- a/deluge/plugins/notifications/notifications/common.py +++ b/deluge/plugins/notifications/notifications/common.py @@ -36,38 +36,190 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. # +from twisted.internet import defer +from deluge import component +from deluge.event import known_events +from deluge.log import LOG as log def get_resource(filename): import pkg_resources, os 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": [], - } -} +class CustomNotifications(component.Component): + def __init__(self, impl): + component.Component.__init__(self, "Notifications") + self.__impl = impl + self.custom_notifications = { + "email": {}, + "popup": {}, + "blink": {}, + "sound": {} + } + + def enable(self): + pass + + def disable(self): + for kind in self.custom_notifications.iterkeys(): + for eventtype in self.custom_notifications[kind].copy().iterkeys(): + wrapper, handler = self.custom_notifications[kind][eventtype] + self.__deregister_custom_provider(kind, eventtype) + + + def handle_custom_email_notification(self, result): + raise NotImplementedError("%s does not implement this function" % + self.__class__.__name__) + + def handle_custom_popup_notification(self, result): + raise NotImplementedError("%s does not implement this function" % + self.__class__.__name__) + + def handle_custom_blink_notification(self, result): + raise NotImplementedError("%s does not implement this function" % + self.__class__.__name__) + + def handle_custom_sound_notification(self, result): + raise NotImplementedError("%s does not implement this function" % + self.__class__.__name__) + + def register_custom_email_notification(self, eventtype, handler): + """This is used to register email notifications for custom event types. + + :param event: str, the event name + :param handler: function, to be called when `:param:event` is emitted + + Your handler should return a tuple of (email_subject, email_contents). + """ + if self.__impl != 'core': + return defer.fail("This function can only be called from a " + "CorePlugin") + self.__register_custom_provider('email', eventtype, handler) + + def deregister_custom_email_notification(self, eventtype): + if self.__impl != 'core': + return defer.fail("This function can only be called from a " + "CorePlugin") + self.__deregister_custom_provider('email', eventtype) + + def register_custom_popup_notification(self, eventtype, handler): + """This is used to register popup notifications for custom event types. + + :param event: str, the event name + :param handler: function, to be called when `:param:event` is emitted + + Your handler should return a tuple of (popup_title, popup_contents). + """ + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + self.__register_custom_provider('popup', eventtype, handler) + + def deregister_custom_popup_notification(self, eventtype): + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + self.__deregister_custom_provider('popup', eventtype) + + def register_custom_blink_notification(self, eventtype, handler): + """This is used to register blink notifications for custom event types. + + :param event: str, the event name + :param handler: function, to be called when `:param:event` is emitted + + Your handler should return `True` or `False` to blink or not the + trayicon. + """ + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + self.__register_custom_provider('blink', eventtype, handler) + + def deregister_custom_blink_notification(self, eventtype): + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + self.__deregister_custom_provider('blink', eventtype) + + def register_custom_sound_notification(self, eventtype, handler): + """This is used to register sound notifications for custom event types. + + :param event: str, the event name + :param handler: function, to be called when `:param:event` is emitted + + Your handler should return either '' to use the sound defined on the + notification preferences, the path to a sound file, which will then be + played or None, where no sound will be played at all. + """ + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + self.__register_custom_provider('sound', eventtype, handler) + + def deregister_custom_sound_notification(self, eventtype): + if self.__impl != 'gtk': + return defer.fail("This function can only be called from a " + "GtkPlugin") + 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) + 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(self._on_notify_sucess, kind) + d.addErrback(self._on_notify_failure, kind) + return d + + def __register_custom_provider(self, kind, eventtype, handler): + if not self.__handled_eventtype(eventtype): + return defer.succeed("Event not handled") + if eventtype not in self.custom_notifications: + def wrapper(*args, **kwargs): + return self.__handle_custom_providers(kind, eventtype, + *args, **kwargs) + self.custom_notifications[kind][eventtype] = (wrapper, handler) + else: + wrapper, handler = self.custom_notifications[kind][eventtype] + try: + component.get("EventManager").register_event_handler( + eventtype, wrapper + ) + except KeyError: + from deluge.ui.client import client + client.register_event_handler(eventtype, wrapper) + + def __deregister_custom_provider(self, kind, eventtype): + wrapper, handler = self.custom_notifications[kind][eventtype] + try: + component.get("EventManager").deregister_event_handler( + eventtype, wrapper + ) + except KeyError: + from deluge.ui.client import client + client.deregister_event_handler(eventtype, wrapper) + self.custom_notifications[kind].pop(eventtype) + + def __handled_eventtype(self, eventtype): + if eventtype not in known_events: + log.error("The event \"%s\" is not known" % eventtype) + return False + if known_events[eventtype].__module__.startswith('deluge.event'): + log.error("You cannot register custom notification providers " + "for built-in event types.") + return False + return True + + 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 diff --git a/deluge/plugins/notifications/notifications/core.py b/deluge/plugins/notifications/notifications/core.py index da81c328d..b44029a0f 100644 --- a/deluge/plugins/notifications/notifications/core.py +++ b/deluge/plugins/notifications/notifications/core.py @@ -39,7 +39,7 @@ import smtplib from twisted.internet import defer, threads -from deluge.event import known_events, DelugeEvent +from deluge.event import known_events from deluge.log import LOG as log from deluge.plugins.pluginbase import CorePluginBase import deluge.component as component @@ -47,6 +47,7 @@ import deluge.configmanager from deluge.core.rpcserver import export from test import TestEmailNotifications +from common import CustomNotifications DEFAULT_PREFS = { "smtp_enabled": False, @@ -63,12 +64,11 @@ DEFAULT_PREFS = { } } -class Core(CorePluginBase, component.Component): +class Core(CorePluginBase, CustomNotifications): def __init__(self, plugin_name): CorePluginBase.__init__(self, plugin_name) - component.Component.__init__(self, "Notifications") - self.email_message_providers = {} - self.tn = TestEmailNotifications() + CustomNotifications.__init__(self, 'core') + self.tn = TestEmailNotifications('core') def enable(self): self.config = deluge.configmanager.ConfigManager( @@ -78,15 +78,11 @@ class Core(CorePluginBase, component.Component): ) 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): 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) + CustomNotifications.disable(self) def update(self): pass @@ -116,55 +112,7 @@ class Core(CorePluginBase, component.Component): log.debug("Handled Notification Events: %s", handled_events) return handled_events - 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.") - - 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 - - - def _prepare_email(self, result): + def handle_custom_email_notification(self, result): if not self.config['smtp_enabled']: return defer.succeed("SMTP notification not enabled.") subject, message = result @@ -262,11 +210,11 @@ Subject: %(subject)s 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 +# 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/gtkui.py b/deluge/plugins/notifications/notifications/gtkui.py index 35c276f80..1d9167ec3 100644 --- a/deluge/plugins/notifications/notifications/gtkui.py +++ b/deluge/plugins/notifications/notifications/gtkui.py @@ -61,7 +61,8 @@ except ImportError: POPUP_AVAILABLE = False # Relative imports -from common import get_resource +from common import get_resource, CustomNotifications +from test import TestEmailNotifications DEFAULT_PREFS = { # BLINK @@ -86,10 +87,11 @@ RECIPIENT_FIELD, RECIPIENT_EDIT = range(2) SUB_NOT_SOUND) = range(6) -class GtkUI(GtkPluginBase, component.Component): +class GtkUI(GtkPluginBase, CustomNotifications): def __init__(self, plugin_name): GtkPluginBase.__init__(self, plugin_name) - component.Component.__init__(self, "Notifications") + CustomNotifications.__init__(self, 'gtk') + self.tn = TestEmailNotifications('gtk') def enable(self): self.config = deluge.configmanager.ConfigManager( @@ -227,7 +229,11 @@ class GtkUI(GtkPluginBase, component.Component): client.register_event_handler("TorrentFinishedEvent", self._on_torrent_finished_event) + self.tn.enable() + def disable(self): + self.tn.disable() + CustomNotifications.disable(self) component.get("Preferences").remove_page("Notifications") component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs) @@ -266,24 +272,6 @@ class GtkUI(GtkPluginBase, component.Component): 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(), @@ -410,14 +398,14 @@ class GtkUI(GtkPluginBase, component.Component): else: self.glade.get_widget('sound_path').set_property('sensitive', False) - # Notification methods - def blink(self): + + 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 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: @@ -434,7 +422,7 @@ class GtkUI(GtkPluginBase, component.Component): return defer.fail(err_msg) return defer.succeed(_("Notification popup shown")) - def play_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: @@ -490,14 +478,6 @@ class GtkUI(GtkPluginBase, component.Component): 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] @@ -518,3 +498,19 @@ class GtkUI(GtkPluginBase, component.Component): not self.subscriptions_model[path][SUB_NOT_SOUND] return + def handle_custom_popup_notification(self, result): + title, message = result + return defer.maybeDeferred(self.__popup, title, message) + + def handle_custom_blink_notification(self, result): + 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): + if isinstance(result, basestring): + 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/setup.py b/deluge/plugins/notifications/setup.py index 88056e15f..b594df079 100755 --- a/deluge/plugins/notifications/setup.py +++ b/deluge/plugins/notifications/setup.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 @@ -37,7 +37,7 @@ # statement from all source files in the program, then also delete it here. # -from setuptools import setup +from setuptools import setup, find_packages __plugin_name__ = "Notifications" __author__ = "Pedro Algarvio" @@ -59,7 +59,7 @@ setup( license=__license__, long_description=__long_description__ if __long_description__ else __description__, - packages=[__plugin_name__.lower()], + packages=find_packages(exclude=['**/test.py']), package_data = __pkg_data__, entry_points="""