Browse Source

Add universal Growl support.

- Windows: through GNTP library
- OSX: through Growl library for local server and GTNP for remote server
- Unix/Linux: through GNTP library
For Ubuntu there's support for NotifyOSD.
Made Growl class texts translatable.
pull/7/head
ShyPike 14 years ago
parent
commit
53aed799a1
  1. 16
      INSTALL.txt
  2. 8
      SABnzbd.py
  3. 443
      gntp/__init__.py
  4. 130
      gntp/notifier.py
  5. 30
      interfaces/Classic/templates/config_email.tmpl
  6. 50
      interfaces/Plush/templates/config_email.tmpl
  7. 2
      interfaces/Plush/templates/config_switches.tmpl
  8. 33
      interfaces/smpl/templates/config_email.tmpl
  9. 10
      interfaces/smpl/templates/main.tmpl
  10. 1
      package.py
  11. 2
      sabnzbd/__init__.py
  12. 5
      sabnzbd/cfg.py
  13. 6
      sabnzbd/downloader.py
  14. 223
      sabnzbd/growler.py
  15. 19
      sabnzbd/interface.py
  16. 11
      sabnzbd/misc.py
  17. 4
      sabnzbd/newzbin.py
  18. 4
      sabnzbd/nzbqueue.py
  19. 6
      sabnzbd/osxmenu.py
  20. 10
      sabnzbd/postproc.py
  21. 3
      sabnzbd/skintext.py
  22. 65
      sabnzbd/utils/osx.py

16
INSTALL.txt

@ -39,9 +39,14 @@ Start the SABnzbd.exe program.
Within 5-10 seconds your web browser will start and show the user interface. Within 5-10 seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki. Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
3) INSTALL pre-built OSX binaries
-------------------------------------------------------------------------------
Download teh DMG file, mount and drag the SABnzbd icon to Programs.
Just like you do with so many apps.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
3) INSTALL with only sources 4) INSTALL with only sources
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
You need to have Python installed and some modules. You need to have Python installed and some modules.
@ -55,7 +60,7 @@ Windows
Python-2.7.latest Python-2.7.latest
Essential modules Essential modules
cheetah-2.0.1+ http://www.cheetahtemplate.org/ cheetah-2.0.1+ http://www.cheetahtemplate.org/ (or use "pypm install cheetah")
yenc module >= 0.3 http://sabnzbd.sourceforge.net/yenc-0.3.tar.gz yenc module >= 0.3 http://sabnzbd.sourceforge.net/yenc-0.3.tar.gz
http://sabnzbd.sourceforge.net/yenc-0.3-w32fixed.zip (Win32-only) http://sabnzbd.sourceforge.net/yenc-0.3-w32fixed.zip (Win32-only)
par2cmdline >= 0.4 http://parchive.sourceforge.net/ par2cmdline >= 0.4 http://parchive.sourceforge.net/
@ -65,6 +70,7 @@ Optional modules
unrar >= 3.90+ http://www.rarlab.com/rar_add.htm unrar >= 3.90+ http://www.rarlab.com/rar_add.htm
unzip >= 5.52 http://www.info-zip.org/ unzip >= 5.52 http://www.info-zip.org/
gnu gettext http://www.gnu.org/software/gettext/ gnu gettext http://www.gnu.org/software/gettext/
gntp https://github.com/kfdm/gntp/ (or use "pypm install gntp")
Optional modules Windows Optional modules Windows
pyopenssl >= 0.11 http://pypi.python.org/pypi/pyOpenSSL pyopenssl >= 0.11 http://pypi.python.org/pypi/pyOpenSSL
@ -91,7 +97,7 @@ Use the "Help" button in the web-interface to be directed to the Help Wiki.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
4) TROUBLESHOOTING 5) TROUBLESHOOTING
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Your browser may start up with just an error page. Your browser may start up with just an error page.
@ -109,7 +115,7 @@ This will show a black window where logging information will be shown. This
may help you solve problems easier. may help you solve problems easier.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
5) MORE INFORMATION 6) MORE INFORMATION
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Visit the WIKI site: Visit the WIKI site:
@ -117,7 +123,7 @@ Visit the WIKI site:
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
6) CREDITS 7) CREDITS
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Serveral parts of SABnzbd were built by other people, illustrating the Serveral parts of SABnzbd were built by other people, illustrating the

8
SABnzbd.py

@ -78,7 +78,7 @@ import sabnzbd.config as config
import sabnzbd.cfg import sabnzbd.cfg
import sabnzbd.downloader import sabnzbd.downloader
from sabnzbd.encoding import unicoder, latin1 from sabnzbd.encoding import unicoder, latin1
from sabnzbd.utils import osx import sabnzbd.growler as growler
from threading import Thread from threading import Thread
@ -1387,7 +1387,8 @@ def main():
if sabnzbd.FOUNDATION: if sabnzbd.FOUNDATION:
import sabnzbd.osxmenu import sabnzbd.osxmenu
sabnzbd.osxmenu.notify("SAB_Launched", None) sabnzbd.osxmenu.notify("SAB_Launched", None)
osx.sendGrowlMsg('SABnzbd %s' % (sabnzbd.__version__),"http://%s:%s/sabnzbd" % (browserhost, cherryport),osx.NOTIFICATION['startup']) growler.send_notification('SABnzbd %s' % (sabnzbd.__version__),
"http://%s:%s/sabnzbd" % (browserhost, cherryport), 'startup')
# Now's the time to check for a new version # Now's the time to check for a new version
check_latest_version() check_latest_version()
autorestarted = False autorestarted = False
@ -1527,8 +1528,7 @@ def main():
if getattr(sys, 'frozen', None) == 'macosx_app': if getattr(sys, 'frozen', None) == 'macosx_app':
AppHelper.stopEventLoop() AppHelper.stopEventLoop()
else: else:
if sabnzbd.DARWIN: growler.send_notification('SABnzbd',T('SABnzbd shutdown finished'), 'startup')
osx.sendGrowlMsg('SABnzbd',T('SABnzbd shutdown finished'),osx.NOTIFICATION['startup'])
os._exit(0) os._exit(0)

443
gntp/__init__.py

@ -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')

130
gntp/notifier.py

@ -0,0 +1,130 @@
"""
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)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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)
return response

30
interfaces/Classic/templates/config_email.tmpl

@ -52,11 +52,39 @@ $T('explain-email_account')<br>
<strong>$T('opt-email_pwd'):</strong><br> <strong>$T('opt-email_pwd'):</strong><br>
$T('explain-email_pwd')<br> $T('explain-email_pwd')<br>
<input type="password" size="35" name="email_pwd" value="$email_pwd"> <input type="password" size="35" name="email_pwd" value="$email_pwd">
<input type="hidden" name="session" value="$session">
</fieldset> </fieldset>
<!--#if $have_growl or $have_ntfosd#-->
<fieldset class="EntryFieldSet">
<legend>$T('growlSettings')</legend>
<!--#if $have_ntfosd#-->
<label><input type="checkbox" name="ntfosd_enable" value="1" <!--#if $ntfosd_enable != "0" then "checked=1" else ""#--> /> <strong>$T('opt-ntfosd_enable'):</strong></label><br>
$T('explain-ntfosd_enable')
<br/>
<br/>
<!--#end if#-->
<!--#if $have_growl#-->
<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-growl_server'):</strong><br>
$T('explain-growl_server')<br>
<input type="text" size="35" name="growl_server" value="$growl_server">
<br>
<br>
<strong>$T('opt-growl_password'):</strong><br>
$T('explain-growl_password')<br>
<input type="password" size="35" name="growl_password" value="$growl_password">
</fieldset>
<!--#end if#-->
<!--#end if#-->
</div> </div>
<input type="hidden" name="session" value="$session">
<p><input type="submit" value="$T('button-saveChanges')">&nbsp;&nbsp; <p><input type="submit" value="$T('button-saveChanges')">&nbsp;&nbsp;
<input type="button" onclick="if (confirm('$T('askTestEmail').replace("'","`") ')) { this.form.action='testmail?session=$session&'; this.form.submit(); return false;}" value="$T('link-testEmail')"/> <input type="button" onclick="if (confirm('$T('askTestEmail').replace("'","`") ')) { this.form.action='testmail?session=$session&'; this.form.submit(); return false;}" value="$T('link-testEmail')"/>
<input type="button" onclick="this.form.action='testnotification?session=$session&'; this.form.submit(); return false;"value="$T('testNotify')"/>
</p> </p>
</form> </form>
<!--#if $lastmail#--> <!--#if $lastmail#-->

50
interfaces/Plush/templates/config_email.tmpl

@ -111,6 +111,54 @@
</fieldset> </fieldset>
</div><!-- /component-group2 --> </div><!-- /component-group2 -->
<!--#if $have_growl or $have_ntfosd#-->
<div id="core-component-group3" class="component-group clearfix">
<div class="component-group-desc">
<h3>$T('growlSettings')</h3>
</div>
<fieldset class="component-group-list">
<!--#if $have_ntfosd#-->
<div class="field-pair">
<input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if $ntfosd_enable != "0" then "checked=1" else ""#--> />
<label class="clearfix" for="ntfosd_enable">
<span class="component-title">$T('opt-ntfosd_enable')</span>
<span class="component-desc">$T('explain-ntfosd_enable')</span>
</label>
</div>
<!--#end if#-->
<!--#if $have_growl#-->
<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>
<div class="field-pair">
<label class="nocheck clearfix" for="growl_server">
<span class="component-title">$T('opt-growl_server')</span>
<input type="text" name="growl_server" id="growl_server" value="$growl_server"/>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">$T('explain-growl_server')</span>
</label>
</div>
<div class="field-pair">
<label class="nocheck clearfix" for="growl_password">
<span class="component-title">$T('opt-growl_password')</span>
<input type="password" size="35" name="growl_password" id="growl_password" value="$growl_password"/>
</label>
<label class="nocheck clearfix">
<span class="component-title">&nbsp;</span>
<span class="component-desc">$T('explain-growl_password')</span>
</label>
</div>
<!--#end if#-->
</fieldset>
</div><!-- /component-group3 -->
<!--#end if#-->
<div class="component-group-last clearfix"> <div class="component-group-last clearfix">
<div class="component-group-desc"> <div class="component-group-desc">
<h3>&nbsp;</h3> <h3>&nbsp;</h3>
@ -120,6 +168,8 @@
<a id="save"><span class="config_sprite_container sprite_config_save">&nbsp;</span> $T('button-saveChanges')</a> <a id="save"><span class="config_sprite_container sprite_config_save">&nbsp;</span> $T('button-saveChanges')</a>
<a id="test_email" href="testmail?session=$session" rel="$T('askTestEmail')"> <a id="test_email" href="testmail?session=$session" rel="$T('askTestEmail')">
<span class="config_sprite_container sprite_config_email_test">&nbsp;</span> $T('link-testEmail')</a> <span class="config_sprite_container sprite_config_email_test">&nbsp;</span> $T('link-testEmail')</a>
<a id="test_notification" href="testnotification?session=$session">
<span class="config_sprite_container sprite_config_email_test">&nbsp;</span> $T('testNotify')</a>
</div> </div>
<!--#if $lastmail#--> <!--#if $lastmail#-->
&nbsp;&nbsp;&nbsp;&nbsp;$T('emailResult') = <b>$lastmail</b> &nbsp;&nbsp;&nbsp;&nbsp;$T('emailResult') = <b>$lastmail</b>

2
interfaces/Plush/templates/config_switches.tmpl

@ -35,7 +35,7 @@
</label> </label>
</div> </div>
<!--#end if#--> <!--#end if#-->
</fieldset> </fieldset>
</div><!-- /component-group1 --> </div><!-- /component-group1 -->
<div id="core-component-group2" class="component-group clearfix"> <div id="core-component-group2" class="component-group clearfix">

33
interfaces/smpl/templates/config_email.tmpl

@ -67,13 +67,44 @@
<br class="clear" /> <br class="clear" />
</fieldset> </fieldset>
<!--#if $have_growl or $have_ntfosd#-->
<fieldset class="EntryFieldSet">
<legend>$T('growlSettings')</legend>
<hr />
<!--#if $have_ntfosd#-->
<label><span class="label">$T('opt-ntfosd_enable'):</span>
<input class="radio" type="checkbox" name="ntfosd_enable" value="1" <!--#if $ntfosd_enable != "0" then "checked=1" else ""#--> />
<span class="tips">$T('explain-ntfosd_enable')</span></label>
<br class="clear" />
<!--#end if#-->
<!--#if $have_growl#-->
<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 ""#--> />
<span class="tips">$T('explain-growl_enable')</span></label>
<br class="clear" />
<label class="label">$T('opt-growl_server'):</label>
<input type="text" size="35" name="growl_server" value="$growl_server">
<span class="tips">$T('explain-growl_server')</span>
<br class="clear" />
<label class="label">$T('opt-growl_password'):</label>
<input type="password" size="35" name="growl_password" value="$growl_password">
<span class="tips">$T('explain-growl_password')</span>
<br class="clear" />
<!--#end if#-->
</fieldset>
<!--#end if#-->
</div> </div>
<p> <p>
<input type="button" size="40" value="$T('button-saveChanges')" onclick="javascript:submitconfig('config/email/saveEmail', this, 'configEmail')"> <input type="button" size="40" value="$T('button-saveChanges')" onclick="javascript:submitconfig('config/email/saveEmail', this, 'configEmail')">
</p> </p>
<br class="clear" /> <br class="clear" />
<a class="config" onClick="testemail();">Test E-Mail</a> <a class="config" onClick="testemail();">$T('link-testEmail')</a>
<a class="config" onClick="testnotification();">$T('testNotify')</a>
<!--#if $lastmail#--> <!--#if $lastmail#-->
&nbsp;&nbsp;&nbsp;&nbsp;$T('emailResult') = <b>$lastmail</b> &nbsp;&nbsp;&nbsp;&nbsp;$T('emailResult') = <b>$lastmail</b>
<!--#end if#--> <!--#end if#-->

10
interfaces/smpl/templates/main.tmpl

@ -740,6 +740,16 @@ function lrb(url, extra, refresh)
d.addErrback(handleServerError); d.addErrback(handleServerError);
} }
function testnotification()
{
d = doSimpleXMLHttpRequest('config/email/testnotification?session='+session);
d.addCallback(function (d)
{
alert("$T('smpl-notesent')");
});
d.addErrback(handleServerError);
}
function unblock_server(host, port) function unblock_server(host, port)
{ {
d = doSimpleXMLHttpRequest('connections/unblock_server?server='+host+':'+port+'&session='+session); d = doSimpleXMLHttpRequest('connections/unblock_server?server='+host+':'+port+'&session='+session);

1
package.py

@ -307,6 +307,7 @@ data_files = [
'COPYRIGHT.txt', 'COPYRIGHT.txt',
'ISSUES.txt', 'ISSUES.txt',
'nzb.ico', 'nzb.ico',
'sabnzbd.ico',
'Sample-PostProc.cmd', 'Sample-PostProc.cmd',
'Sample-PostProc.sh', 'Sample-PostProc.sh',
'PKG-INFO', 'PKG-INFO',

2
sabnzbd/__init__.py

@ -227,6 +227,8 @@ def initialize(pause_downloader = False, clean_up = False, evalSched=False, repa
cfg.bandwidth_limit.callback(guard_speedlimit) cfg.bandwidth_limit.callback(guard_speedlimit)
cfg.top_only.callback(guard_top_only) cfg.top_only.callback(guard_top_only)
cfg.pause_on_post_processing.callback(guard_pause_on_pp) cfg.pause_on_post_processing.callback(guard_pause_on_pp)
cfg.growl_server.callback(sabnzbd.growler.change_value)
cfg.growl_password.callback(sabnzbd.growler.change_value)
cfg.quotum_size.callback(guard_quotum_size) cfg.quotum_size.callback(guard_quotum_size)
cfg.quotum_day.callback(guard_quotum_dp) cfg.quotum_day.callback(guard_quotum_dp)
cfg.quotum_period.callback(guard_quotum_dp) cfg.quotum_period.callback(guard_quotum_dp)

5
sabnzbd/cfg.py

@ -212,6 +212,11 @@ nzb_key = OptionStr('misc', 'nzb_key', create_api_key())
disable_key = OptionBool('misc', 'disable_api_key', False) disable_key = OptionBool('misc', 'disable_api_key', False)
api_warnings = OptionBool('misc', 'api_warnings', True) api_warnings = OptionBool('misc', 'api_warnings', True)
growl_server = OptionStr('growl', 'growl_server')
growl_password = OptionPassword('growl', 'growl_password')
growl_enable = OptionBool('growl', 'growl_enable', True)
ntfosd_enable = OptionBool('growl', 'ntfosd_enable', True)
quotum_size = OptionStr('misc', 'quotum_size') quotum_size = OptionStr('misc', 'quotum_size')
quotum_day = OptionStr('misc', 'quotum_day') quotum_day = OptionStr('misc', 'quotum_day')
quotum_resume = OptionBool('misc', 'quotum_resume', False) quotum_resume = OptionBool('misc', 'quotum_resume', False)

6
sabnzbd/downloader.py

@ -30,7 +30,7 @@ import sabnzbd
from sabnzbd.decorators import synchronized, synchronized_CV, CV from sabnzbd.decorators import synchronized, synchronized_CV, CV
from sabnzbd.decoder import Decoder from sabnzbd.decoder import Decoder
from sabnzbd.newswrapper import NewsWrapper, request_server_info from sabnzbd.newswrapper import NewsWrapper, request_server_info
from sabnzbd.utils import osx import sabnzbd.growler as growler
from sabnzbd.constants import * from sabnzbd.constants import *
import sabnzbd.config as config import sabnzbd.config as config
import sabnzbd.cfg as cfg import sabnzbd.cfg as cfg
@ -201,7 +201,7 @@ class Downloader(Thread):
if not self.paused: if not self.paused:
self.paused = True self.paused = True
logging.info("Pausing") logging.info("Pausing")
osx.sendGrowlMsg("SABnzbd",T('Paused'),osx.NOTIFICATION['download']) growler.send_notification("SABnzbd", T('Paused'), 'download')
if self.is_paused(): if self.is_paused():
BPSMeter.do.reset() BPSMeter.do.reset()
if cfg.autodisconnect(): if cfg.autodisconnect():
@ -747,7 +747,7 @@ class Downloader(Thread):
def stop(self): def stop(self):
self.shutdown = True self.shutdown = True
osx.sendGrowlMsg("SABnzbd",T('Shutting down'),osx.NOTIFICATION['startup']) growler.send_notification("SABnzbd",T('Shutting down'), 'startup')
def stop(): def stop():

223
sabnzbd/growler.py

@ -0,0 +1,223 @@
#!/usr/bin/python -OO
# Copyright 2008-2011 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
"""
sabnzbd.growler - Send notifications to Growl
"""
#------------------------------------------------------------------------------
import os.path
import logging
import socket
import sabnzbd
import sabnzbd.cfg
from sabnzbd.encoding import unicoder, latin1
from gntp import GNTPRegister
from gntp.notifier import GrowlNotifier
try:
import Growl
_HAVE_OSX_GROWL = True
except ImportError:
_HAVE_OSX_GROWL = False
try:
import pynotify
_HAVE_NTFOSD = True
except ImportError:
_HAVE_NTFOSD = False
#------------------------------------------------------------------------------
# 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
}
_KEYS = ('startup', 'download', 'pp', 'complete', 'other')
#------------------------------------------------------------------------------
# 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 change_value():
""" Signal that we should register with a new Growl server
"""
global _GROWL_REG
_GROWL_REG = False
#------------------------------------------------------------------------------
def have_growl():
""" Return if any Growl support is present
"""
return True
def have_ntfosd():
""" Return if any PyNotify support is present
"""
return bool(_HAVE_NTFOSD)
#------------------------------------------------------------------------------
def send_notification(title , msg, gtype):
""" Send Notification message
"""
if have_growl():
send_growl(title, msg, gtype)
if have_ntfosd():
send_notify_osd(title, msg)
#------------------------------------------------------------------------------
def register_growl():
""" Register this app with Growl
"""
host, port = sabnzbd.misc.split_host(sabnzbd.cfg.growl_server())
if host:
sys_name = '@' + sabnzbd.misc.hostname().lower()
else:
sys_name = ''
# Clean up persistent data in GNTP to make re-registration work
GNTPRegister.notifications = []
GNTPRegister.headers = {}
growler = GrowlNotifier(
applicationName = 'SABnzbd%s' % sys_name,
applicationIcon = get_icon(),
notifications = [Tx(_NOTIFICATION[key]) for key in _KEYS],
hostname = host or None,
port = port or 23053,
password = sabnzbd.cfg.growl_password() or None
)
try:
ret = growler.register()
if ret is None or isinstance(ret, bool):
logging.info('Registered with Growl')
ret = growler
else:
logging.debug('Cannot register with Growl %s', ret)
del growler
ret = None
except socket.error, err:
logging.debug('Cannot register with Growl %s', err)
del growler
ret = None
return ret
#------------------------------------------------------------------------------
def send_growl(title , msg, gtype):
""" Send Growl message
"""
global _GROWL, _GROWL_REG
if not sabnzbd.cfg.growl_enable():
return
if _HAVE_OSX_GROWL and not sabnzbd.cfg.growl_server():
send_local_growl(title, msg, gtype)
return
for n in (0, 1):
if not _GROWL_REG: _GROWL = None
_GROWL = _GROWL or register_growl()
if _GROWL:
assert isinstance(_GROWL, GrowlNotifier)
_GROWL_REG = True
logging.debug('Send to Growl: %s %s %s', gtype, latin1(title), latin1(msg))
try:
ret = _GROWL.notify(
noteType = Tx(_NOTIFICATION.get(gtype, 'other')),
title = title,
description = unicoder(msg),
#icon = options.icon,
#sticky = options.sticky,
#priority = options.priority
)
if ret is None or isinstance(ret, bool):
return
elif ret[0] == '401':
_GROWL = False
else:
logging.debug('Growl error %s', ret)
return
except socket.error, err:
logging.debug('Growl error %s', err)
return
else:
return
#------------------------------------------------------------------------------
# Local OSX Growl support
#
if _HAVE_OSX_GROWL:
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 """
notes = [Tx(_NOTIFICATION[key]) for key in _KEYS]
growler = Growl.GrowlNotifier(
applicationName = 'SABnzbd',
applicationIcon = _OSX_ICON,
notifications = notes,
defaultNotifications = notes
)
growler.register()
growler.notify(Tx(_NOTIFICATION.get(gtype, 'other')), title, msg)
#------------------------------------------------------------------------------
# Ubuntu NotifyOSD Support
#
if _HAVE_NTFOSD:
_NTFOSD = False
def send_notify_osd(title, message):
""" Send a message to NotifyOSD
"""
global _NTFOSD
if sabnzbd.cfg.ntfosd_enable():
icon = os.path.join(sabnzbd.DIR_PROG, 'sabnzbd.ico')
_NTFOSD = _NTFOSD or pynotify.init('icon-summary-body')
if _NTFOSD:
logging.info('Send to NotifyOSD: %s / %s', latin1(title), latin1(message))
note = pynotify.Notification(title, message, icon)
note.show()

19
sabnzbd/interface.py

@ -51,6 +51,7 @@ from sabnzbd.downloader import Downloader
from sabnzbd.nzbqueue import NzbQueue from sabnzbd.nzbqueue import NzbQueue
import sabnzbd.wizard import sabnzbd.wizard
from sabnzbd.utils.servertests import test_nntp_server_dict from sabnzbd.utils.servertests import test_nntp_server_dict
from sabnzbd.growler import send_notification
from sabnzbd.constants import * from sabnzbd.constants import *
from sabnzbd.lang import list_languages, set_language from sabnzbd.lang import list_languages, set_language
@ -2451,6 +2452,7 @@ LIST_EMAIL = (
'email_server', 'email_to', 'email_from', 'email_server', 'email_to', 'email_from',
'email_account', 'email_pwd', 'email_dir', 'email_rss' 'email_account', 'email_pwd', 'email_dir', 'email_rss'
) )
LIST_GROWL = ('growl_enable', 'growl_server', 'growl_password', 'ntfosd_enable')
class ConfigEmail(object): class ConfigEmail(object):
def __init__(self, web_dir, root, prim): def __init__(self, web_dir, root, prim):
@ -2469,10 +2471,13 @@ class ConfigEmail(object):
conf['my_home'] = sabnzbd.DIR_HOME conf['my_home'] = sabnzbd.DIR_HOME
conf['my_lcldata'] = sabnzbd.DIR_LCLDATA conf['my_lcldata'] = sabnzbd.DIR_LCLDATA
conf['lastmail'] = self.__lastmail conf['lastmail'] = self.__lastmail
conf['have_growl'] = sabnzbd.growler.have_growl()
conf['have_ntfosd'] = sabnzbd.growler.have_ntfosd()
for kw in LIST_EMAIL: for kw in LIST_EMAIL:
conf[kw] = config.get_config('misc', kw).get_string() conf[kw] = config.get_config('misc', kw).get_string()
for kw in LIST_GROWL:
conf[kw] = config.get_config('growl', kw).get_string()
template = Template(file=os.path.join(self.__web_dir, 'config_email.tmpl'), template = Template(file=os.path.join(self.__web_dir, 'config_email.tmpl'),
filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES) filter=FILTER, searchList=[conf], compilerSettings=DIRECTIVES)
@ -2487,6 +2492,10 @@ class ConfigEmail(object):
msg = config.get_config('misc', kw).set(platform_encode(kwargs.get(kw))) msg = config.get_config('misc', kw).set(platform_encode(kwargs.get(kw)))
if msg: if msg:
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg))) return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)))
for kw in LIST_GROWL:
msg = config.get_config('growl', kw).set(platform_encode(kwargs.get(kw)))
if msg:
return badParameterResponse(T('Incorrect value for %s: %s') % (kw, unicoder(msg)))
config.save_config() config.save_config()
self.__lastmail = None self.__lastmail = None
@ -2507,6 +2516,14 @@ class ConfigEmail(object):
str(123*MEBI), pack, 'my_script', 'Line 1\nLine 2\nLine 3\nd\xe8ja vu\n', 0) str(123*MEBI), pack, 'my_script', 'Line 1\nLine 2\nLine 3\nd\xe8ja vu\n', 0)
raise dcRaiser(self.__root, kwargs) raise dcRaiser(self.__root, kwargs)
@cherrypy.expose
def testnotification(self, **kwargs):
msg = check_session(kwargs)
if msg: return msg
logging.info("Sending test notification")
send_notification('SABNzbd', T('Test Notification'), 'other')
raise dcRaiser(self.__root, kwargs)
def rss_history(url, limit=50, search=None): def rss_history(url, limit=50, search=None):
url = url.replace('rss','') url = url.replace('rss','')

11
sabnzbd/misc.py

@ -661,6 +661,17 @@ def split_host(srv):
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
def hostname():
""" Return host's pretty name """
if sabnzbd.WIN32:
return os.environ.get('computername', 'unknown')
try:
return os.uname()[1]
except:
return 'unknown'
#------------------------------------------------------------------------------
def check_mount(path): def check_mount(path):
""" Return False if volume isn't mounted on Linux or OSX """ Return False if volume isn't mounted on Linux or OSX
""" """

4
sabnzbd/newzbin.py

@ -41,7 +41,7 @@ from sabnzbd.misc import cat_to_opts, sanitize_foldername, bad_fetch, cat_conver
from sabnzbd.encoding import name_fixer from sabnzbd.encoding import name_fixer
import sabnzbd.newswrapper import sabnzbd.newswrapper
import sabnzbd.cfg as cfg import sabnzbd.cfg as cfg
from sabnzbd.utils import osx import sabnzbd.growler as growler
################################################################################ ################################################################################
@ -135,7 +135,7 @@ class MSGIDGrabber(Thread):
bad_fetch(nzo, msgid, msg=nzo_info, retry=True) bad_fetch(nzo, msgid, msg=nzo_info, retry=True)
msgid = None msgid = None
osx.sendGrowlMsg(T('NZB added to queue'),filename,osx.NOTIFICATION['download']) growler.send_notification(T('NZB added to queue'), filename, 'download')
# Keep some distance between the grabs # Keep some distance between the grabs
sleeper(5) sleeper(5)

4
sabnzbd/nzbqueue.py

@ -39,7 +39,7 @@ import sabnzbd.cfg as cfg
from sabnzbd.articlecache import ArticleCache from sabnzbd.articlecache import ArticleCache
import sabnzbd.downloader import sabnzbd.downloader
from sabnzbd.assembler import Assembler, file_has_articles from sabnzbd.assembler import Assembler, file_has_articles
from sabnzbd.utils import osx import sabnzbd.growler as growler
from sabnzbd.encoding import latin1, platform_encode from sabnzbd.encoding import latin1, platform_encode
from sabnzbd.bpsmeter import BPSMeter from sabnzbd.bpsmeter import BPSMeter
@ -325,7 +325,7 @@ class NzbQueue(TryList):
self.save(nzo) self.save(nzo)
if not (quiet or nzo.status in ('Fetching',)): if not (quiet or nzo.status in ('Fetching',)):
osx.sendGrowlMsg(T('NZB added to queue'), nzo.filename, osx.NOTIFICATION['download']) growler.send_notification(T('NZB added to queue'), nzo.filename, 'download')
if cfg.auto_sort(): if cfg.auto_sort():
self.sort_by_avg_age() self.sort_by_avg_age()

6
sabnzbd/osxmenu.py

@ -40,7 +40,7 @@ import sabnzbd.cfg
from sabnzbd.constants import * from sabnzbd.constants import *
from sabnzbd.misc import get_filename, get_ext, diskfree from sabnzbd.misc import get_filename, get_ext, diskfree
from sabnzbd.panic import launch_a_browser from sabnzbd.panic import launch_a_browser
from sabnzbd.utils import osx import sabnzbd.growler as growler
from sabnzbd.nzbqueue import NzbQueue from sabnzbd.nzbqueue import NzbQueue
import sabnzbd.config as config import sabnzbd.config as config
@ -506,7 +506,7 @@ class SABnzbdDelegate(NSObject):
if sabnzbd.NEW_VERSION and self.version_notify: if sabnzbd.NEW_VERSION and self.version_notify:
#logging.info("[osx] New Version : %s" % (sabnzbd.NEW_VERSION)) #logging.info("[osx] New Version : %s" % (sabnzbd.NEW_VERSION))
new_release, new_rel_url = sabnzbd.NEW_VERSION.split(';') new_release, new_rel_url = sabnzbd.NEW_VERSION.split(';')
osx.sendGrowlMsg("SABnzbd","%s : %s" % (T('New release available'),new_release),osx.NOTIFICATION['other']) growler.send_notification("SABnzbd","%s : %s" % (T('New release available'), new_release), 'other')
self.version_notify = 0 self.version_notify = 0
except : except :
logging.info("[osx] versionUpdate Exception %s" % (sys.exc_info()[0])) logging.info("[osx] versionUpdate Exception %s" % (sys.exc_info()[0]))
@ -728,7 +728,7 @@ class SABnzbdDelegate(NSObject):
sabnzbd.halt() sabnzbd.halt()
cherrypy.engine.exit() cherrypy.engine.exit()
sabnzbd.SABSTOP = True sabnzbd.SABSTOP = True
osx.sendGrowlMsg('SABnzbd',T('SABnzbd shutdown finished'),osx.NOTIFICATION['other']) growler.send_notification('SABnzbd', T('SABnzbd shutdown finished'), growler.NOTIFICATION['other'])
logging.info('Leaving SABnzbd') logging.info('Leaving SABnzbd')
sys.stderr.flush() sys.stderr.flush()
sys.stdout.flush() sys.stdout.flush()

10
sabnzbd/postproc.py

@ -45,7 +45,7 @@ import sabnzbd.config as config
import sabnzbd.cfg as cfg import sabnzbd.cfg as cfg
import sabnzbd.nzbqueue import sabnzbd.nzbqueue
import sabnzbd.database as database import sabnzbd.database as database
from sabnzbd.utils import osx import sabnzbd.growler as growler
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
@ -451,10 +451,10 @@ def process_job(nzo):
## Show final status in history ## Show final status in history
if all_ok: if all_ok:
osx.sendGrowlMsg(T('Download Completed'), filename, osx.NOTIFICATION['complete']) growler.send_notification(T('Download Completed'), filename, 'complete')
nzo.status = 'Completed' nzo.status = 'Completed'
else: else:
osx.sendGrowlMsg(T('Download Failed'), filename, osx.NOTIFICATION['complete']) growler.send_notification(T('Download Failed'), filename, 'complete')
nzo.status = 'Failed' nzo.status = 'Failed'
except: except:
@ -463,7 +463,7 @@ def process_job(nzo):
logging.info("Traceback: ", exc_info = True) logging.info("Traceback: ", exc_info = True)
crash_msg = T('see logfile') crash_msg = T('see logfile')
nzo.fail_msg = T('PostProcessing was aborted (%s)') % unicoder(crash_msg) nzo.fail_msg = T('PostProcessing was aborted (%s)') % unicoder(crash_msg)
osx.sendGrowlMsg(T('Download Failed'), filename, osx.NOTIFICATION['complete']) growler.send_notification(T('Download Failed'), filename, 'complete')
nzo.status = 'Failed' nzo.status = 'Failed'
par_error = True par_error = True
all_ok = False all_ok = False
@ -513,7 +513,7 @@ def parring(nzo, workdir):
""" Perform par processing. Returns: (par_error, re_add) """ Perform par processing. Returns: (par_error, re_add)
""" """
filename = nzo.final_name filename = nzo.final_name
osx.sendGrowlMsg(T('Post-processing'), nzo.final_name, osx.NOTIFICATION['pp']) growler.send_notification(T('Post-processing'), nzo.final_name, 'pp')
logging.info('Par2 check starting on %s', filename) logging.info('Par2 check starting on %s', filename)
## Collect the par files ## Collect the par files

3
sabnzbd/skintext.py

@ -499,8 +499,7 @@ SKIN_TEXT = {
'feedSettings' : TT('Settings'), #: Tab title for Config->Feeds 'feedSettings' : TT('Settings'), #: Tab title for Config->Feeds
'filters' : TT('Filters'), #: Tab title for Config->Feeds 'filters' : TT('Filters'), #: Tab title for Config->Feeds
# Config->Email 'configEmail' : TT('Notifications'), #: Main Config page
'configEmail' : TT('Email Notifications'), #: Main Config page
'emailOptions' : TT('Email Options'), #: Section header 'emailOptions' : TT('Email Options'), #: Section header
'opt-email_endjob' : TT('Email Notification On Job Completion'), 'opt-email_endjob' : TT('Email Notification On Job Completion'),
'email-never' : TT('Never'), #: When to send email 'email-never' : TT('Never'), #: When to send email

65
sabnzbd/utils/osx.py

@ -1,65 +0,0 @@
#!/usr/bin/python -OO
# Copyright 2008-2011 The SABnzbd-Team <team@sabnzbd.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# 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'}
# 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
}
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')
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()
except ImportError:
def sendGrowlMsg(nTitle , nMsg, nType):
pass
Loading…
Cancel
Save