Ignore Growl timeout. fixes #1240
This commit is contained in:
@@ -37,8 +37,11 @@ class Growl(Notification):
|
|||||||
)
|
)
|
||||||
self.growl.register()
|
self.growl.register()
|
||||||
self.registered = True
|
self.registered = True
|
||||||
except:
|
except Exception, e:
|
||||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
if 'timed out' in str(e):
|
||||||
|
self.registered = True
|
||||||
|
else:
|
||||||
|
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||||
|
|
||||||
def notify(self, message = '', data = {}, listener = None):
|
def notify(self, message = '', data = {}, listener = None):
|
||||||
if self.isDisabled(): return
|
if self.isDisabled(): return
|
||||||
|
|||||||
+63
-37
@@ -1,8 +1,9 @@
|
|||||||
import hashlib
|
|
||||||
import re
|
import re
|
||||||
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
import StringIO
|
||||||
|
|
||||||
__version__ = '0.6'
|
__version__ = '0.8'
|
||||||
|
|
||||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||||
GNTP_INFO_LINE = re.compile(
|
GNTP_INFO_LINE = re.compile(
|
||||||
@@ -19,7 +20,7 @@ GNTP_INFO_LINE_SHORT = re.compile(
|
|||||||
|
|
||||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||||
|
|
||||||
GNTP_EOL = u'\r\n'
|
GNTP_EOL = '\r\n'
|
||||||
|
|
||||||
|
|
||||||
class BaseError(Exception):
|
class BaseError(Exception):
|
||||||
@@ -43,6 +44,14 @@ class UnsupportedError(BaseError):
|
|||||||
errordesc = 'Currently unsupported by gntp.py'
|
errordesc = 'Currently unsupported by gntp.py'
|
||||||
|
|
||||||
|
|
||||||
|
class _GNTPBuffer(StringIO.StringIO):
|
||||||
|
"""GNTP Buffer class"""
|
||||||
|
def writefmt(self, message = "", *args):
|
||||||
|
"""Shortcut function for writing GNTP Headers"""
|
||||||
|
self.write((message % args).encode('utf8', 'replace'))
|
||||||
|
self.write(GNTP_EOL)
|
||||||
|
|
||||||
|
|
||||||
class _GNTPBase(object):
|
class _GNTPBase(object):
|
||||||
"""Base initilization
|
"""Base initilization
|
||||||
|
|
||||||
@@ -206,8 +215,8 @@ class _GNTPBase(object):
|
|||||||
if not match:
|
if not match:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
key = match.group(1).strip()
|
key = unicode(match.group(1).strip(), 'utf8', 'replace')
|
||||||
val = match.group(2).strip()
|
val = unicode(match.group(2).strip(), 'utf8', 'replace')
|
||||||
dict[key] = val
|
dict[key] = val
|
||||||
return dict
|
return dict
|
||||||
|
|
||||||
@@ -217,6 +226,15 @@ class _GNTPBase(object):
|
|||||||
else:
|
else:
|
||||||
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
|
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
|
||||||
|
|
||||||
|
def add_resource(self, data):
|
||||||
|
"""Add binary resource
|
||||||
|
|
||||||
|
:param string data: Binary Data
|
||||||
|
"""
|
||||||
|
identifier = hashlib.md5(data).hexdigest()
|
||||||
|
self.resources[identifier] = data
|
||||||
|
return 'x-growl-resource://%s' % identifier
|
||||||
|
|
||||||
def decode(self, data, password = None):
|
def decode(self, data, password = None):
|
||||||
"""Decode GNTP Message
|
"""Decode GNTP Message
|
||||||
|
|
||||||
@@ -229,19 +247,30 @@ class _GNTPBase(object):
|
|||||||
self.headers = self._parse_dict(parts[0])
|
self.headers = self._parse_dict(parts[0])
|
||||||
|
|
||||||
def encode(self):
|
def encode(self):
|
||||||
"""Encode a GNTP Message
|
"""Encode a generic GNTP Message
|
||||||
|
|
||||||
:return string: Encoded GNTP Message ready to be sent
|
:return string: GNTP Message ready to be sent
|
||||||
"""
|
"""
|
||||||
self.validate()
|
|
||||||
|
|
||||||
message = self._format_info() + GNTP_EOL
|
buffer = _GNTPBuffer()
|
||||||
|
|
||||||
|
buffer.writefmt(self._format_info())
|
||||||
|
|
||||||
#Headers
|
#Headers
|
||||||
for k, v in self.headers.iteritems():
|
for k, v in self.headers.iteritems():
|
||||||
message += u'%s: %s%s' % (k, v, GNTP_EOL)
|
buffer.writefmt('%s: %s', k, v)
|
||||||
|
buffer.writefmt()
|
||||||
|
|
||||||
message += GNTP_EOL
|
#Resources
|
||||||
return message
|
for resource, data in self.resources.iteritems():
|
||||||
|
buffer.writefmt('Identifier: %s', resource)
|
||||||
|
buffer.writefmt('Length: %d', len(data))
|
||||||
|
buffer.writefmt()
|
||||||
|
buffer.write(data)
|
||||||
|
buffer.writefmt()
|
||||||
|
buffer.writefmt()
|
||||||
|
|
||||||
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
class GNTPRegister(_GNTPBase):
|
class GNTPRegister(_GNTPBase):
|
||||||
@@ -290,7 +319,7 @@ class GNTPRegister(_GNTPBase):
|
|||||||
|
|
||||||
for i, part in enumerate(parts):
|
for i, part in enumerate(parts):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
continue # Skip Header
|
continue # Skip Header
|
||||||
if part.strip() == '':
|
if part.strip() == '':
|
||||||
continue
|
continue
|
||||||
notice = self._parse_dict(part)
|
notice = self._parse_dict(part)
|
||||||
@@ -319,22 +348,33 @@ class GNTPRegister(_GNTPBase):
|
|||||||
|
|
||||||
:return string: Encoded GNTP Registration message
|
:return string: Encoded GNTP Registration message
|
||||||
"""
|
"""
|
||||||
self.validate()
|
|
||||||
|
|
||||||
message = self._format_info() + GNTP_EOL
|
buffer = _GNTPBuffer()
|
||||||
|
|
||||||
|
buffer.writefmt(self._format_info())
|
||||||
|
|
||||||
#Headers
|
#Headers
|
||||||
for k, v in self.headers.iteritems():
|
for k, v in self.headers.iteritems():
|
||||||
message += u'%s: %s%s' % (k, v, GNTP_EOL)
|
buffer.writefmt('%s: %s', k, v)
|
||||||
|
buffer.writefmt()
|
||||||
|
|
||||||
#Notifications
|
#Notifications
|
||||||
if len(self.notifications) > 0:
|
if len(self.notifications) > 0:
|
||||||
for notice in self.notifications:
|
for notice in self.notifications:
|
||||||
message += GNTP_EOL
|
|
||||||
for k, v in notice.iteritems():
|
for k, v in notice.iteritems():
|
||||||
message += u'%s: %s%s' % (k, v, GNTP_EOL)
|
buffer.writefmt('%s: %s', k, v)
|
||||||
|
buffer.writefmt()
|
||||||
|
|
||||||
message += GNTP_EOL
|
#Resources
|
||||||
return message
|
for resource, data in self.resources.iteritems():
|
||||||
|
buffer.writefmt('Identifier: %s', resource)
|
||||||
|
buffer.writefmt('Length: %d', len(data))
|
||||||
|
buffer.writefmt()
|
||||||
|
buffer.write(data)
|
||||||
|
buffer.writefmt()
|
||||||
|
buffer.writefmt()
|
||||||
|
|
||||||
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
class GNTPNotice(_GNTPBase):
|
class GNTPNotice(_GNTPBase):
|
||||||
@@ -379,7 +419,7 @@ class GNTPNotice(_GNTPBase):
|
|||||||
|
|
||||||
for i, part in enumerate(parts):
|
for i, part in enumerate(parts):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
continue # Skip Header
|
continue # Skip Header
|
||||||
if part.strip() == '':
|
if part.strip() == '':
|
||||||
continue
|
continue
|
||||||
notice = self._parse_dict(part)
|
notice = self._parse_dict(part)
|
||||||
@@ -388,21 +428,6 @@ class GNTPNotice(_GNTPBase):
|
|||||||
#open('notice.png','wblol').write(notice['Data'])
|
#open('notice.png','wblol').write(notice['Data'])
|
||||||
self.resources[notice.get('Identifier')] = notice
|
self.resources[notice.get('Identifier')] = notice
|
||||||
|
|
||||||
def encode(self):
|
|
||||||
"""Encode a GNTP Notification Message
|
|
||||||
|
|
||||||
:return string: GNTP Notification Message ready to be sent
|
|
||||||
"""
|
|
||||||
self.validate()
|
|
||||||
|
|
||||||
message = self._format_info() + GNTP_EOL
|
|
||||||
#Headers
|
|
||||||
for k, v in self.headers.iteritems():
|
|
||||||
message += u'%s: %s%s' % (k, v, GNTP_EOL)
|
|
||||||
|
|
||||||
message += GNTP_EOL
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
class GNTPSubscribe(_GNTPBase):
|
class GNTPSubscribe(_GNTPBase):
|
||||||
"""Represents a GNTP Subscribe Command
|
"""Represents a GNTP Subscribe Command
|
||||||
@@ -457,7 +482,8 @@ class GNTPError(_GNTPBase):
|
|||||||
self.add_header('Error-Description', errordesc)
|
self.add_header('Error-Description', errordesc)
|
||||||
|
|
||||||
def error(self):
|
def error(self):
|
||||||
return self.headers['Error-Code'], self.headers['Error-Description']
|
return (self.headers.get('Error-Code', None),
|
||||||
|
self.headers.get('Error-Description', None))
|
||||||
|
|
||||||
|
|
||||||
def parse_gntp(data, password = None):
|
def parse_gntp(data, password = None):
|
||||||
|
|||||||
+63
-46
@@ -22,43 +22,6 @@ __all__ = [
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def mini(description, applicationName = 'PythonMini', noteType = "Message",
|
|
||||||
title = "Mini Message", applicationIcon = None, hostname = 'localhost',
|
|
||||||
password = None, port = 23053, sticky = False, priority = None,
|
|
||||||
callback = None):
|
|
||||||
"""Single notification function
|
|
||||||
|
|
||||||
Simple notification function in one line. Has only one required parameter
|
|
||||||
and attempts to use reasonable defaults for everything else
|
|
||||||
:param string description: Notification message
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
For now, only URL callbacks are supported. In the future, the
|
|
||||||
callback argument will also support a function
|
|
||||||
"""
|
|
||||||
growl = GrowlNotifier(
|
|
||||||
applicationName = applicationName,
|
|
||||||
notifications = [noteType],
|
|
||||||
defaultNotifications = [noteType],
|
|
||||||
hostname = hostname,
|
|
||||||
password = password,
|
|
||||||
port = port,
|
|
||||||
)
|
|
||||||
result = growl.register()
|
|
||||||
if result is not True:
|
|
||||||
return result
|
|
||||||
|
|
||||||
return growl.notify(
|
|
||||||
noteType = noteType,
|
|
||||||
title = title,
|
|
||||||
description = description,
|
|
||||||
icon = applicationIcon,
|
|
||||||
sticky = sticky,
|
|
||||||
priority = priority,
|
|
||||||
callback = callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GrowlNotifier(object):
|
class GrowlNotifier(object):
|
||||||
"""Helper class to simplfy sending Growl messages
|
"""Helper class to simplfy sending Growl messages
|
||||||
|
|
||||||
@@ -93,10 +56,12 @@ class GrowlNotifier(object):
|
|||||||
def _checkIcon(self, data):
|
def _checkIcon(self, data):
|
||||||
'''
|
'''
|
||||||
Check the icon to see if it's valid
|
Check the icon to see if it's valid
|
||||||
@param data:
|
|
||||||
@todo Consider checking for a valid URL
|
If it's a simple URL icon, then we return True. If it's a data icon
|
||||||
|
then we return False
|
||||||
'''
|
'''
|
||||||
return data
|
logger.info('Checking icon')
|
||||||
|
return data.startswith('http')
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
"""Send GNTP Registration
|
"""Send GNTP Registration
|
||||||
@@ -112,7 +77,11 @@ class GrowlNotifier(object):
|
|||||||
enabled = notification in self.defaultNotifications
|
enabled = notification in self.defaultNotifications
|
||||||
register.add_notification(notification, enabled)
|
register.add_notification(notification, enabled)
|
||||||
if self.applicationIcon:
|
if self.applicationIcon:
|
||||||
register.add_header('Application-Icon', self.applicationIcon)
|
if self._checkIcon(self.applicationIcon):
|
||||||
|
register.add_header('Application-Icon', self.applicationIcon)
|
||||||
|
else:
|
||||||
|
id = register.add_resource(self.applicationIcon)
|
||||||
|
register.add_header('Application-Icon', id)
|
||||||
if self.password:
|
if self.password:
|
||||||
register.set_password(self.password, self.passwordHash)
|
register.set_password(self.password, self.passwordHash)
|
||||||
self.add_origin_info(register)
|
self.add_origin_info(register)
|
||||||
@@ -120,7 +89,7 @@ class GrowlNotifier(object):
|
|||||||
return self._send('register', register)
|
return self._send('register', register)
|
||||||
|
|
||||||
def notify(self, noteType, title, description, icon = None, sticky = False,
|
def notify(self, noteType, title, description, icon = None, sticky = False,
|
||||||
priority = None, callback = None):
|
priority = None, callback = None, identifier = None):
|
||||||
"""Send a GNTP notifications
|
"""Send a GNTP notifications
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
@@ -151,11 +120,18 @@ class GrowlNotifier(object):
|
|||||||
if priority:
|
if priority:
|
||||||
notice.add_header('Notification-Priority', priority)
|
notice.add_header('Notification-Priority', priority)
|
||||||
if icon:
|
if icon:
|
||||||
notice.add_header('Notification-Icon', self._checkIcon(icon))
|
if self._checkIcon(icon):
|
||||||
|
notice.add_header('Notification-Icon', icon)
|
||||||
|
else:
|
||||||
|
id = notice.add_resource(icon)
|
||||||
|
notice.add_header('Notification-Icon', id)
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
notice.add_header('Notification-Text', description)
|
notice.add_header('Notification-Text', description)
|
||||||
if callback:
|
if callback:
|
||||||
notice.add_header('Notification-Callback-Target', callback)
|
notice.add_header('Notification-Callback-Target', callback)
|
||||||
|
if identifier:
|
||||||
|
notice.add_header('Notification-Coalescing-ID', identifier)
|
||||||
|
|
||||||
self.add_origin_info(notice)
|
self.add_origin_info(notice)
|
||||||
self.notify_hook(notice)
|
self.notify_hook(notice)
|
||||||
@@ -193,9 +169,10 @@ class GrowlNotifier(object):
|
|||||||
def subscribe_hook(self, packet):
|
def subscribe_hook(self, packet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _send(self, type, packet):
|
def _send(self, messagetype, packet):
|
||||||
"""Send the GNTP Packet"""
|
"""Send the GNTP Packet"""
|
||||||
|
|
||||||
|
packet.validate()
|
||||||
data = packet.encode()
|
data = packet.encode()
|
||||||
|
|
||||||
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
|
||||||
@@ -203,7 +180,7 @@ class GrowlNotifier(object):
|
|||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
s.settimeout(self.socketTimeout)
|
s.settimeout(self.socketTimeout)
|
||||||
s.connect((self.hostname, self.port))
|
s.connect((self.hostname, self.port))
|
||||||
s.send(data.encode('utf8', 'replace'))
|
s.send(data)
|
||||||
recv_data = s.recv(1024)
|
recv_data = s.recv(1024)
|
||||||
while not recv_data.endswith("\r\n\r\n"):
|
while not recv_data.endswith("\r\n\r\n"):
|
||||||
recv_data += s.recv(1024)
|
recv_data += s.recv(1024)
|
||||||
@@ -212,11 +189,51 @@ class GrowlNotifier(object):
|
|||||||
|
|
||||||
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
|
||||||
|
|
||||||
if response.info['messagetype'] == '-OK':
|
if type(response) == gntp.GNTPOK:
|
||||||
return True
|
return True
|
||||||
logger.error('Invalid response: %s', response.error())
|
logger.error('Invalid response: %s', response.error())
|
||||||
return response.error()
|
return response.error()
|
||||||
|
|
||||||
|
|
||||||
|
def mini(description, applicationName = 'PythonMini', noteType = "Message",
|
||||||
|
title = "Mini Message", applicationIcon = None, hostname = 'localhost',
|
||||||
|
password = None, port = 23053, sticky = False, priority = None,
|
||||||
|
callback = None, notificationIcon = None, identifier = None,
|
||||||
|
notifierFactory = GrowlNotifier):
|
||||||
|
"""Single notification function
|
||||||
|
|
||||||
|
Simple notification function in one line. Has only one required parameter
|
||||||
|
and attempts to use reasonable defaults for everything else
|
||||||
|
:param string description: Notification message
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
For now, only URL callbacks are supported. In the future, the
|
||||||
|
callback argument will also support a function
|
||||||
|
"""
|
||||||
|
growl = notifierFactory(
|
||||||
|
applicationName = applicationName,
|
||||||
|
notifications = [noteType],
|
||||||
|
defaultNotifications = [noteType],
|
||||||
|
applicationIcon = applicationIcon,
|
||||||
|
hostname = hostname,
|
||||||
|
password = password,
|
||||||
|
port = port,
|
||||||
|
)
|
||||||
|
result = growl.register()
|
||||||
|
if result is not True:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return growl.notify(
|
||||||
|
noteType = noteType,
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
icon = notificationIcon,
|
||||||
|
sticky = sticky,
|
||||||
|
priority = priority,
|
||||||
|
callback = callback,
|
||||||
|
identifier = identifier,
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# If we're running this module directly we're likely running it as a test
|
# If we're running this module directly we're likely running it as a test
|
||||||
# so extra debugging is useful
|
# so extra debugging is useful
|
||||||
|
|||||||
Reference in New Issue
Block a user