Ignore Growl timeout. fixes #1240
This commit is contained in:
@@ -37,8 +37,11 @@ class Growl(Notification):
|
||||
)
|
||||
self.growl.register()
|
||||
self.registered = True
|
||||
except:
|
||||
log.error('Failed register of growl: %s', traceback.format_exc())
|
||||
except Exception, e:
|
||||
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):
|
||||
if self.isDisabled(): return
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import hashlib
|
||||
import re
|
||||
import hashlib
|
||||
import time
|
||||
import StringIO
|
||||
|
||||
__version__ = '0.6'
|
||||
__version__ = '0.8'
|
||||
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
GNTP_INFO_LINE = re.compile(
|
||||
@@ -19,7 +20,7 @@ GNTP_INFO_LINE_SHORT = re.compile(
|
||||
|
||||
GNTP_HEADER = re.compile('([\w-]+):(.+)')
|
||||
|
||||
GNTP_EOL = u'\r\n'
|
||||
GNTP_EOL = '\r\n'
|
||||
|
||||
|
||||
class BaseError(Exception):
|
||||
@@ -43,6 +44,14 @@ class UnsupportedError(BaseError):
|
||||
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):
|
||||
"""Base initilization
|
||||
|
||||
@@ -206,8 +215,8 @@ class _GNTPBase(object):
|
||||
if not match:
|
||||
continue
|
||||
|
||||
key = match.group(1).strip()
|
||||
val = match.group(2).strip()
|
||||
key = unicode(match.group(1).strip(), 'utf8', 'replace')
|
||||
val = unicode(match.group(2).strip(), 'utf8', 'replace')
|
||||
dict[key] = val
|
||||
return dict
|
||||
|
||||
@@ -217,6 +226,15 @@ class _GNTPBase(object):
|
||||
else:
|
||||
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):
|
||||
"""Decode GNTP Message
|
||||
|
||||
@@ -229,19 +247,30 @@ class _GNTPBase(object):
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
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
|
||||
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
|
||||
return message
|
||||
#Resources
|
||||
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):
|
||||
@@ -290,7 +319,7 @@ class GNTPRegister(_GNTPBase):
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
@@ -319,22 +348,33 @@ class GNTPRegister(_GNTPBase):
|
||||
|
||||
:return string: Encoded GNTP Registration message
|
||||
"""
|
||||
self.validate()
|
||||
|
||||
message = self._format_info() + GNTP_EOL
|
||||
buffer = _GNTPBuffer()
|
||||
|
||||
buffer.writefmt(self._format_info())
|
||||
|
||||
#Headers
|
||||
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
|
||||
if len(self.notifications) > 0:
|
||||
for notice in self.notifications:
|
||||
message += GNTP_EOL
|
||||
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
|
||||
return message
|
||||
#Resources
|
||||
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):
|
||||
@@ -379,7 +419,7 @@ class GNTPNotice(_GNTPBase):
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if i == 0:
|
||||
continue # Skip Header
|
||||
continue # Skip Header
|
||||
if part.strip() == '':
|
||||
continue
|
||||
notice = self._parse_dict(part)
|
||||
@@ -388,21 +428,6 @@ class GNTPNotice(_GNTPBase):
|
||||
#open('notice.png','wblol').write(notice['Data'])
|
||||
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):
|
||||
"""Represents a GNTP Subscribe Command
|
||||
@@ -457,7 +482,8 @@ class GNTPError(_GNTPBase):
|
||||
self.add_header('Error-Description', errordesc)
|
||||
|
||||
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):
|
||||
|
||||
@@ -22,43 +22,6 @@ __all__ = [
|
||||
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):
|
||||
"""Helper class to simplfy sending Growl messages
|
||||
|
||||
@@ -93,10 +56,12 @@ class GrowlNotifier(object):
|
||||
def _checkIcon(self, data):
|
||||
'''
|
||||
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):
|
||||
"""Send GNTP Registration
|
||||
@@ -112,7 +77,11 @@ class GrowlNotifier(object):
|
||||
enabled = notification in self.defaultNotifications
|
||||
register.add_notification(notification, enabled)
|
||||
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:
|
||||
register.set_password(self.password, self.passwordHash)
|
||||
self.add_origin_info(register)
|
||||
@@ -120,7 +89,7 @@ class GrowlNotifier(object):
|
||||
return self._send('register', register)
|
||||
|
||||
def notify(self, noteType, title, description, icon = None, sticky = False,
|
||||
priority = None, callback = None):
|
||||
priority = None, callback = None, identifier = None):
|
||||
"""Send a GNTP notifications
|
||||
|
||||
.. warning::
|
||||
@@ -151,11 +120,18 @@ class GrowlNotifier(object):
|
||||
if priority:
|
||||
notice.add_header('Notification-Priority', priority)
|
||||
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:
|
||||
notice.add_header('Notification-Text', description)
|
||||
if callback:
|
||||
notice.add_header('Notification-Callback-Target', callback)
|
||||
if identifier:
|
||||
notice.add_header('Notification-Coalescing-ID', identifier)
|
||||
|
||||
self.add_origin_info(notice)
|
||||
self.notify_hook(notice)
|
||||
@@ -193,9 +169,10 @@ class GrowlNotifier(object):
|
||||
def subscribe_hook(self, packet):
|
||||
pass
|
||||
|
||||
def _send(self, type, packet):
|
||||
def _send(self, messagetype, packet):
|
||||
"""Send the GNTP Packet"""
|
||||
|
||||
packet.validate()
|
||||
data = packet.encode()
|
||||
|
||||
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.settimeout(self.socketTimeout)
|
||||
s.connect((self.hostname, self.port))
|
||||
s.send(data.encode('utf8', 'replace'))
|
||||
s.send(data)
|
||||
recv_data = s.recv(1024)
|
||||
while not recv_data.endswith("\r\n\r\n"):
|
||||
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)
|
||||
|
||||
if response.info['messagetype'] == '-OK':
|
||||
if type(response) == gntp.GNTPOK:
|
||||
return True
|
||||
logger.error('Invalid response: %s', 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 we're running this module directly we're likely running it as a test
|
||||
# so extra debugging is useful
|
||||
|
||||
Reference in New Issue
Block a user