From ca08287cff0b9a0f1704b281da3995bb5e9af21f Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 7 Jan 2013 20:54:21 +0100 Subject: [PATCH] Ignore Growl timeout. fixes #1240 --- couchpotato/core/notifications/growl/main.py | 7 +- libs/gntp/__init__.py | 100 +++++++++++++++--------- libs/gntp/notifier.py | 109 ++++++++++++++++----------- 3 files changed, 131 insertions(+), 85 deletions(-) diff --git a/couchpotato/core/notifications/growl/main.py b/couchpotato/core/notifications/growl/main.py index 4aa1c31..7f1398d 100644 --- a/couchpotato/core/notifications/growl/main.py +++ b/couchpotato/core/notifications/growl/main.py @@ -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 diff --git a/libs/gntp/__init__.py b/libs/gntp/__init__.py index 7e0d60f..eabbfa4 100755 --- a/libs/gntp/__init__.py +++ b/libs/gntp/__init__.py @@ -1,8 +1,9 @@ -import hashlib import re +import hashlib import time +import StringIO -__version__ = '0.6' +__version__ = '0.8' #GNTP/ [:][ :.] 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() + + #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() - message += GNTP_EOL - return message + 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): diff --git a/libs/gntp/notifier.py b/libs/gntp/notifier.py index 300e4a6..539dae2 100755 --- a/libs/gntp/notifier.py +++ b/libs/gntp/notifier.py @@ -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