diff --git a/data/interfaces/brink/config.html b/data/interfaces/brink/config.html index c9f9974d..55ffde67 100644 --- a/data/interfaces/brink/config.html +++ b/data/interfaces/brink/config.html @@ -299,7 +299,7 @@ -

DNZBMatrix

+

DNZBMatrix

diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index a3f994cb..58baf946 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -715,6 +715,26 @@ +
+

Pushover

+
+ +
+
+
+ +
+
+ +
+
+ + +
+
+
+ +
Musicbrainz
@@ -928,6 +948,26 @@ $("#nmaoptions").slideUp(); } }); + if ($("#pushover").is(":checked")) + { + $("#pushoveroptions").show(); + } + else + { + $("#pushoveroptions").hide(); + } + + $("#pushover").click(function(){ + if ($("#pushover").is(":checked")) + { + $("#pushoveroptions").slideDown(); + } + else + { + $("#pushoveroptions").slideUp(); + } + }); + if ($("#preferred_bitrate").is(":checked")) { $("#preferred_bitrate_options").show(); diff --git a/headphones/__init__.py b/headphones/__init__.py index 3f27c709..2da69469 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -194,6 +194,10 @@ NMA_APIKEY = None NMA_PRIORITY = None NMA_ONSNATCH = None SYNOINDEX_ENABLED = False +PUSHOVER_ENABLED = True +PUSHOVER_PRIORITY = 1 +PUSHOVER_KEYS = None +PUSHOVER_ONSNATCH = True MIRRORLIST = ["musicbrainz.org","headphones","custom"] MIRROR = None CUSTOMHOST = None @@ -265,7 +269,8 @@ def initialize(): NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS,\ NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE, FOLDER_PERMISSIONS, \ ENCODERFOLDER, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, \ - ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, MIRRORLIST, \ + ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, \ + PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, MIRRORLIST, \ MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \ XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \ PREFERRED_BITRATE_LOW_BUFFER,CACHE_SIZEMB @@ -284,6 +289,7 @@ def initialize(): CheckSection('Rutracker') CheckSection('What.cd') CheckSection('Prowl') + CheckSection('Pushover') CheckSection('XBMC') CheckSection('NMA') CheckSection('Synoindex') @@ -431,6 +437,11 @@ def initialize(): SYNOINDEX_ENABLED = bool(check_setting_int(CFG, 'Synoindex', 'synoindex_enabled', 0)) + PUSHOVER_ENABLED = bool(check_setting_int(CFG, 'Pushover', 'pushover_enabled', 0)) + PUSHOVER_KEYS = check_setting_str(CFG, 'Pushover', 'pushover_keys', '') + PUSHOVER_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_onsnatch', 0)) + PUSHOVER_PRIORITY = check_setting_int(CFG, 'Pushover', 'pushover_priority', 0) + MIRROR = check_setting_str(CFG, 'General', 'mirror', 'musicbrainz.org') CUSTOMHOST = check_setting_str(CFG, 'General', 'customhost', 'localhost') CUSTOMPORT = check_setting_int(CFG, 'General', 'customport', 5000) @@ -720,6 +731,12 @@ def config_write(): new_config['NMA']['nma_priority'] = NMA_PRIORITY new_config['NMA']['nma_onsnatch'] = int(PROWL_ONSNATCH) + new_config['Pushover'] = {} + new_config['Pushover']['pushover_enabled'] = int(PUSHOVER_ENABLED) + new_config['Pushover']['pushover_keys'] = PUSHOVER_KEYS + new_config['Pushover']['pushover_onsnatch'] = int(PUSHOVER_ONSNATCH) + new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY) + new_config['Synoindex'] = {} new_config['Synoindex']['synoindex_enabled'] = int(SYNOINDEX_ENABLED) diff --git a/headphones/importer.py b/headphones/importer.py index 447ff908..0365a872 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -49,6 +49,16 @@ def artistlist_to_mbids(artistlist, forced=False): if not artist and not (artist == ' '): continue + + # If adding artists through Manage New Artists, they're coming through as non-unicode (utf-8?) + # and screwing everything up + if not isinstance(artist, unicode): + try: + artist = artist.decode('utf-8', 'replace') + except: + logger.warn("Unable to convert artist to unicode so cannot do a database lookup") + continue + results = mb.findArtist(artist, limit=1) if not results: diff --git a/headphones/mb.py b/headphones/mb.py index 84c21ec5..5a20e0ad 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -407,17 +407,6 @@ def getTracksFromRelease(release): # Used when there is a disambiguation def findArtistbyAlbum(name): - # Somehow non unicode is getting passed into this function? - if not isinstance(name, unicode): - try: - name = name.decode('latin-1', 'replace') - except: - try: - name = name.decode(headphones.SYS_ENCODING, 'replace') - except: - logger.warn("Unable to convert artist to unicode so cannot do a database lookup") - return False - myDB = db.DBConnection() artist = myDB.action('SELECT AlbumTitle from have WHERE ArtistName=? AND AlbumTitle IS NOT NULL ORDER BY RANDOM()', [name]).fetchone() diff --git a/headphones/notifiers.py b/headphones/notifiers.py index 3df88485..11a5e57c 100644 --- a/headphones/notifiers.py +++ b/headphones/notifiers.py @@ -23,6 +23,7 @@ from httplib import HTTPSConnection from urllib import urlencode import os.path import subprocess +import lib.simplejson as simplejson class PROWL: @@ -86,67 +87,102 @@ class XBMC: self.hosts = headphones.XBMC_HOST self.username = headphones.XBMC_USERNAME self.password = headphones.XBMC_PASSWORD - - def _send(self, command): - - hosts = [x.strip() for x in self.hosts.split(',')] + + def _sendhttp(self, host, command): + username = self.username password = self.password url_command = urllib.urlencode(command) - for host in hosts: - - url = host + '/xbmcCmds/xbmcHttp/?' + url_command + url = host + '/xbmcCmds/xbmcHttp/?' + url_command - req = urllib2.Request(url) + req = urllib2.Request(url) - if password: - base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '') - req.add_header("Authorization", "Basic %s" % base64string) + if password: + base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '') + req.add_header("Authorization", "Basic %s" % base64string) - logger.info('XBMC url: %s' % url) + logger.info('XBMC url: %s' % url) - try: - handle = urllib2.urlopen(req) - except Exception, e: - logger.warn('Error opening XBMC url: ' % e) - return + try: + handle = urllib2.urlopen(req) + except Exception, e: + logger.warn('Error opening XBMC url: %s' % e) + return - response = handle.read().decode(headphones.SYS_ENCODING) + response = handle.read().decode(headphones.SYS_ENCODING) - return response + return response + def _sendjson(self, host, method, params={}): + data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}] + data = simplejson.JSONEncoder().encode(data) + + content = {'Content-Type': 'application/json', 'Content-Length': len(data)} + + req = urllib2.Request(host+'/jsonrpc', data, content) + + if self.username and self.password: + base64string = base64.encodestring('%s:%s' % (self.username, self.password)).replace('\n', '') + req.add_header("Authorization", "Basic %s" % base64string) + + try: + handle = urllib2.urlopen(req) + except Exception, e: + logger.warn('Error opening XBMC url: %s' % e) + return + + response = simplejson.JSONDecoder().decode(handle.read()) + + try: + return response[0]['result'] + except: + logger.warn('XBMC returned error: %s' % response[0]['error']) + return + def update(self): # From what I read you can't update the music library on a per directory or per path basis # so need to update the whole thing - - updatecommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(music)'} - - logger.info('Sending library update command to XBMC') - request = self._send(updatecommand) - - if not request: - logger.warn('Error sending update request to XBMC') + + hosts = [x.strip() for x in self.hosts.split(',')] + + for host in hosts: + logger.info('Sending library update command to XBMC @ '+host) + request = self._sendjson(host, 'AudioLibrary.Scan') + + if not request: + logger.warn('Error sending update request to XBMC') def notify(self, artist, album, albumartpath): - + + hosts = [x.strip() for x in self.hosts.split(',')] + header = "Headphones" message = "%s - %s added to your library" % (artist, album) time = "3000" # in ms - - - notification = header + "," + message + "," + time + "," + albumartpath - - notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + notification + ')' } - - logger.info('Sending notification command to XMBC') - request = self._send(notifycommand) - - if not request: - logger.warn('Error sending notification request to XBMC') - + + for host in hosts: + logger.info('Sending notification command to XMBC @ '+host) + try: + version = self._sendjson(host, 'Application.GetProperties', {'properties': ['version']})['version']['major'] + + if version < 12: #Eden + notification = header + "," + message + "," + time + "," + albumartpath + notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'} + request = self._sendhttp(host, notifycommand) + + else: #Frodo + params = {'title':header, 'message': message, 'displaytime': int(time), 'image': albumartpath} + request = self._sendjson(host, 'GUI.ShowNotification', params) + + if not request: + raise Exception + + except: + logger.warn('Error sending notification request to XBMC') + class NMA: def __init__(self): @@ -227,3 +263,62 @@ class Synoindex: if isinstance(path_list, list): for path in path_list: self.notify(path) +class PUSHOVER: + + application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz" + keys = [] + priority = [] + + def __init__(self): + self.enabled = headphones.PUSHOVER_ENABLED + self.keys = headphones.PUSHOVER_KEYS + self.priority = headphones.PUSHOVER_PRIORITY + pass + + def conf(self, options): + return cherrypy.config['config'].get('Pushover', options) + + def notify(self, message, event): + if not headphones.PUSHOVER_ENABLED: + return + + http_handler = HTTPSConnection("api.pushover.net") + + data = {'token': self.application_token, + 'user': headphones.PUSHOVER_KEYS, + 'title': event, + 'message': message.encode("utf-8"), + 'priority': headphones.PUSHOVER_PRIORITY } + + http_handler.request("POST", + "/1/messages.json", + headers = {'Content-type': "application/x-www-form-urlencoded"}, + body = urlencode(data)) + response = http_handler.getresponse() + request_status = response.status + logger.debug(u"Pushover response status: %r" % request_status) + logger.debug(u"Pushover response headers: %r" % response.getheaders()) + logger.debug(u"Pushover response body: %r" % response.read()) + + if request_status == 200: + logger.info(u"Pushover notifications sent.") + return True + elif request_status >= 400 and request_status < 500: + logger.info(u"Pushover request failed: %s" % response.reason) + return False + else: + logger.info(u"Pushover notification failed.") + return False + + def updateLibrary(self): + #For uniformity reasons not removed + return + + def test(self, keys, priority): + + self.enabled = True + self.keys = keys + self.priority = priority + + self.notify('Main Screen Activate', 'Test Message') + diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 1cc07a7f..eb093344 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -395,6 +395,12 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list) for albumpath in albumpaths: syno.notify(albumpath) + if headphones.PUSHOVER_ENABLED: + pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle'] + logger.info(u"Pushover request") + pushover = notifiers.PUSHOVER() + pushover.notify(pushmessage,"Download and Postprocessing completed") + def embedAlbumArt(artwork, downloaded_track_list): logger.info('Embedding album art') @@ -454,7 +460,7 @@ def moveFiles(albumpath, release, tracks): '$Album': album, '$Year': year, '$Type': releasetype, - '$First': firstchar, + '$First': firstchar.upper(), '$artist': artist.lower(), '$album': album.lower(), '$year': year, @@ -795,6 +801,9 @@ def forcePostProcess(): if headphones.DOWNLOAD_TORRENT_DIR: download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace')) + # If DOWNLOAD_DIR and DOWNLOAD_TORRENT_DIR are the same, remove the duplicate to prevent us from trying to process the same folder twice. + download_dirs = list(set(download_dirs)) + logger.info('Checking to see if there are any folders to process in download_dir(s): %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace')) # Get a list of folders in the download_dir folders = [] @@ -818,6 +827,15 @@ def forcePostProcess(): logger.info('Processing: %s' % folder_basename) + # First try to see if there's a match in the snatched table, then we'll try to parse the foldername + snatched = myDB.action('SELECT AlbumID, Title from snatched WHERE FolderName LIKE ?', [folder_basename]) + if snatched: + logger.info('Found a match in the database: %s. Verifying to make sure it is the correct album' % snatched['Title']) + verify(snatched['AlbumID'], folder) + continue + + # Try to parse the folder name into a valid format + # TODO: Add metadata lookup try: name, album, year = helpers.extract_data(folder_basename) except: diff --git a/headphones/sab.py b/headphones/sab.py index 9b0e2bd1..f3c7fcd3 100644 --- a/headphones/sab.py +++ b/headphones/sab.py @@ -128,6 +128,10 @@ def sendNZB(nzb): logger.info(u"Sending Prowl notification") prowl = notifiers.PROWL() prowl.notify(nzb.name,"Download started") + if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH: + logger.info(u"Sending Pushover notification") + prowl = notifiers.PUSHOVER() + prowl.notify(nzb.name,"Download started") if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH: logger.debug(u"Sending NMA notification") nma = notifiers.NMA() diff --git a/headphones/searcher.py b/headphones/searcher.py index 016a4894..7e61d937 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -209,7 +209,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): "term": term } - searchURL = "http://rss.nzbmatrix.com/rss.php?" + urllib.urlencode(params) + searchURL = "https://rss.nzbmatrix.com/rss.php?" + urllib.urlencode(params) logger.info(u'Parsing results from NZBMatrix' % searchURL) try: data = urllib2.urlopen(searchURL, timeout=20).read() diff --git a/headphones/webserve.py b/headphones/webserve.py index 08d1863f..4e04246f 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -649,6 +649,10 @@ class WebInterface(object): "nma_priority": int(headphones.NMA_PRIORITY), "nma_onsnatch": checked(headphones.NMA_ONSNATCH), "synoindex_enabled": checked(headphones.SYNOINDEX_ENABLED), + "pushover_enabled": checked(headphones.PUSHOVER_ENABLED), + "pushover_onsnatch": checked(headphones.PUSHOVER_ONSNATCH), + "pushover_keys": headphones.PUSHOVER_KEYS, + "pushover_priority": headphones.PUSHOVER_PRIORITY, "mirror_list": headphones.MIRRORLIST, "mirror": headphones.MIRROR, "customhost": headphones.CUSTOMHOST, @@ -688,7 +692,8 @@ class WebInterface(object): remix=0, spokenword=0, audiobook=0, autowant_upcoming=False, autowant_all=False, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None, bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, delete_lossless_files=0, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None, - xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, synoindex_enabled=False, mirror=None, customhost=None, customport=None, + xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, synoindex_enabled=False, + pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None, preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, cache_sizemb=None, **kwargs): headphones.HTTP_HOST = http_host @@ -787,6 +792,10 @@ class WebInterface(object): headphones.NMA_PRIORITY = nma_priority headphones.NMA_ONSNATCH = nma_onsnatch headphones.SYNOINDEX_ENABLED = synoindex_enabled + headphones.PUSHOVER_ENABLED = pushover_enabled + headphones.PUSHOVER_ONSNATCH = pushover_onsnatch + headphones.PUSHOVER_KEYS = pushover_keys + headphones.PUSHOVER_PRIORITY = pushover_priority headphones.MIRROR = mirror headphones.CUSTOMHOST = customhost headphones.CUSTOMPORT = customport