Custom notifications now working for emails, popups, blinks and sound.

Blinks might need some changes because, there's no need to formar or gather extra info, so, it might not be needed to allow plugin developers to register custom handlers for it.
This commit is contained in:
Pedro Algarvio 2009-11-22 02:35:12 +00:00
parent 1b7a50f88b
commit 8420d6105b
4 changed files with 229 additions and 133 deletions

View File

@ -36,38 +36,190 @@
# this exception statement from your version. If you delete this exception # this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here. # 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): def get_resource(filename):
import pkg_resources, os import pkg_resources, os
return pkg_resources.resource_filename("notifications", return pkg_resources.resource_filename("notifications",
os.path.join("data", filename)) 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

View File

@ -39,7 +39,7 @@
import smtplib import smtplib
from twisted.internet import defer, threads 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.log import LOG as log
from deluge.plugins.pluginbase import CorePluginBase from deluge.plugins.pluginbase import CorePluginBase
import deluge.component as component import deluge.component as component
@ -47,6 +47,7 @@ import deluge.configmanager
from deluge.core.rpcserver import export from deluge.core.rpcserver import export
from test import TestEmailNotifications from test import TestEmailNotifications
from common import CustomNotifications
DEFAULT_PREFS = { DEFAULT_PREFS = {
"smtp_enabled": False, "smtp_enabled": False,
@ -63,12 +64,11 @@ DEFAULT_PREFS = {
} }
} }
class Core(CorePluginBase, component.Component): class Core(CorePluginBase, CustomNotifications):
def __init__(self, plugin_name): def __init__(self, plugin_name):
CorePluginBase.__init__(self, plugin_name) CorePluginBase.__init__(self, plugin_name)
component.Component.__init__(self, "Notifications") CustomNotifications.__init__(self, 'core')
self.email_message_providers = {} self.tn = TestEmailNotifications('core')
self.tn = TestEmailNotifications()
def enable(self): def enable(self):
self.config = deluge.configmanager.ConfigManager( self.config = deluge.configmanager.ConfigManager(
@ -78,15 +78,11 @@ class Core(CorePluginBase, component.Component):
) )
log.debug("\n\nENABLING CORE NOTIFICATIONS\n\n") log.debug("\n\nENABLING CORE NOTIFICATIONS\n\n")
self.tn.enable() 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): def disable(self):
self.tn.disable() self.tn.disable()
log.debug("\n\nDISABLING CORE NOTIFICATIONS\n\n") log.debug("\n\nDISABLING CORE NOTIFICATIONS\n\n")
for eventtype in self.email_message_providers.keys(): CustomNotifications.disable(self)
self.deregister_email_message_provider(eventtype)
def update(self): def update(self):
pass pass
@ -116,55 +112,7 @@ class Core(CorePluginBase, component.Component):
log.debug("Handled Notification Events: %s", handled_events) log.debug("Handled Notification Events: %s", handled_events)
return handled_events return handled_events
def register_email_message_provider(self, eventtype, handler): def handle_custom_email_notification(self, result):
"""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):
if not self.config['smtp_enabled']: if not self.config['smtp_enabled']:
return defer.succeed("SMTP notification not enabled.") return defer.succeed("SMTP notification not enabled.")
subject, message = result subject, message = result
@ -262,11 +210,11 @@ Subject: %(subject)s
return d return d
def _on_notify_sucess(self, result): # def _on_notify_sucess(self, result):
log.debug("\n\nEMAIL Notification success: %s", result) # log.debug("\n\nEMAIL Notification success: %s", result)
return result # return result
#
#
def _on_notify_failure(self, failure): # def _on_notify_failure(self, failure):
log.debug("\n\nEMAIL Notification failure: %s", failure) # log.debug("\n\nEMAIL Notification failure: %s", failure)
return failure # return failure

View File

@ -61,7 +61,8 @@ except ImportError:
POPUP_AVAILABLE = False POPUP_AVAILABLE = False
# Relative imports # Relative imports
from common import get_resource from common import get_resource, CustomNotifications
from test import TestEmailNotifications
DEFAULT_PREFS = { DEFAULT_PREFS = {
# BLINK # BLINK
@ -86,10 +87,11 @@ RECIPIENT_FIELD, RECIPIENT_EDIT = range(2)
SUB_NOT_SOUND) = range(6) SUB_NOT_SOUND) = range(6)
class GtkUI(GtkPluginBase, component.Component): class GtkUI(GtkPluginBase, CustomNotifications):
def __init__(self, plugin_name): def __init__(self, plugin_name):
GtkPluginBase.__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): def enable(self):
self.config = deluge.configmanager.ConfigManager( self.config = deluge.configmanager.ConfigManager(
@ -227,7 +229,11 @@ class GtkUI(GtkPluginBase, component.Component):
client.register_event_handler("TorrentFinishedEvent", client.register_event_handler("TorrentFinishedEvent",
self._on_torrent_finished_event) self._on_torrent_finished_event)
self.tn.enable()
def disable(self): def disable(self):
self.tn.disable()
CustomNotifications.disable(self)
component.get("Preferences").remove_page("Notifications") component.get("Preferences").remove_page("Notifications")
component.get("PluginManager").deregister_hook("on_apply_prefs", component.get("PluginManager").deregister_hook("on_apply_prefs",
self.on_apply_prefs) self.on_apply_prefs)
@ -266,24 +272,6 @@ class GtkUI(GtkPluginBase, component.Component):
current_blink_subscriptions.append(event) current_blink_subscriptions.append(event)
if sound: if sound:
current_sound_subscriptions.append(event) 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({ self.config.config.update({
"popup_enabled": self.glade.get_widget("popup_enabled").get_active(), "popup_enabled": self.glade.get_widget("popup_enabled").get_active(),
@ -410,14 +398,14 @@ class GtkUI(GtkPluginBase, component.Component):
else: else:
self.glade.get_widget('sound_path').set_property('sensitive', False) 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 = defer.maybeDeferred(self.systray.blink, True)
d.addCallback(self._on_notify_sucess, "blink") d.addCallback(self._on_notify_sucess, "blink")
d.addCallback(self._on_notify_failure, "blink") d.addCallback(self._on_notify_failure, "blink")
return d return d
def popup(self, title='', message=''): def __popup(self, title='', message=''):
if not self.config['popup_enabled']: if not self.config['popup_enabled']:
return defer.succeed(_("Popup notification is not enabled.")) return defer.succeed(_("Popup notification is not enabled."))
if not POPUP_AVAILABLE: if not POPUP_AVAILABLE:
@ -434,7 +422,7 @@ class GtkUI(GtkPluginBase, component.Component):
return defer.fail(err_msg) return defer.fail(err_msg)
return defer.succeed(_("Notification popup shown")) return defer.succeed(_("Notification popup shown"))
def play_sound(self, sound_path=''): def __play_sound(self, sound_path=''):
if not self.config['sound_enabled']: if not self.config['sound_enabled']:
return defer.succeed(_("Sound notification not enabled")) return defer.succeed(_("Sound notification not enabled"))
if not SOUND_AVAILABLE: if not SOUND_AVAILABLE:
@ -490,14 +478,6 @@ class GtkUI(GtkPluginBase, component.Component):
d.addErrback(self._on_notify_failure, 'popup') d.addErrback(self._on_notify_failure, 'popup')
return d 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): def _on_email_col_toggled(self, cell, path):
self.subscriptions_model[path][SUB_NOT_EMAIL] = \ self.subscriptions_model[path][SUB_NOT_EMAIL] = \
not 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] not self.subscriptions_model[path][SUB_NOT_SOUND]
return 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)

View File

@ -22,9 +22,9 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with deluge. If not, write to: # along with deluge. If not, write to:
# The Free Software Foundation, Inc., # The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
# In addition, as a special exception, the copyright holders give # In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL # 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. # 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" __plugin_name__ = "Notifications"
__author__ = "Pedro Algarvio" __author__ = "Pedro Algarvio"
@ -59,7 +59,7 @@ setup(
license=__license__, license=__license__,
long_description=__long_description__ if __long_description__ else __description__, long_description=__long_description__ if __long_description__ else __description__,
packages=[__plugin_name__.lower()], packages=find_packages(exclude=['**/test.py']),
package_data = __pkg_data__, package_data = __pkg_data__,
entry_points=""" entry_points="""