growlee: add syslog backend
[vuplus_dvbapp-plugin] / growlee / src / plugin.py
index b7144a1..423db9d 100644 (file)
 from Plugins.Plugin import PluginDescriptor
 
 from Tools import Notifications
-from netgrowl import GrowlRegistrationPacket, GrowlNotificationPacket, \
-               GROWL_UDP_PORT, md5_constructor
-from twisted.internet.protocol import DatagramProtocol
-from twisted.internet import reactor
-from twisted.web.client import getPage
-from struct import unpack
-from socket import gaierror
-from urllib import urlencode
 
 from Screens.Setup import SetupSummary
 from Screens.Screen import Screen
-from Screens.MessageBox import MessageBox
 from Components.ActionMap import ActionMap
 from Components.config import config, getConfigListEntry, ConfigSubsection, \
-               ConfigText, ConfigPassword, ConfigYesNo, ConfigSelection, ConfigSet
+               ConfigText, ConfigPassword, ConfigYesNo, ConfigSelection, ConfigSet, \
+               ConfigSubList, ConfigNumber, NoSave
 from Components.ConfigList import ConfigListScreen
 from Components.Sources.StaticText import StaticText
 
-config.plugins.growlee = ConfigSubsection()
-config.plugins.growlee.enable_incoming = ConfigYesNo(default=False)
-config.plugins.growlee.enable_outgoing = ConfigYesNo(default=False)
-config.plugins.growlee.address = ConfigText(fixed_size=False)
-config.plugins.growlee.password = ConfigPassword()
-config.plugins.growlee.prowl_api_key = ConfigText(fixed_size=False)
-config.plugins.growlee.protocol = ConfigSelection(default="growl", choices = [("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl")])
-config.plugins.growlee.blacklist = ConfigSet(choices = [])
-
-NOTIFICATIONID = 'GrowleeReceivedNotification'
+from GrowleeConnection import gotNotification, emergencyDisable, growleeConnection
+
+from . import NOTIFICATIONID
+
+growlee = ConfigSubsection()
+config.plugins.growlee = growlee
+growlee.hostcount = ConfigNumber(default=0)
+growlee.hosts = ConfigSubList()
+
+def addHost(name):
+       s = ConfigSubsection()
+       s.name = ConfigText(default=name, fixed_size=False)
+       s.enable_incoming = ConfigYesNo(default=False)
+       s.enable_outgoing = ConfigYesNo(default=False)
+       s.address = ConfigText(fixed_size=False)
+       s.password = ConfigPassword()
+       s.protocol = ConfigSelection(default="growl", choices=[("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl"), ("syslog", "Syslog UDP")])
+       s.level = ConfigSelection(default="-1", choices=[("-1", _("Low (Yes/No)")), ("0", _("Normal (Information)")), ("1", _("High (Warning)")), ("2", _("Highest (Emergency)"))])
+       s.blacklist = ConfigSet(choices=[])
+       config.plugins.growlee.hosts.append(s)
+       return s
+
+i = 0
+while i < growlee.hostcount.value:
+       addHost(str(i+1))
+       i += 1
+
+# XXX: change to new config format
+# NOTE: after some time, remove this and hardcode default length to 1
+# since internally we assume to have at least 1 host configured
+if growlee.hostcount.value == 0:
+       growlee.enable_outgoing = ConfigYesNo(default=False)
+       growlee.enable_incoming = ConfigYesNo(default=False)
+       growlee.address = ConfigText(fixed_size=False)
+       growlee.password = ConfigPassword()
+       password = growlee.password.value
+       growlee.prowl_api_key = ConfigText()
+       growlee.protocol = ConfigSelection(default="growl", choices=[("growl", "Growl"), ("snarl", "Snarl"), ("prowl", "Prowl")])
+       growlee.level = ConfigSelection(default="-1", choices=[("-1", _("Low (Yes/No)")), ("0", _("Normal (Information)")), ("1", _("High (Warning)")), ("2", _("Highest (Emergency)"))])
+       growlee.blacklist = ConfigSet(choices=[])
+       if growlee.protocol.value == "prowl":
+               password = growlee.prowl_api_key.value
+
+       s = addHost(_("Converted connection"))
+       s.enable_incoming.value = growlee.enable_incoming.value
+       s.enable_outgoing.value = growlee.enable_outgoing.value
+       s.address.value = growlee.address.value
+       s.password.value = password
+       s.protocol.value = growlee.protocol.value
+       s.level.value = growlee.level.value
+       s.blacklist.value = growlee.blacklist.value
+
+       growlee.enable_incoming.value = False
+       growlee.enable_outgoing.value = False
+       growlee.address.value = ""
+       growlee.password.value = ""
+       growlee.prowl_api_key.value = ""
+       growlee.protocol.value = "growl"
+       growlee.level.value = "-1"
+       growlee.blacklist.value = []
+
+       growlee.hostcount.value += 1
+       growlee.save()
+       del s
+
+del i, growlee
 
 class GrowleeConfiguration(Screen, ConfigListScreen):
+       skin = """
+               <screen name="RSSSetup" position="center,center" size="560,400" title="Simple RSS Reader Setup" >
+                       <ePixmap position="0,0" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
+                       <ePixmap position="140,0" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
+                       <ePixmap position="280,0" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
+                       <ePixmap position="420,0" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
+                       <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
+                       <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
+                       <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
+                       <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
+                       <widget name="config" position="5,45" size="550,350" scrollbarMode="showOnDemand" />
+               </screen>"""
+
        def __init__(self, session):
                Screen.__init__(self, session)
-               self.skinName = [ "GrowleeConfiguration", "Setup" ]
 
                # Buttons
                self["key_red"] = StaticText(_("Cancel"))
                self["key_green"] = StaticText(_("OK"))
+               self["key_yellow"] = StaticText(_("New"))
+               self["key_blue"] = StaticText(_("Delete"))
 
                # Summary
                self.setup_title = "Growlee Configuration"
                self.onChangedEntry = []
 
                # Define Actions
-               self["actions"] = ActionMap(["SetupActions"],
-                       {
-                               "cancel": self.keyCancel,
-                               "save": self.keySave,
-                       }
-               )
-
-               config.plugins.growlee.protocol.addNotifier(self.setupList, initial_call=False)
+               self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
+               {
+                       "blue": self.delete,
+                       "yellow": self.new,
+                       "cancel": self.keyCancel,
+                       "save": self.keySave,
+               })
+
+               self.hostElement = NoSave(ConfigSelection(choices=[(x, x.name.value) for x in config.plugins.growlee.hosts]))
+               self.hostElement.addNotifier(self.setupList, initial_call=False)
                ConfigListScreen.__init__(
                        self,
                        [],
                        session=session,
                        on_change=self.changed
                )
+               self.cur = self.hostElement.value
 
                # Trigger change
                self.setupList()
                self.changed()
 
+       def delete(self):
+               from Screens.MessageBox import MessageBox
+
+               self.session.openWithCallback(
+                       self.deleteConfirm,
+                       MessageBox,
+                       _("Really delete this entry?\nIt cannot be recovered!")
+               )
+
+       def deleteConfirm(self, result):
+               if result and config.plugins.growlee.hostcount.value > 0:
+                       config.plugins.growlee.hostcount.value -= 1
+                       config.plugins.growlee.hosts.remove(self.cur)
+                       self.hostElement.setChoices([(x, x.name.value) for x in config.plugins.growlee.hosts])
+                       self.cur = self.hostElement.value
+
+       def new(self):
+               self.cur = addHost(_("New connection"))
+               config.plugins.growlee.hostcount.value += 1
+               self.hostElement.setChoices([(x, x.name.value) for x in config.plugins.growlee.hosts])
+               self.hostElement.setValue(self.cur)
+
        def changed(self):
                for x in self.onChangedEntry:
                        x()
 
        def setupList(self, *args):
+               last = self.cur
+               if self.setupList in last.protocol.notifiers:
+                       last.protocol.notifiers.remove(self.setupList)
+               cur = self.hostElement.value
+               self.cur = cur
+               cur.protocol.notifiers.append(self.setupList)
+
                l = [
-                       getConfigListEntry(_("Type"), config.plugins.growlee.protocol),
-                       getConfigListEntry(_("Send Notifications?"), config.plugins.growlee.enable_outgoing),
+                       getConfigListEntry(_("Host"), self.hostElement),
+                       getConfigListEntry(_("Name"), cur.name),
+                       getConfigListEntry(_("Type"), cur.protocol),
+                       getConfigListEntry(_("Minimum Priority"), cur.level),
+                       getConfigListEntry(_("Send Notifications?"), cur.enable_outgoing),
                ]
 
-               if config.plugins.growlee.protocol.value == "prowl":
-                       l.append(getConfigListEntry(_("API Key"), config.plugins.growlee.prowl_api_key))
+               proto = cur.protocol.value
+               if proto ==  "prowl":
+                       l.append(getConfigListEntry(_("API Key"), cur.password))
                else:
                        l.extend((
-                               getConfigListEntry(_("Receive Notifications?"), config.plugins.growlee.enable_incoming),
-                               getConfigListEntry(_("Address"), config.plugins.growlee.address),
-                               getConfigListEntry(_("Password"), config.plugins.growlee.password),
+                               getConfigListEntry(_("Receive Notifications?"), cur.enable_incoming),
+                               getConfigListEntry(_("Address"), cur.address),
                        ))
+                       if proto == "growl":
+                               l.append(
+                                       getConfigListEntry(_("Password"), cur.password)
+                               )
 
                self["config"].list = l
 
@@ -95,231 +196,40 @@ class GrowleeConfiguration(Screen, ConfigListScreen):
        def createSummary(self):
                return SetupSummary
 
+       def cancelConfirm(self):
+               ConfigListScreen.cancelConfirm(self)
+               config.plugins.growlee.cancel()
+
        def keySave(self):
+               config.plugins.growlee.save()
                if self["config"].isChanged():
-                       global port
-                       if port:
-                               def maybeConnect(*args, **kwargs):
-                                       if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-                                               doConnect()
-
-                               d = port.stopListening()
-                               if d is not None:
-                                       d.addCallback(maybeConnect).addErrback(emergencyDisable)
-                               else:
-                                       maybeConnect()
-                       elif config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-                               doConnect()
+                       def doConnect(*args, **kwargs):
+                               growleeConnection.listen()
+
+                       d = growleeConnection.stop()
+                       if d is not None:
+                               d.addCallback(doConnect).addErrback(emergencyDisable)
+                       else:
+                               maybeConnect()
 
                self.saveAll()
                self.close()
 
        def close(self):
-               config.plugins.growlee.protocol.notifiers.remove(self.setupList)
+               if self.setupList in self.cur.protocol.notifiers:
+                       self.cur.protocol.notifiers.remove(self.setupList)
                Screen.close(self)
 
 def configuration(session, **kwargs):
        session.open(GrowleeConfiguration)
 
-def doConnect():
-       global port
-       if config.plugins.growlee.protocol.value == "snarl":
-               port = reactor.listenTCP(GROWL_UDP_PORT, growlProtocolOneWrapper)
-       else:
-               port = reactor.listenUDP(GROWL_UDP_PORT, growlProtocolOneWrapper)
-
-def emergencyDisable(*args, **kwargs):
-       global port
-       if port:
-               port.stopListening()
-               port = None
-
-       if gotNotification in Notifications.notificationAdded:
-               Notifications.notificationAdded.remove(gotNotification)
-       Notifications.AddPopup(
-               _("Network error.\nDisabling Growlee!"),
-               MessageBox.TYPE_ERROR,
-               10
-       )
-
-class GrowlProtocolOneWrapper(DatagramProtocol):
-       def startProtocol(self):
-               proto = config.plugins.growlee.protocol.value
-               if config.plugins.growlee.enable_outgoing.value and not proto == "prowl":
-                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                       if proto == "growl":
-                               p = GrowlRegistrationPacket(password=config.plugins.growlee.password.value)
-                               p.addNotification()
-                               payload = p.payload()
-                       else: #proto == "snarl":
-                               payload = "type=SNP#?version=1.0#?action=register#?app=growlee\r\n"
-                       try:
-                               self.transport.write(payload, addr)
-                       except gaierror:
-                               emergencyDisable()
-
-       def doStop(self):
-               if config.plugins.growlee.enable_outgoing.value and config.plugins.growlee.protocol.value == "snarl":
-                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                       payload = "type=SNP#?version=1.0#?action=unregister#?app=growlee\r\n"
-                       try:
-                               self.transport.write(payload, addr)
-                       except gaierror:
-                               pass
-               DaragramProtocol.doStop(self)
-
-       def sendNotification(self, *args, **kwargs):
-               if not self.transport or not config.plugins.growlee.enable_outgoing.value:
-                       return
-
-               proto = config.plugins.growlee.protocol.value
-               if proto == "prowl":
-                       headers = {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'}
-                       data = {
-                               'apikey': config.plugins.growlee.prowl_api_key.value,
-                               'application': "growlee",
-                               'event': kwargs.get('title', 'No title.'),
-                               'description': kwargs.get('description', 'No message.'),
-                               'priority': kwargs.get('priority', 0),
-                       }
-
-                       getPage('https://prowl.weks.net/publicapi/add/', method = 'POST', headers = headers, postdata = urlencode(data)).addErrback(emergencyDisable)
-               else:
-                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                       if proto == "growl":
-                               # map timeout -> sticky
-                               if "timeout" in kwargs:
-                                       if kwargs["timeout"] == -1:
-                                               kwargs["sticky"] = True
-                                       del kwargs["timeout"]
-
-                               p = GrowlNotificationPacket(*args, application="growlee", **kwargs)
-                               payload = p.payload()
-                       else: #proto == "snarl":
-                               title = kwargs.get('title', 'No title.')
-                               text = kwargs.get('description', 'No message.')
-                               timeout = kwargs.get('timeout', -1)
-
-                               # NOTE: timeout = 0 means sticky, so add one second to map -1 to 0 and make 0 non-sticky
-                               if timeout < 2:
-                                       timeout += 1
-
-                               payload = "type=SNP#?version=1.0#?action=notification#?app=growlee#?class=growleeClass#?title=%s#?text=%s#?timeout=%d\r\n" % (title, text, timeout)
-                       try:
-                               self.transport.write(payload, addr)
-                       except gaierror:
-                               emergencyDisable()
-
-       def datagramReceived(self, data, addr):
-               proto = config.plugins.growlee.protocol.value
-               if proto == "prowl" or not config.plugins.growlee.enable_incoming.value:
-                       return
-
-               Len = len(data)
-               if proto == "growl":
-                       if Len < 16:
-                               return
-
-                       digest = data[-16:]
-                       password = config.plugins.growlee.password.value
-                       checksum = md5_constructor()
-                       checksum.update(data[:-16])
-                       if password:
-                               checksum.update(password)
-                       if digest != checksum.digest():
-                               return
-
-                       # notify packet
-                       if data[1] == '\x01':
-                               nlen, tlen, dlen, alen = unpack("!HHHH",str(data[4:12]))
-                               notification, title, description = unpack("%ds%ds%ds" % (nlen, tlen, dlen), data[12:Len-alen-16])
-
-                               Notifications.AddNotificationWithID(
-                                       NOTIFICATIONID,
-                                       MessageBox,
-                                       text = title + '\n' + description,
-                                       type = MessageBox.TYPE_INFO,
-                                       timeout = 5,
-                                       close_on_any_key = True,
-                               )
-
-                       # TODO: do we want to handle register packets? :-)
-               else: #proto == "snarl":
-                       if Len < 23 or not data[:23] == "type=SNP#?version=1.0#?":
-                               return
-
-                       items = data[23:].split('#?')
-
-                       title = ''
-                       description = ''
-                       timeout = 5
-                       for item in items:
-                               key, value = item.split('=')
-                               if key == "action":
-                                       if value != "notification":
-                                               # NOTE: we pretend to handle&accept pretty much everything one throws at us
-                                               addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                                               payload = "SNP/1.0/0/OK\r\n"
-                                               try:
-                                                       self.transport.write(payload, addr)
-                                               except gaierror:
-                                                       emergencyDisable()
-                                               return
-                               elif key == "title":
-                                       title = value
-                               elif key == "text":
-                                       description = value
-                               elif key == "timeout":
-                                       timeout = int(value)
-
-                       Notifications.AddNotificationWithID(
-                               NOTIFICATIONID,
-                               MessageBox,
-                               text = title + '\n' + description,
-                               type = MessageBox.TYPE_INFO,
-                               timeout = timeout,
-                               close_on_any_key = True,
-                       )
-
-                       # return ok
-                       addr = (config.plugins.growlee.address.value, GROWL_UDP_PORT)
-                       payload = "SNP/1.0/0/OK\r\n"
-                       try:
-                               self.transport.write(payload, addr)
-                       except gaierror:
-                               emergencyDisable()
-
-
-growlProtocolOneWrapper = GrowlProtocolOneWrapper()
-port = None
-
-def gotNotification():
-       notifications = Notifications.notifications
-       if notifications:
-               _, screen, args, kwargs, id = notifications[-1]
-               if screen is MessageBox and id != NOTIFICATIONID and id not in config.plugins.growlee.blacklist.value:
-
-                       type = kwargs.get("type", 0)
-                       timeout = kwargs.get("timeout", -1)
-
-                       if "text" in kwargs:
-                               description = kwargs["text"]
-                       else:
-                               description = args[0]
-                       description = description.decode('utf-8')
-
-                       # NOTE: priority is in [-2; 2] but type is [0; 3] so map it
-                       # XXX: maybe priority==type-2 would be more appropriate
-                       growlProtocolOneWrapper.sendNotification(title="Dreambox", description=description, password=config.plugins.growlee.password.value, priority=type-1, timeout=timeout)
-
 def autostart(**kwargs):
-       if config.plugins.growlee.enable_incoming.value or config.plugins.growlee.enable_outgoing.value:
-               doConnect()
-
        # NOTE: we need to be the first one to be notified since other listeners
        # may remove the notifications from the list for good
        Notifications.notificationAdded.insert(0, gotNotification)
 
+       growleeConnection.listen()
+
 def Plugins(**kwargs):
        return [
                PluginDescriptor(