mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-28 10:09:51 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cdba27504 | ||
|
|
3636e3ce4b | ||
|
|
f98d55a14e | ||
|
|
b645314e50 | ||
|
|
95cae0e6c4 | ||
|
|
1fdf04e9c0 |
@@ -1,5 +1,5 @@
|
||||
*******************************************
|
||||
*** This is SABnzbd 0.6.9 ***
|
||||
*** This is SABnzbd 0.6.10 ***
|
||||
*******************************************
|
||||
SABnzbd is an open-source cross-platform binary newsreader.
|
||||
It simplifies the process of downloading from Usenet dramatically,
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
-------------------------------------------------------------------------------
|
||||
0.6.10RC1 by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
- Allow saving of category paths ending in a *
|
||||
This is will prevent the creation of job folders in the final folder
|
||||
- Fix incompatibility with unrar 4.01 regarding detection of encrypted files
|
||||
- Create .bak (backup) file for sabnzbd.ini before modifying it
|
||||
- OSX: Compatible with Growl 1.2.2 and 1.3
|
||||
- OSX: Prevent changes to SABnzbd.app folder which confused the OSX Firewall
|
||||
- OSX: Fix access rights of SABnzbs.app so that restricted users can run SABnzbd
|
||||
- OSX: Combined SnowLeopard/Lion DMG and separate Leopard DMG
|
||||
-------------------------------------------------------------------------------
|
||||
0.6.9Final by The SABnzbd-Team
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
SABnzbd 0.6.9
|
||||
SABnzbd 0.6.10
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
0) LICENSE
|
||||
|
||||
4
PKG-INFO
4
PKG-INFO
@@ -1,7 +1,7 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: SABnzbd
|
||||
Version: 0.6.9
|
||||
Summary: SABnzbd-0.6.9
|
||||
Version: 0.6.10RC1
|
||||
Summary: SABnzbd-0.6.10RC1
|
||||
Home-page: http://sourceforge.net/projects/sabnzbdplus
|
||||
Author: The SABnzbd Team
|
||||
Author-email: team@sabnzbd.org
|
||||
|
||||
22
README.rtf
22
README.rtf
@@ -4,26 +4,22 @@
|
||||
\paperw11900\paperh16840\vieww16360\viewh15680\viewkind0
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
|
||||
|
||||
\f0\b\fs48 \cf0 SABnzbd 0.6.9\
|
||||
\f0\b\fs48 \cf0 SABnzbd 0.6.10RC1\
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\b0\fs26 \cf0 \
|
||||
|
||||
\b What's new
|
||||
\b0 \
|
||||
- Update Plush to solve minor browser incompatibilities\
|
||||
- On Windows the 64bit versions of par2 and unrar were never used\
|
||||
- Updated unrar to 4.01\
|
||||
- Using the "Download" button in newzbin.com RSS feeds produced malformed names.\
|
||||
- When removing job folders in the "temporary download folder", remove everything.\
|
||||
This is needed because some operating systems add spurious files and folders.\
|
||||
- Generic Sorter failed to uppercase first letter of title when starting with "the/a/to" etc.\
|
||||
- Add "hidden" option allow_64bit_tools (lost when going from 0.5.6 to 0.6.0)\
|
||||
- Allow saving of category paths ending in a *\
|
||||
This is will prevent the creation of job folders in the final folder\
|
||||
- Fix incompatibility with unrar 4.01 regarding detection of encrypted files\
|
||||
- Create .bak (backup) file for sabnzbd.ini before modifying it\
|
||||
- OSX: Compatible with Growl 1.2.2 and 1.3\
|
||||
- OSX: Prevent changes to SABnzbd.app folder which confused the OSX Firewall\
|
||||
- OSX: Fix access rights of SABnzbs.app so that restricted users can run SABnzbd\
|
||||
- OSX: Combined SnowLeopard/Lion DMG and separate Leopard DMG\
|
||||
\
|
||||
- OSX has now a Leopard/SnowLeopard DMG and a Lion-only DMG\
|
||||
You can see the difference in the DMG's background image\
|
||||
\
|
||||
|
||||
\b About
|
||||
\b0 \
|
||||
SABnzbd is an open-source cross-platform binary newsreader.\
|
||||
|
||||
21
README.txt
21
README.txt
@@ -1,17 +1,14 @@
|
||||
************************ SABnzbd 0.6.9 ************************
|
||||
************************ SABnzbd 0.6.10RC1 ************************
|
||||
|
||||
What's new:
|
||||
- Update Plush to solve minor browser incompatibilities
|
||||
- On Windows the 64bit versions of par2 and unrar were never used
|
||||
- Updated unrar to 4.01
|
||||
- Using the "Download" button in newzbin.com RSS feeds produced malformed names.
|
||||
- When removing job folders in the "temporary download folder", remove everything.
|
||||
This is needed because some operating systems add spurious files and folders.
|
||||
- Generic Sorter failed to uppercase first letter of title when starting with "the/a/to" etc.
|
||||
- Add "hidden" option allow_64bit_tools (lost when going from 0.5.6 to 0.6.0)
|
||||
|
||||
- OSX has now a Leopard/SnowLeopard DMG and a Lion-only DMG
|
||||
You can see the difference in the DMG's background image
|
||||
- Allow saving of category paths ending in a *
|
||||
This is will prevent the creation of job folders in the final folder
|
||||
- Fix incompatibility with unrar 4.01 regarding detection of encrypted files
|
||||
- Create .bak (backup) file for sabnzbd.ini before modifying it
|
||||
- OSX: Compatible with Growl 1.2.2 and 1.3
|
||||
- OSX: Prevent changes to SABnzbd.app folder which confused the OSX Firewall
|
||||
- OSX: Fix access rights of SABnzbs.app so that restricted users can run SABnzbd
|
||||
- OSX: Combined SnowLeopard/Lion DMG and separate Leopard DMG
|
||||
|
||||
|
||||
About:
|
||||
|
||||
443
gntp/__init__.py
Normal file
443
gntp/__init__.py
Normal file
@@ -0,0 +1,443 @@
|
||||
import re
|
||||
import hashlib
|
||||
import time
|
||||
import platform
|
||||
|
||||
__version__ = '0.4'
|
||||
|
||||
class BaseError(Exception):
|
||||
pass
|
||||
|
||||
class ParseError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=500,errordesc='Error parsing the message')
|
||||
return error.encode()
|
||||
|
||||
class AuthError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=400,errordesc='Error with authorization')
|
||||
return error.encode()
|
||||
|
||||
class UnsupportedError(BaseError):
|
||||
def gntp_error(self):
|
||||
error = GNTPError(errorcode=500,errordesc='Currently unsupported by gntp.py')
|
||||
return error.encode()
|
||||
|
||||
class _GNTPBase(object):
|
||||
info = {
|
||||
'version':'1.0',
|
||||
'messagetype':None,
|
||||
'encryptionAlgorithmID':None
|
||||
}
|
||||
_requiredHeaders = []
|
||||
headers = {}
|
||||
resources = {}
|
||||
def add_origin_info(self):
|
||||
self.add_header('Origin-Machine-Name',platform.node())
|
||||
self.add_header('Origin-Software-Name','gntp.py')
|
||||
self.add_header('Origin-Software-Version',__version__)
|
||||
self.add_header('Origin-Platform-Name',platform.system())
|
||||
self.add_header('Origin-Platform-Version',platform.platform())
|
||||
def __str__(self):
|
||||
return self.encode()
|
||||
def _parse_info(self,data):
|
||||
'''
|
||||
Parse the first line of a GNTP message to get security and other info values
|
||||
@param data: GNTP Message
|
||||
@return: GNTP Message information in a dictionary
|
||||
'''
|
||||
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
|
||||
match = re.match('GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)'+
|
||||
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?'+
|
||||
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n', data,re.IGNORECASE)
|
||||
|
||||
if not match:
|
||||
raise ParseError('ERROR_PARSING_INFO_LINE')
|
||||
|
||||
info = match.groupdict()
|
||||
if info['encryptionAlgorithmID'] == 'NONE':
|
||||
info['encryptionAlgorithmID'] = None
|
||||
|
||||
return info
|
||||
def set_password(self,password,encryptAlgo='MD5'):
|
||||
'''
|
||||
Set a password for a GNTP Message
|
||||
@param password: Null to clear password
|
||||
@param encryptAlgo: Supports MD5,SHA1,SHA256,SHA512
|
||||
@todo: Support other hash functions
|
||||
'''
|
||||
hash = {
|
||||
'MD5': hashlib.md5,
|
||||
'SHA1': hashlib.sha1,
|
||||
'SHA256': hashlib.sha256,
|
||||
'SHA512': hashlib.sha512,
|
||||
}
|
||||
|
||||
self.password = password
|
||||
self.encryptAlgo = encryptAlgo.upper()
|
||||
if not password:
|
||||
self.info['encryptionAlgorithmID'] = None
|
||||
self.info['keyHashAlgorithm'] = None;
|
||||
return
|
||||
if not self.encryptAlgo in hash.keys():
|
||||
raise UnsupportedError('INVALID HASH "%s"'%self.encryptAlgo)
|
||||
|
||||
hashfunction = hash.get(self.encryptAlgo)
|
||||
|
||||
password = password.encode('utf8')
|
||||
seed = time.ctime()
|
||||
salt = hashfunction(seed).hexdigest()
|
||||
saltHash = hashfunction(seed).digest()
|
||||
keyBasis = password+saltHash
|
||||
key = hashfunction(keyBasis).digest()
|
||||
keyHash = hashfunction(key).hexdigest()
|
||||
|
||||
self.info['keyHashAlgorithmID'] = self.encryptAlgo
|
||||
self.info['keyHash'] = keyHash.upper()
|
||||
self.info['salt'] = salt.upper()
|
||||
def _decode_hex(self,value):
|
||||
'''
|
||||
Helper function to decode hex string to `proper` hex string
|
||||
@param value: Value to decode
|
||||
@return: Hex string
|
||||
'''
|
||||
result = ''
|
||||
for i in range(0,len(value),2):
|
||||
tmp = int(value[i:i+2],16)
|
||||
result += chr(tmp)
|
||||
return result
|
||||
def _decode_binary(self,rawIdentifier,identifier):
|
||||
rawIdentifier += '\r\n\r\n'
|
||||
dataLength = int(identifier['Length'])
|
||||
pointerStart = self.raw.find(rawIdentifier)+len(rawIdentifier)
|
||||
pointerEnd = pointerStart + dataLength
|
||||
data = self.raw[pointerStart:pointerEnd]
|
||||
if not len(data) == dataLength:
|
||||
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s'%(dataLength,len(data)))
|
||||
return data
|
||||
def _validate_password(self,password):
|
||||
'''
|
||||
Validate GNTP Message against stored password
|
||||
'''
|
||||
self.password = password
|
||||
if password == None: raise Exception()
|
||||
keyHash = self.info.get('keyHash',None)
|
||||
if keyHash is None and self.password is None:
|
||||
return True
|
||||
if keyHash is None:
|
||||
raise AuthError('Invalid keyHash')
|
||||
if self.password is None:
|
||||
raise AuthError('Missing password')
|
||||
|
||||
password = self.password.encode('utf8')
|
||||
saltHash = self._decode_hex(self.info['salt'])
|
||||
|
||||
keyBasis = password+saltHash
|
||||
key = hashlib.md5(keyBasis).digest()
|
||||
keyHash = hashlib.md5(key).hexdigest()
|
||||
|
||||
if not keyHash.upper() == self.info['keyHash'].upper():
|
||||
raise AuthError('Invalid Hash')
|
||||
return True
|
||||
def validate(self):
|
||||
'''
|
||||
Verify required headers
|
||||
'''
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header,False):
|
||||
raise ParseError('Missing Notification Header: '+header)
|
||||
|
||||
def _format_info(self):
|
||||
'''
|
||||
Generate info line for GNTP Message
|
||||
@return: Info line string
|
||||
'''
|
||||
info = u'GNTP/%s %s'%(
|
||||
self.info.get('version'),
|
||||
self.info.get('messagetype'),
|
||||
)
|
||||
if self.info.get('encryptionAlgorithmID',None):
|
||||
info += ' %s:%s'%(
|
||||
self.info.get('encryptionAlgorithmID'),
|
||||
self.info.get('ivValue'),
|
||||
)
|
||||
else:
|
||||
info+=' NONE'
|
||||
|
||||
if self.info.get('keyHashAlgorithmID',None):
|
||||
info += ' %s:%s.%s'%(
|
||||
self.info.get('keyHashAlgorithmID'),
|
||||
self.info.get('keyHash'),
|
||||
self.info.get('salt')
|
||||
)
|
||||
|
||||
return info
|
||||
def _parse_dict(self,data):
|
||||
'''
|
||||
Helper function to parse blocks of GNTP headers into a dictionary
|
||||
@param data:
|
||||
@return: Dictionary of headers
|
||||
'''
|
||||
dict = {}
|
||||
for line in data.split('\r\n'):
|
||||
match = re.match('([\w-]+):(.+)', line)
|
||||
if not match: continue
|
||||
|
||||
key = match.group(1).strip()
|
||||
val = match.group(2).strip()
|
||||
dict[key] = val
|
||||
return dict
|
||||
def add_header(self,key,value):
|
||||
if isinstance(value, unicode):
|
||||
self.headers[key] = value
|
||||
else:
|
||||
self.headers[key] = unicode('%s'%value,'utf8','replace')
|
||||
def decode(self,data,password=None):
|
||||
'''
|
||||
Decode GNTP Message
|
||||
@param data:
|
||||
'''
|
||||
self.password = password
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Message
|
||||
@return: GNTP Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message.encode('utf8')
|
||||
class GNTPRegister(_GNTPBase):
|
||||
'''
|
||||
GNTP Registration Message
|
||||
'''
|
||||
notifications = []
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notifications-Count'
|
||||
]
|
||||
_requiredNotificationHeaders = ['Notification-Name']
|
||||
def __init__(self,data=None,password=None):
|
||||
'''
|
||||
@param data: (Optional) See decode()
|
||||
@param password: (Optional) Password to use while encoding/decoding messages
|
||||
'''
|
||||
self.info['messagetype'] = 'REGISTER'
|
||||
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_header('Application-Name', 'pygntp')
|
||||
self.add_header('Notifications-Count', 0)
|
||||
self.add_origin_info()
|
||||
def validate(self):
|
||||
'''
|
||||
Validate required headers and validate notification headers
|
||||
'''
|
||||
for header in self._requiredHeaders:
|
||||
if not self.headers.get(header,False):
|
||||
raise ParseError('Missing Registration Header: '+header)
|
||||
for notice in self.notifications:
|
||||
for header in self._requiredNotificationHeaders:
|
||||
if not notice.get(header,False):
|
||||
raise ParseError('Missing Notification Header: '+header)
|
||||
def decode(self,data,password):
|
||||
'''
|
||||
Decode existing GNTP Registration message
|
||||
@param data: Message to decode.
|
||||
'''
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i,part in enumerate(parts):
|
||||
if i==0: continue #Skip Header
|
||||
if part.strip()=='': continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Notification-Name',False):
|
||||
self.notifications.append(notice)
|
||||
elif notice.get('Identifier',False):
|
||||
notice['Data'] = self._decode_binary(part,notice)
|
||||
#open('register.png','wblol').write(notice['Data'])
|
||||
self.resources[ notice.get('Identifier') ] = notice
|
||||
|
||||
def add_notification(self,name,enabled=True):
|
||||
'''
|
||||
Add new Notification to Registration message
|
||||
@param name: Notification Name
|
||||
@param enabled: Default Notification to Enabled
|
||||
'''
|
||||
notice = {}
|
||||
notice['Notification-Name'] = u'%s'%name
|
||||
notice['Notification-Enabled'] = u'%s'%enabled
|
||||
|
||||
self.notifications.append(notice)
|
||||
self.add_header('Notifications-Count', len(self.notifications))
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Registration Message
|
||||
@return: GNTP Registration Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
#Notifications
|
||||
if len(self.notifications)>0:
|
||||
for notice in self.notifications:
|
||||
message += EOL
|
||||
for k,v in notice.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message
|
||||
|
||||
class GNTPNotice(_GNTPBase):
|
||||
'''
|
||||
GNTP Notification Message
|
||||
'''
|
||||
_requiredHeaders = [
|
||||
'Application-Name',
|
||||
'Notification-Name',
|
||||
'Notification-Title'
|
||||
]
|
||||
def __init__(self,data=None,app=None,name=None,title=None,password=None):
|
||||
'''
|
||||
|
||||
@param data: (Optional) See decode()
|
||||
@param app: (Optional) Set Application-Name
|
||||
@param name: (Optional) Set Notification-Name
|
||||
@param title: (Optional) Set Notification Title
|
||||
@param password: (Optional) Password to use while encoding/decoding messages
|
||||
'''
|
||||
self.info['messagetype'] = 'NOTIFY'
|
||||
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
if app:
|
||||
self.add_header('Application-Name', app)
|
||||
if name:
|
||||
self.add_header('Notification-Name', name)
|
||||
if title:
|
||||
self.add_header('Notification-Title', title)
|
||||
self.add_origin_info()
|
||||
def decode(self,data,password):
|
||||
'''
|
||||
Decode existing GNTP Notification message
|
||||
@param data: Message to decode.
|
||||
'''
|
||||
self.raw = data
|
||||
parts = self.raw.split('\r\n\r\n')
|
||||
self.info = self._parse_info(data)
|
||||
self._validate_password(password)
|
||||
self.headers = self._parse_dict(parts[0])
|
||||
|
||||
for i,part in enumerate(parts):
|
||||
if i==0: continue #Skip Header
|
||||
if part.strip()=='': continue
|
||||
notice = self._parse_dict(part)
|
||||
if notice.get('Identifier',False):
|
||||
notice['Data'] = self._decode_binary(part,notice)
|
||||
#open('notice.png','wblol').write(notice['Data'])
|
||||
self.resources[ notice.get('Identifier') ] = notice
|
||||
def encode(self):
|
||||
'''
|
||||
Encode a GNTP Notification Message
|
||||
@return: GNTP Notification Message ready to be sent
|
||||
'''
|
||||
self.validate()
|
||||
EOL = u'\r\n'
|
||||
|
||||
message = self._format_info() + EOL
|
||||
#Headers
|
||||
for k,v in self.headers.iteritems():
|
||||
message += u'%s: %s%s'%(k,v,EOL)
|
||||
|
||||
message += EOL
|
||||
return message.encode('utf8')
|
||||
|
||||
class GNTPSubscribe(_GNTPBase):
|
||||
def __init__(self,data=None,password=None):
|
||||
self.info['messagetype'] = 'SUBSCRIBE'
|
||||
self._requiredHeaders = [
|
||||
'Subscriber-ID',
|
||||
'Subscriber-Name',
|
||||
]
|
||||
if data:
|
||||
self.decode(data,password)
|
||||
else:
|
||||
self.set_password(password)
|
||||
self.add_origin_info()
|
||||
|
||||
class GNTPOK(_GNTPBase):
|
||||
_requiredHeaders = ['Response-Action']
|
||||
def __init__(self,data=None,action=None):
|
||||
'''
|
||||
@param data: (Optional) See _GNTPResponse.decode()
|
||||
@param action: (Optional) Set type of action the OK Response is for
|
||||
'''
|
||||
self.info['messagetype'] = '-OK'
|
||||
if data:
|
||||
self.decode(data)
|
||||
if action:
|
||||
self.add_header('Response-Action', action)
|
||||
self.add_origin_info()
|
||||
|
||||
class GNTPError(_GNTPBase):
|
||||
_requiredHeaders = ['Error-Code','Error-Description']
|
||||
def __init__(self,data=None,errorcode=None,errordesc=None):
|
||||
'''
|
||||
@param data: (Optional) See _GNTPResponse.decode()
|
||||
@param errorcode: (Optional) Error code
|
||||
@param errordesc: (Optional) Error Description
|
||||
'''
|
||||
self.info['messagetype'] = '-ERROR'
|
||||
if data:
|
||||
self.decode(data)
|
||||
if errorcode:
|
||||
self.add_header('Error-Code', errorcode)
|
||||
self.add_header('Error-Description', errordesc)
|
||||
self.add_origin_info()
|
||||
def error(self):
|
||||
return self.headers['Error-Code'],self.headers['Error-Description']
|
||||
|
||||
def parse_gntp(data,password=None):
|
||||
'''
|
||||
Attempt to parse a message as a GNTP message
|
||||
@param data: Message to be parsed
|
||||
@param password: Optional password to be used to verify the message
|
||||
'''
|
||||
match = re.match('GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',data,re.IGNORECASE)
|
||||
if not match:
|
||||
raise ParseError('INVALID_GNTP_INFO')
|
||||
info = match.groupdict()
|
||||
if info['messagetype'] == 'REGISTER':
|
||||
return GNTPRegister(data,password=password)
|
||||
elif info['messagetype'] == 'NOTIFY':
|
||||
return GNTPNotice(data,password=password)
|
||||
elif info['messagetype'] == 'SUBSCRIBE':
|
||||
return GNTPSubscribe(data,password=password)
|
||||
elif info['messagetype'] == '-OK':
|
||||
return GNTPOK(data)
|
||||
elif info['messagetype'] == '-ERROR':
|
||||
return GNTPError(data)
|
||||
raise ParseError('INVALID_GNTP_MESSAGE')
|
||||
135
gntp/notifier.py
Normal file
135
gntp/notifier.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
A Python module that uses GNTP to post messages
|
||||
Mostly mirrors the Growl.py file that comes with Mac Growl
|
||||
http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py
|
||||
"""
|
||||
import gntp
|
||||
import socket
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GrowlNotifier(object):
|
||||
applicationName = 'Python GNTP'
|
||||
notifications = []
|
||||
defaultNotifications = []
|
||||
applicationIcon = None
|
||||
passwordHash = 'MD5'
|
||||
|
||||
#GNTP Specific
|
||||
password = None
|
||||
hostname = 'localhost'
|
||||
port = 23053
|
||||
|
||||
def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None, port=None):
|
||||
if applicationName:
|
||||
self.applicationName = applicationName
|
||||
assert self.applicationName, 'An application name is required.'
|
||||
|
||||
if notifications:
|
||||
self.notifications = list(notifications)
|
||||
assert self.notifications, 'A sequence of one or more notification names is required.'
|
||||
|
||||
if defaultNotifications is not None:
|
||||
self.defaultNotifications = list(defaultNotifications)
|
||||
elif not self.defaultNotifications:
|
||||
self.defaultNotifications = list(self.notifications)
|
||||
|
||||
if applicationIcon is not None:
|
||||
self.applicationIcon = self._checkIcon(applicationIcon)
|
||||
elif self.applicationIcon is not None:
|
||||
self.applicationIcon = self._checkIcon(self.applicationIcon)
|
||||
|
||||
#GNTP Specific
|
||||
if password:
|
||||
self.password = password
|
||||
|
||||
if hostname:
|
||||
self.hostname = hostname
|
||||
assert self.hostname, 'Requires valid hostname'
|
||||
|
||||
if port:
|
||||
self.port = int(port)
|
||||
assert isinstance(self.port,int), 'Requires valid port'
|
||||
|
||||
def _checkIcon(self, data):
|
||||
'''
|
||||
Check the icon to see if it's valid
|
||||
@param data:
|
||||
@todo Consider checking for a valid URL
|
||||
'''
|
||||
return data
|
||||
|
||||
def register(self):
|
||||
'''
|
||||
Send GNTP Registration
|
||||
'''
|
||||
logger.info('Sending registration to %s:%s',self.hostname,self.port)
|
||||
register = gntp.GNTPRegister()
|
||||
register.add_header('Application-Name',self.applicationName)
|
||||
for notification in self.notifications:
|
||||
enabled = notification in self.defaultNotifications
|
||||
register.add_notification(notification,enabled)
|
||||
if self.applicationIcon:
|
||||
register.add_header('Application-Icon',self.applicationIcon)
|
||||
if self.password:
|
||||
register.set_password(self.password,self.passwordHash)
|
||||
response = self.send('register',register.encode())
|
||||
if isinstance(response,gntp.GNTPOK): return True
|
||||
logger.debug('Invalid response %s',response.error())
|
||||
return response.error()
|
||||
|
||||
def notify(self, noteType, title, description, icon=None, sticky=False, priority=None):
|
||||
'''
|
||||
Send a GNTP notifications
|
||||
'''
|
||||
logger.info('Sending notification [%s] to %s:%s',noteType,self.hostname,self.port)
|
||||
assert noteType in self.notifications
|
||||
notice = gntp.GNTPNotice()
|
||||
notice.add_header('Application-Name',self.applicationName)
|
||||
notice.add_header('Notification-Name',noteType)
|
||||
notice.add_header('Notification-Title',title)
|
||||
if self.password:
|
||||
notice.set_password(self.password,self.passwordHash)
|
||||
if sticky:
|
||||
notice.add_header('Notification-Sticky',sticky)
|
||||
if priority:
|
||||
notice.add_header('Notification-Priority',priority)
|
||||
if icon:
|
||||
notice.add_header('Notification-Icon',self._checkIcon(icon))
|
||||
if description:
|
||||
notice.add_header('Notification-Text',description)
|
||||
response = self.send('notify',notice.encode())
|
||||
if isinstance(response,gntp.GNTPOK): return True
|
||||
logger.debug('Invalid response %s',response.error())
|
||||
return response.error()
|
||||
def subscribe(self,id,name,port):
|
||||
sub = gntp.GNTPSubscribe()
|
||||
sub.add_header('Subscriber-ID',id)
|
||||
sub.add_header('Subscriber-Name',name)
|
||||
sub.add_header('Subscriber-Port',port)
|
||||
if self.password:
|
||||
sub.set_password(self.password,self.passwordHash)
|
||||
response = self.send('subscribe',sub.encode())
|
||||
if isinstance(response,gntp.GNTPOK): return True
|
||||
logger.debug('Invalid response %s',response.error())
|
||||
return response.error()
|
||||
def send(self,type,data):
|
||||
'''
|
||||
Send the GNTP Packet
|
||||
'''
|
||||
#logger.debug('To : %s:%s <%s>\n%s',self.hostname,self.port,type,data)
|
||||
#Less verbose please
|
||||
logger.debug('To : %s:%s <%s>',self.hostname,self.port,type)
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(1)
|
||||
s.connect((self.hostname,self.port))
|
||||
s.send(data.encode('utf-8', 'replace'))
|
||||
response = gntp.parse_gntp(s.recv(1024))
|
||||
s.close()
|
||||
|
||||
#logger.debug('From : %s:%s <%s>\n%s',self.hostname,self.port,response.__class__,response)
|
||||
#Less verbose please
|
||||
logger.debug('From : %s:%s <%s>',self.hostname,self.port,response.__class__)
|
||||
return response
|
||||
@@ -129,6 +129,9 @@
|
||||
$T('explain-ampm')<br>
|
||||
<br/>
|
||||
<!--#end if#-->
|
||||
<label><input type="checkbox" name="growl_enable" value="1" <!--#if $growl_enable > 0 then "checked=1" else ""#--> /> <strong>$T('opt-growl_enable')</strong></label><br>
|
||||
$T('explain-growl_enable')<br>
|
||||
<br/>
|
||||
<strong>$T('opt-ignore_samples'):</strong><br>
|
||||
$T('explain-ignore_samples')<br>
|
||||
<input class="radio" type="radio" name="ignore_samples" value="0" <!--#if $ignore_samples == 0 then 'checked="1"' else ""#--> /> $T('igsam-off')
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
</label>
|
||||
</div>
|
||||
<!--#end if#-->
|
||||
<div class="field-pair">
|
||||
<input type="checkbox" name="growl_enable" id="growl_enable" value="1" <!--#if $growl_enable > 0 then "checked=1" else ""#--> />
|
||||
<label class="clearfix" for="growl_enable">
|
||||
<span class="component-title">$T('opt-growl_enable')</span>
|
||||
<span class="component-desc">$T('explain-growl_enable')</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div><!-- /component-group1 -->
|
||||
|
||||
|
||||
@@ -175,6 +175,12 @@
|
||||
<br class="clear" />
|
||||
<!--#end if#-->
|
||||
|
||||
<label><span class="label">$T('opt-growl_enable'):</span>
|
||||
<input class="radio" type="checkbox" name="growl_enable" value="1" <!--#if $growl_enable > 0 then 'checked="1"' else ""#--> /></label>
|
||||
<span class="tips">$T('explain-growl_enable')</span>
|
||||
<br class="clear" />
|
||||
|
||||
|
||||
<span class="label">$T('opt-ignore_samples'):</span>
|
||||
<input class="radio" type="radio" name="ignore_samples" value="0" <!--#if $ignore_samples == 0 then 'checked="1"' else ""#--> /> $T('igsam-off')
|
||||
<input class="radio" type="radio" name="ignore_samples" value="1" <!--#if $ignore_samples == 1 then 'checked="1"' else ""#--> /> $T('igsam-del')
|
||||
|
||||
29
licenses/License-gntp.txt
Normal file
29
licenses/License-gntp.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
The module gntp is (C) Paul Traylor
|
||||
|
||||
Home of the module:
|
||||
https://github.com/kfdm/gntp/
|
||||
|
||||
It is covered by the following license.
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
Copyright (c) 2011 Paul Traylor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
-------------------------------------------------------------------------
|
||||
BIN
osx/image/sabnzbd.png
Normal file
BIN
osx/image/sabnzbd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB |
16
package.py
16
package.py
@@ -382,16 +382,18 @@ if target == 'app':
|
||||
# Select OSX version specific background image
|
||||
# Take care to preserve the special attributes of the background image file
|
||||
if [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 7, 0]:
|
||||
# Lion and higher
|
||||
f = open('osx/image/sabnzbd_lion.png', 'rb')
|
||||
# Lion and higher: generates SnowLeopard/Lion DMG
|
||||
f = open('osx/image/sabnzbd.png', 'rb')
|
||||
png = f.read()
|
||||
f.close()
|
||||
f = open('/Volumes/SABnzbd/sabnzbd.png', 'wb')
|
||||
f.write(png)
|
||||
f.close()
|
||||
else:
|
||||
# Snow Leopard and lower
|
||||
pass
|
||||
# Snow Leopard and lower: generates Leopard DMG
|
||||
f = open('osx/image/sabnzbd_leopard.png', 'rb')
|
||||
png = f.read()
|
||||
f.close()
|
||||
f = open('/Volumes/SABnzbd/sabnzbd.png', 'wb')
|
||||
f.write(png)
|
||||
f.close()
|
||||
|
||||
# Rename the volume
|
||||
fp = open('mount.log', 'r')
|
||||
|
||||
@@ -204,6 +204,8 @@ ssl_type = OptionStr('misc', 'ssl_type', 'v23')
|
||||
unpack_check = OptionBool('misc', 'unpack_check', True)
|
||||
no_penalties = OptionBool('misc', 'no_penalties', False)
|
||||
|
||||
growl_enable = OptionBool('growl', 'growl_enable', True)
|
||||
|
||||
# Internal options, not saved in INI file
|
||||
debug_delay = OptionNumber('misc', 'debug_delay', 0, add=False)
|
||||
|
||||
|
||||
@@ -725,28 +725,42 @@ def save_config(force=False):
|
||||
|
||||
filename = CFG.filename
|
||||
try:
|
||||
# Check if file is writable
|
||||
if not sabnzbd.misc.is_writable(filename):
|
||||
logging.error(Ta('Cannot write to INI file %s'), filename)
|
||||
modified = False
|
||||
return False
|
||||
|
||||
# Read current content
|
||||
f = open(CFG.filename)
|
||||
f = open(filename)
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
# Write to temp file
|
||||
CFG.filename = filename + '.tmp'
|
||||
f = open(CFG.filename, 'w')
|
||||
tmpname = filename + '.tmp'
|
||||
bakname = filename + '.bak'
|
||||
|
||||
# Write new file
|
||||
f = open(tmpname, 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
# Update temp file content
|
||||
CFG.filename = tmpname
|
||||
CFG.write()
|
||||
|
||||
# Rename to backup
|
||||
if os.path.isfile(bakname):
|
||||
os.remove(bakname)
|
||||
os.rename(filename, bakname)
|
||||
|
||||
# Rename temp file, overwriting old one
|
||||
os.remove(filename)
|
||||
os.rename(CFG.filename, filename)
|
||||
os.rename(tmpname, filename)
|
||||
|
||||
modified = False
|
||||
res = True
|
||||
except:
|
||||
logging.error(Ta('Cannot create temp file for %s'), CFG.filename)
|
||||
logging.error(Ta('Cannot create backup file for %s'), filename)
|
||||
logging.info("Traceback: ", exc_info = True)
|
||||
res = False
|
||||
CFG.filename = filename
|
||||
return res
|
||||
|
||||
@@ -1129,7 +1129,7 @@ SWITCH_LIST = \
|
||||
'safe_postproc', 'no_dupes', 'replace_spaces', 'replace_dots', 'replace_illegal', 'auto_browser',
|
||||
'ignore_samples', 'pause_on_post_processing', 'quick_check', 'nice', 'ionice',
|
||||
'ssl_type', 'pre_script', 'pause_on_pwrar', 'ampm', 'sfv_check', 'folder_rename',
|
||||
'unpack_check'
|
||||
'unpack_check', 'growl_enable'
|
||||
)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
@@ -1151,7 +1151,10 @@ class ConfigSwitches(object):
|
||||
conf['have_ionice'] = bool(sabnzbd.newsunpack.IONICE_COMMAND)
|
||||
|
||||
for kw in SWITCH_LIST:
|
||||
conf[kw] = config.get_config('misc', kw)()
|
||||
if kw == 'growl_enable':
|
||||
conf[kw] = config.get_config('growl', kw)()
|
||||
else:
|
||||
conf[kw] = config.get_config('misc', kw)()
|
||||
|
||||
conf['script_list'] = list_scripts() or ['None']
|
||||
conf['have_ampm'] = HAVE_AMPM
|
||||
@@ -1166,7 +1169,10 @@ class ConfigSwitches(object):
|
||||
if msg: return msg
|
||||
|
||||
for kw in SWITCH_LIST:
|
||||
item = config.get_config('misc', kw)
|
||||
if kw == 'growl_enable':
|
||||
item = config.get_config('growl', kw)
|
||||
else:
|
||||
item = config.get_config('misc', kw)
|
||||
value = platform_encode(kwargs.get(kw))
|
||||
msg = item.set(value)
|
||||
if msg:
|
||||
|
||||
@@ -30,6 +30,7 @@ import subprocess
|
||||
import socket
|
||||
import time
|
||||
import glob
|
||||
import stat
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.decorators import synchronized
|
||||
@@ -1166,3 +1167,10 @@ def remove_all(path, pattern='*', keep_folder=False, recursive=False):
|
||||
except:
|
||||
logging.info('Cannot remove folder %s', path)
|
||||
|
||||
|
||||
def is_writable(path):
|
||||
""" Return True is file is writable (also when non-existent) """
|
||||
if os.path.isfile(path):
|
||||
return bool(os.stat(path).st_mode & stat.S_IWUSR)
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -588,8 +588,16 @@ def rar_extract_core(rarfile, numrars, one_folder, nzo, setname, extraction_path
|
||||
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
|
||||
fail = 1
|
||||
|
||||
elif line.startswith('Encrypted file: CRC failed'):
|
||||
filename = TRANS(line[31:-23].strip())
|
||||
elif 'ncrypted file' in line and 'CRC failed' in line:
|
||||
# unrar 4.x syntax
|
||||
m = re.search('encrypted file (.+)\. Corrupt file', line)
|
||||
if not m:
|
||||
# unrar 3.x syntax
|
||||
m = re.search('Encrypted file: CRC failed in (.+) \(password', line)
|
||||
if m:
|
||||
filename = TRANS(m.group(1)).strip()
|
||||
else:
|
||||
filename = '???'
|
||||
nzo.fail_msg = T('Unpacking failed, archive requires a password')
|
||||
msg = ('[%s][%s] '+Ta('Unpacking failed, archive requires a password')) % (setname, latin1(filename))
|
||||
nzo.set_unpack_info('Unpack', unicoder(msg), set=setname)
|
||||
|
||||
@@ -15,51 +15,154 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
#"""
|
||||
#TO FIX : Translations are not working with this implementation
|
||||
# Growl Registration may only be done once per run ?
|
||||
# Registration is made too early, the language module has not read the text file yet
|
||||
#NOTIFICATION = {'startup':'grwl-notif-startup','download':'grwl-notif-dl','pp':'grwl-notif-pp','other':'grwl-notif-other'}
|
||||
NOTIFICATION = {'startup':'1. On Startup/Shutdown','download':'2. On adding NZB','pp':'3. On post-processing','complete':'4. On download terminated','other':'5. Other Messages'}
|
||||
"""
|
||||
sabnzbd.growler - Send notifications to Growl
|
||||
"""
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# For a future release, make texts translatable.
|
||||
if 0:
|
||||
#------------------------------------------------------------------------------
|
||||
# Define translatable message table
|
||||
TT = lambda x:x
|
||||
_NOTIFICATION = {
|
||||
'startup' : TT('Startup/Shutdown'), #: Message class for Growl server
|
||||
'download' : TT('Added NZB'), #: Message class for Growl server
|
||||
'pp' : TT('Post-processing started'), #: Message class for Growl server
|
||||
'complete' : TT('Job finished'), #: Message class for Growl server
|
||||
'other' : TT('Other Messages') #: Message class for Growl server
|
||||
}
|
||||
import os.path
|
||||
import logging
|
||||
import socket
|
||||
|
||||
import sabnzbd
|
||||
from sabnzbd.encoding import unicoder, latin1
|
||||
import gntp
|
||||
import gntp.notifier
|
||||
|
||||
try:
|
||||
import Growl
|
||||
import os.path
|
||||
import logging
|
||||
|
||||
if os.path.isfile('sabnzbdplus.icns'):
|
||||
nIcon = Growl.Image.imageFromPath('sabnzbdplus.icns')
|
||||
elif os.path.isfile('osx/resources/sabnzbdplus.icns'):
|
||||
nIcon = Growl.Image.imageFromPath('osx/resources/sabnzbdplus.icns')
|
||||
import platform
|
||||
# If running on OSX-Lion and classic Growl (older than 1.3) is absent, assume GNTP-only
|
||||
if [int(n) for n in platform.mac_ver()[0].split('.')] >= [10, 7, 0]:
|
||||
_HAVE_OSX_GROWL = os.path.isfile('/Library/PreferencePanes/Growl.prefPane/Contents/MacOS/Growl')
|
||||
else:
|
||||
nIcon = Growl.Image.imageWithIconForApplication('Terminal')
|
||||
|
||||
def sendGrowlMsg(nTitle , nMsg, nType=NOTIFICATION['other']):
|
||||
gnotifier = SABGrowlNotifier(applicationIcon=nIcon)
|
||||
gnotifier.register()
|
||||
#TO FIX
|
||||
#gnotifier.notify(T(nType), nTitle, nMsg)
|
||||
gnotifier.notify(nType, nTitle, nMsg)
|
||||
|
||||
class SABGrowlNotifier(Growl.GrowlNotifier):
|
||||
applicationName = "SABnzbd"
|
||||
#TO FIX
|
||||
#notifications = [T(notification) for notification in NOTIFICATION.values()]
|
||||
notifications = NOTIFICATION.values()
|
||||
|
||||
_HAVE_OSX_GROWL = True
|
||||
except ImportError:
|
||||
def sendGrowlMsg(nTitle , nMsg, nType):
|
||||
pass
|
||||
_HAVE_OSX_GROWL = False
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Define translatable message table
|
||||
NOTIFICATION = {'startup':'1. On Startup/Shutdown','download':'2. On adding NZB','pp':'3. On post-processing','complete':'4. On download terminated','other':'5. Other Messages'}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Setup platform dependent Growl support
|
||||
#
|
||||
_GROWL_ICON = None # Platform-dependant icon path
|
||||
_GROWL = None # Instance of the Notifier after registration
|
||||
_GROWL_REG = False # Succesful registration
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def get_icon():
|
||||
icon = os.path.join(sabnzbd.DIR_PROG, 'sabnzbd.ico')
|
||||
if not os.path.isfile(icon):
|
||||
icon = None
|
||||
return icon
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def register_growl():
|
||||
""" Register this app with Growl
|
||||
"""
|
||||
error = None
|
||||
|
||||
# Clean up persistent data in GNTP to make re-registration work
|
||||
gntp.GNTPRegister.notifications = []
|
||||
gntp.GNTPRegister.headers = {}
|
||||
|
||||
growler = gntp.notifier.GrowlNotifier(
|
||||
applicationName = 'SABnzbd',
|
||||
applicationIcon = get_icon(),
|
||||
notifications = sorted(NOTIFICATION.values()),
|
||||
hostname = None,
|
||||
port = 23053,
|
||||
password = None
|
||||
)
|
||||
|
||||
try:
|
||||
ret = growler.register()
|
||||
if ret is None or isinstance(ret, bool):
|
||||
logging.info('Registered with Growl')
|
||||
ret = growler
|
||||
else:
|
||||
error = 'Cannot register with Growl %s' % ret
|
||||
logging.debug(error)
|
||||
del growler
|
||||
ret = None
|
||||
except socket.error, err:
|
||||
error = 'Cannot register with Growl %s' % err
|
||||
logging.debug(error)
|
||||
del growler
|
||||
ret = None
|
||||
return ret, error
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
def sendGrowlMsg(title , msg, gtype):
|
||||
""" Send Growl message
|
||||
"""
|
||||
global _GROWL, _GROWL_REG
|
||||
|
||||
if not sabnzbd.cfg.growl_enable() or not sabnzbd.DARWIN:
|
||||
return
|
||||
|
||||
if _HAVE_OSX_GROWL:
|
||||
res = send_local_growl(title, msg, gtype)
|
||||
return res
|
||||
|
||||
for n in (0, 1):
|
||||
if not _GROWL_REG: _GROWL = None
|
||||
if not _GROWL:
|
||||
_GROWL, error = register_growl()
|
||||
if _GROWL:
|
||||
assert isinstance(_GROWL, gntp.notifier.GrowlNotifier)
|
||||
_GROWL_REG = True
|
||||
#logging.debug('Send to Growl: %s %s %s', gtype, latin1(title), latin1(msg))
|
||||
try:
|
||||
ret = _GROWL.notify(
|
||||
noteType = gtype,
|
||||
title = title,
|
||||
description = unicoder(msg),
|
||||
#icon = options.icon,
|
||||
#sticky = options.sticky,
|
||||
#priority = options.priority
|
||||
)
|
||||
if ret is None or isinstance(ret, bool):
|
||||
return None
|
||||
elif ret[0] == '401':
|
||||
_GROWL = False
|
||||
else:
|
||||
logging.debug('Growl error %s', ret)
|
||||
return 'Growl error %s', ret
|
||||
except socket.error, err:
|
||||
logging.debug('Growl error %s', err)
|
||||
return 'Growl error %s', err
|
||||
else:
|
||||
return error
|
||||
return None
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Local OSX Growl support
|
||||
#
|
||||
if _HAVE_OSX_GROWL:
|
||||
_local_growl = None
|
||||
if os.path.isfile('sabnzbdplus.icns'):
|
||||
_OSX_ICON = Growl.Image.imageFromPath('sabnzbdplus.icns')
|
||||
elif os.path.isfile('osx/resources/sabnzbdplus.icns'):
|
||||
_OSX_ICON = Growl.Image.imageFromPath('osx/resources/sabnzbdplus.icns')
|
||||
else:
|
||||
_OSX_ICON = Growl.Image.imageWithIconForApplication('Terminal')
|
||||
|
||||
def send_local_growl(title , msg, gtype):
|
||||
""" Send to local Growl server, OSX-only """
|
||||
global _local_growl
|
||||
if not _local_growl:
|
||||
notes = sorted(NOTIFICATION.values())
|
||||
_local_growl = Growl.GrowlNotifier(
|
||||
applicationName = 'SABnzbd',
|
||||
applicationIcon = _OSX_ICON,
|
||||
notifications = notes,
|
||||
defaultNotifications = notes
|
||||
)
|
||||
_local_growl.register()
|
||||
_local_growl.notify(gtype, title, msg)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user