From bbde74db8d7f44a3de8844dafd6e3ef71299e482 Mon Sep 17 00:00:00 2001 From: Jens Rogier Date: Tue, 6 May 2014 20:41:18 +0200 Subject: [PATCH 01/16] Fix: "Ignored" albums not ignored during scheduled updates. --- headphones/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/importer.py b/headphones/importer.py index d73293ef..c4a239ce 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -496,7 +496,7 @@ def finalize_update(artistid, artistname, errors=False): myDB = db.DBConnection() latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone() - totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid])) + totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid])) #havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']])) havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname])) From 9da75a777b59092c92bc1ab174f54da6e3dc1cdf Mon Sep 17 00:00:00 2001 From: delphiactual Date: Tue, 6 May 2014 20:22:13 -0600 Subject: [PATCH 02/16] NMA fix --- headphones/__init__.py | 4 ++-- headphones/notifiers.py | 52 +++++++++++++++++++++++------------------ headphones/searcher.py | 30 ++++++++++++++++++++++-- headphones/utorrent.py | 3 +-- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 380a31bf..9aa42ba1 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -248,7 +248,7 @@ PLEX_UPDATE = False PLEX_NOTIFY = False NMA_ENABLED = False NMA_APIKEY = None -NMA_PRIORITY = None +NMA_PRIORITY = 0 NMA_ONSNATCH = None PUSHALOT_ENABLED = False PUSHALOT_APIKEY = None @@ -1014,7 +1014,7 @@ def config_write(): new_config['NMA'] = {} new_config['NMA']['nma_enabled'] = int(NMA_ENABLED) new_config['NMA']['nma_apikey'] = NMA_APIKEY - new_config['NMA']['nma_priority'] = NMA_PRIORITY + new_config['NMA']['nma_priority'] = int(NMA_PRIORITY) new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH) new_config['Pushalot'] = {} diff --git a/headphones/notifiers.py b/headphones/notifiers.py index f5e4ff12..4bd7c366 100644 --- a/headphones/notifiers.py +++ b/headphones/notifiers.py @@ -27,7 +27,7 @@ import time from xml.dom import minidom from httplib import HTTPSConnection from urllib import urlencode - +from lib.pynma import pynma import lib.oauth2 as oauth import lib.pythontwitter as twitter @@ -380,34 +380,40 @@ class Plex: logger.warn('Error sending notification request to Plex Media Server') class NMA: + def notify(self, artist=None, album=None, snatched=None): + title = 'Headphones' + api = headphones.NMA_APIKEY + nma_priority = headphones.NMA_PRIORITY - def __init__(self): - - self.apikey = headphones.NMA_APIKEY - self.priority = headphones.NMA_PRIORITY - - def _send(self, data): - return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data) - - def notify(self, artist=None, album=None, snatched_nzb=None): - - apikey = self.apikey - priority = self.priority - - if snatched_nzb: - event = snatched_nzb + " snatched!" - description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+" + logger.debug(u"NMA title: " + title) + logger.debug(u"NMA API: " + api) + logger.debug(u"NMA Priority: " + str(nma_priority)) + if snatched: + event = snatched + " snatched!" + message = "Headphones has snatched: " + snatched else: event = artist + ' - ' + album + ' complete!' - description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']' + message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']' - data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority} - logger.info('Sending notification request to NotifyMyAndroid') - request = self._send(data) + logger.debug(u"NMA event: " + event) + logger.debug(u"NMA message: " + message) - if not request: - logger.warn('Error sending notification request to NotifyMyAndroid') + batch = False + + p = pynma.PyNMA() + keys = api.split(',') + p.addkey(keys) + + if len(keys) > 1: batch = True + + response = p.push(title, event, message, priority=nma_priority, batch_mode=batch) + + if not response[api][u'code'] == u'200': + logger.error(u'Could not send notification to NotifyMyAndroid') + return False + else: + return True class PUSHBULLET: diff --git a/headphones/searcher.py b/headphones/searcher.py index 1cc78b84..48483b0d 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -699,7 +699,7 @@ def send_to_downloader(data, bestqual, album): except Exception, e: logger.exception("Unhandled exception") - else: + elif headphones.TORRENT_DOWNLOADER == 2: logger.info("Sending torrent to uTorrent") # rutracker needs cookies to be set, pass the .torrent file instead of url @@ -725,6 +725,32 @@ def send_to_downloader(data, bestqual, album): except Exception, e: logger.exception("Unhandled exception") + else: + logger.info("Sending torrent to DownloadStation") + + # rutracker needs cookies to be set, pass the .torrent file instead of url + if bestqual[3] == 'rutracker.org': + file_or_url = rutracker.get_torrent(bestqual[2]) + else: + file_or_url = bestqual[2] + + _hash = CalculateTorrentHash(file_or_url, data) + + folder_name = download_station.addTorrent(file_or_url) + + if folder_name: + logger.info('Torrent folder name: %s' % folder_name) + else: + logger.error('Torrent folder name could not be determined') + return + + # remove temp .torrent file created above + if bestqual[3] == 'rutracker.org': + try: + shutil.rmtree(os.path.split(file_or_url)[0]) + except Exception, e: + logger.exception("Unhandled exception") + myDB = db.DBConnection() myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind]) @@ -762,7 +788,7 @@ def send_to_downloader(data, bestqual, album): if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH: logger.info(u"Sending NMA notification") nma = notifiers.NMA() - nma.notify(snatched_nzb=name) + nma.notify(snatched=name) if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH: logger.info(u"Sending Pushalot notification") pushalot = notifiers.PUSHALOT() diff --git a/headphones/utorrent.py b/headphones/utorrent.py index e60d95c5..3ce07906 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -144,7 +144,6 @@ class utorrentclient(object): def addTorrent(link, hash): label = headphones.UTORRENT_LABEL - uTorrentClient = utorrentclient() uTorrentClient.add_url(link) time.sleep(1) #need to ensure file is loaded uTorrent... @@ -154,4 +153,4 @@ def addTorrent(link, hash): if (torrent[0].lower()==hash): return torrent[26] - return False + return False \ No newline at end of file From 5c1169ba9d3a7bbb4fcd966a77fe9d44bd679d4f Mon Sep 17 00:00:00 2001 From: delphiactual Date: Tue, 6 May 2014 21:38:28 -0600 Subject: [PATCH 03/16] Removed leftover DownloadStation [beta] changes --- headphones/notifiers.py | 2 +- headphones/searcher.py | 32 +++----------------------------- headphones/utorrent.py | 19 +++++++------------ 3 files changed, 11 insertions(+), 42 deletions(-) diff --git a/headphones/notifiers.py b/headphones/notifiers.py index 4bd7c366..1537323b 100644 --- a/headphones/notifiers.py +++ b/headphones/notifiers.py @@ -388,6 +388,7 @@ class NMA: logger.debug(u"NMA title: " + title) logger.debug(u"NMA API: " + api) logger.debug(u"NMA Priority: " + str(nma_priority)) + if snatched: event = snatched + " snatched!" message = "Headphones has snatched: " + snatched @@ -395,7 +396,6 @@ class NMA: event = artist + ' - ' + album + ' complete!' message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']' - logger.debug(u"NMA event: " + event) logger.debug(u"NMA message: " + message) diff --git a/headphones/searcher.py b/headphones/searcher.py index 48483b0d..0a33df12 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -699,7 +699,7 @@ def send_to_downloader(data, bestqual, album): except Exception, e: logger.exception("Unhandled exception") - elif headphones.TORRENT_DOWNLOADER == 2: + else:# if headphones.TORRENT_DOWNLOADER == 2: logger.info("Sending torrent to uTorrent") # rutracker needs cookies to be set, pass the .torrent file instead of url @@ -725,32 +725,6 @@ def send_to_downloader(data, bestqual, album): except Exception, e: logger.exception("Unhandled exception") - else: - logger.info("Sending torrent to DownloadStation") - - # rutracker needs cookies to be set, pass the .torrent file instead of url - if bestqual[3] == 'rutracker.org': - file_or_url = rutracker.get_torrent(bestqual[2]) - else: - file_or_url = bestqual[2] - - _hash = CalculateTorrentHash(file_or_url, data) - - folder_name = download_station.addTorrent(file_or_url) - - if folder_name: - logger.info('Torrent folder name: %s' % folder_name) - else: - logger.error('Torrent folder name could not be determined') - return - - # remove temp .torrent file created above - if bestqual[3] == 'rutracker.org': - try: - shutil.rmtree(os.path.split(file_or_url)[0]) - except Exception, e: - logger.exception("Unhandled exception") - myDB = db.DBConnection() myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind]) @@ -1431,7 +1405,7 @@ def preprocess(resultlist): for result in resultlist: if result[4] == 'torrent': - #Get out of here if we're using Transmission or uTorrent + #Get out of here if we're using Transmission if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling return True, result # get outta here if rutracker @@ -1495,6 +1469,6 @@ def CalculateTorrentHash(link, data): info = bdecode(data)["info"] tor_hash = sha1(bencode(info)).hexdigest() - logger.info('Torrent Hash: ' + str(tor_hash)) + logger.debug('Torrent Hash: ' + str(tor_hash)) return tor_hash \ No newline at end of file diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 3ce07906..848cf6f9 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -13,17 +13,12 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . -import urllib -import urllib2 -import urlparse -import cookielib -import json -import re -import os -import time +import urllib, urllib2, urlparse, cookielib +import json, re, os, time + import headphones -from headphones import logger, notifiers +from headphones import logger class utorrentclient(object): TOKEN_REGEX = "" @@ -77,7 +72,7 @@ class utorrentclient(object): return self._action(params) def add_url(self, url): - #can recieve magnet or normal .torrent link + #can receive magnet or normal .torrent link params = [('action', 'add-url'), ('s', url)] return self._action(params) @@ -146,8 +141,8 @@ def addTorrent(link, hash): label = headphones.UTORRENT_LABEL uTorrentClient = utorrentclient() uTorrentClient.add_url(link) - time.sleep(1) #need to ensure file is loaded uTorrent... - uTorrentClient.setprops(hash,'label', label) + time.sleep(1) #ensure file is loaded in uTorrent... + uTorrentClient.setprops(hash,'label',label) torrentList = uTorrentClient.list() for torrent in torrentList[1].get('torrents'): if (torrent[0].lower()==hash): From 62a6be26af288434d8ddecc8f33dbce2f1dd04ff Mon Sep 17 00:00:00 2001 From: delphiactual Date: Wed, 7 May 2014 08:19:08 -0600 Subject: [PATCH 04/16] PyNMA lib --- lib/pynma/__init__.py | 4 ++ lib/pynma/pynma.py | 137 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 lib/pynma/__init__.py create mode 100644 lib/pynma/pynma.py diff --git a/lib/pynma/__init__.py b/lib/pynma/__init__.py new file mode 100644 index 00000000..f90424eb --- /dev/null +++ b/lib/pynma/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/python + +from pynma import PyNMA + diff --git a/lib/pynma/pynma.py b/lib/pynma/pynma.py new file mode 100644 index 00000000..fc7d8de2 --- /dev/null +++ b/lib/pynma/pynma.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +from xml.dom.minidom import parseString +from httplib import HTTPSConnection +from urllib import urlencode + +__version__ = "0.1" + +API_SERVER = 'nma.usk.bz' +ADD_PATH = '/publicapi/notify' + +USER_AGENT="PyNMA/v%s"%__version__ + +def uniq_preserve(seq): # Dave Kirby + # Order preserving + seen = set() + return [x for x in seq if x not in seen and not seen.add(x)] + +def uniq(seq): + # Not order preserving + return {}.fromkeys(seq).keys() + +class PyNMA(object): + """PyNMA(apikey=[], developerkey=None) + takes 2 optional arguments: + - (opt) apykey: might me a string containing 1 key or an array of keys + - (opt) developerkey: where you can store your developer key + """ + + def __init__(self, apikey=[], developerkey=None): + self._developerkey = None + self.developerkey(developerkey) + if apikey: + if type(apikey) == str: + apikey = [apikey] + self._apikey = uniq(apikey) + + def addkey(self, key): + "Add a key (register ?)" + if type(key) == str: + if not key in self._apikey: + self._apikey.append(key) + elif type(key) == list: + for k in key: + if not k in self._apikey: + self._apikey.append(k) + + def delkey(self, key): + "Removes a key (unregister ?)" + if type(key) == str: + if key in self._apikey: + self._apikey.remove(key) + elif type(key) == list: + for k in key: + if key in self._apikey: + self._apikey.remove(k) + + def developerkey(self, developerkey): + "Sets the developer key (and check it has the good length)" + if type(developerkey) == str and len(developerkey) == 48: + self._developerkey = developerkey + + def push(self, application="", event="", description="", url="", priority=0, batch_mode=False): + """Pushes a message on the registered API keys. + takes 5 arguments: + - (req) application: application name [256] + - (req) event: event name [1000] + - (req) description: description [10000] + - (opt) url: url [512] + - (opt) priority: from -2 (lowest) to 2 (highest) (def:0) + - (opt) batch_mode: call API 5 by 5 (def:False) + + Warning: using batch_mode will return error only if all API keys are bad + cf: http://nma.usk.bz/api.php + """ + datas = { + 'application': application[:256].encode('utf8'), + 'event': event[:1024].encode('utf8'), + 'description': description[:10000].encode('utf8'), + 'priority': priority + } + + if url: + datas['url'] = url[:512] + + if self._developerkey: + datas['developerkey'] = self._developerkey + + results = {} + + if not batch_mode: + for key in self._apikey: + datas['apikey'] = key + res = self.callapi('POST', ADD_PATH, datas) + results[key] = res + else: + for i in range(0, len(self._apikey), 5): + datas['apikey'] = ",".join(self._apikey[i:i+5]) + res = self.callapi('POST', ADD_PATH, datas) + results[datas['apikey']] = res + return results + + def callapi(self, method, path, args): + headers = { 'User-Agent': USER_AGENT } + if method == "POST": + headers['Content-type'] = "application/x-www-form-urlencoded" + http_handler = HTTPSConnection(API_SERVER) + http_handler.request(method, path, urlencode(args), headers) + resp = http_handler.getresponse() + + try: + res = self._parse_reponse(resp.read()) + except Exception, e: + res = {'type': "pynmaerror", + 'code': 600, + 'message': str(e) + } + pass + + return res + + def _parse_reponse(self, response): + root = parseString(response).firstChild + for elem in root.childNodes: + if elem.nodeType == elem.TEXT_NODE: continue + if elem.tagName == 'success': + res = dict(elem.attributes.items()) + res['message'] = "" + res['type'] = elem.tagName + return res + if elem.tagName == 'error': + res = dict(elem.attributes.items()) + res['message'] = elem.firstChild.nodeValue + res['type'] = elem.tagName + return res + + From e972a86233fc9a72ef4b9ca7ac482dd600c85511 Mon Sep 17 00:00:00 2001 From: delphiactual Date: Sun, 11 May 2014 21:26:58 -0600 Subject: [PATCH 05/16] Easier to follow logic for uTorrent --- headphones/utorrent.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 848cf6f9..b03acdf2 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -136,16 +136,31 @@ class utorrentclient(object): logger.debug('URL: ' + str(url)) logger.debug('uTorrent webUI raised the following error: ' + str(err)) -def addTorrent(link, hash): +def labelTorrent(hash): label = headphones.UTORRENT_LABEL uTorrentClient = utorrentclient() - uTorrentClient.add_url(link) - time.sleep(1) #ensure file is loaded in uTorrent... - uTorrentClient.setprops(hash,'label',label) + settinglabel = True + while settinglabel: + torrentList = uTorrentClient.list() + for torrent in torrentList[1].get('torrents'): + if (torrent[0].lower() == hash): + uTorrentClient.setprops(hash,'label',label) + settinglabel = False + return True + + +def dirTorrent(hash): + uTorrentClient = utorrentclient() torrentList = uTorrentClient.list() for torrent in torrentList[1].get('torrents'): - if (torrent[0].lower()==hash): + if (torrent[0].lower() == hash): return torrent[26] + return False - return False \ No newline at end of file + +def addTorrent(link, hash): + uTorrentClient = utorrentclient() + uTorrentClient.add_url(link) + labelTorrent(hash) + return dirTorrent(hash) From 188d907e103bc3b4737be2a788801a90ecfe1133 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:02:25 +0200 Subject: [PATCH 06/16] GET, POST, HEAD etc are in lower case in requests.py --- headphones/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/request.py b/headphones/request.py index 9892592e..bbb517a9 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -19,7 +19,7 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N # Map method to the request.XXX method. This is a simple hack, but it allows # requests to apply more magic per method. See lib/requests/api.py. - request_method = getattr(requests, method) + request_method = getattr(requests, method.lower()) try: # Request the URL From 63d3ab1cc1442aa93390e385466eb9797b15326c Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:06:02 +0200 Subject: [PATCH 07/16] Advanced option to turn off remote SSL certificate validation --- headphones/__init__.py | 7 ++++++- headphones/request.py | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 380a31bf..821fb202 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -292,6 +292,8 @@ JOURNAL_MODE = None UMASK = None +VERIFY_SSL_CERT = True + def CheckSection(sec): """ Check if INI section exists, if not create it """ try: @@ -360,7 +362,7 @@ def initialize(): XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \ PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \ PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \ - PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED + PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT if __INITIALIZED__: @@ -643,6 +645,8 @@ def initialize(): ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80) + VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', True)) + # update folder formats in the config & bump up config version if CONFIG_VERSION == '0': from headphones.helpers import replace_all @@ -1092,6 +1096,7 @@ def config_write(): new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB new_config['Advanced']['journal_mode'] = JOURNAL_MODE + new_config['Advanced']['verify_ssl_cert'] = VERIFY_SSL_CERT new_config.write() diff --git a/headphones/request.py b/headphones/request.py index bbb517a9..612d8679 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -5,6 +5,7 @@ from bs4 import BeautifulSoup import requests import feedparser +import headphones def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs): """ @@ -17,6 +18,10 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N if whitelist_status_code and type(whitelist_status_code) != list: whitelist_status_code = [whitelist_status_code] + # Disable verification of SSL certificates if requested. Note: this could + # pose a security issue! + kwargs["verify"] = headphones.VERIFY_SSL_CERT + # Map method to the request.XXX method. This is a simple hack, but it allows # requests to apply more magic per method. See lib/requests/api.py. request_method = getattr(requests, method.lower()) From e30cb2620049ddf50a8d7b88b544d03cd7383706 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:11:57 +0200 Subject: [PATCH 08/16] Clarified meaning of HTTP error codes. Will show if it's local or remote error --- headphones/request.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/headphones/request.py b/headphones/request.py index 612d8679..4edb8b2f 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -50,7 +50,15 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N logger.error("Request timed out.") except requests.HTTPError, e: if e.response is not None: - logger.error("Request raise HTTP error with status code: %d", e.response.status_code) + if e.response.status_code >= 500: + cause = "remote server error" + elif e.response.status_code >= 400: + cause = "local request error" + else: + # I don't think we will end up here, but for completeness + cause = "unknown" + + logger.error("Request raise HTTP error with status code %d (%s).", e.response.status_code, cause) else: logger.error("Request raised HTTP error.") except requests.RequestException, e: From 9b2594355fb9ab029139481a1a590314c60f61a2 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:16:58 +0200 Subject: [PATCH 09/16] Let the --quiet option don't silence --verbose if we want verbose --- Headphones.py | 6 +++--- headphones/__init__.py | 9 +++++---- headphones/logger.py | 10 ++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Headphones.py b/Headphones.py index 480caf08..ce67b2b1 100755 --- a/Headphones.py +++ b/Headphones.py @@ -77,9 +77,9 @@ def main(): args = parser.parse_args() if args.verbose: - headphones.VERBOSE = 2 - elif args.quiet: - headphones.VERBOSE = 0 + headphones.VERBOSE = True + if args.quiet: + headphones.QUIET = True if args.daemon: if sys.platform == 'win32': diff --git a/headphones/__init__.py b/headphones/__init__.py index 821fb202..3b785efc 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -38,7 +38,8 @@ SIGNAL = None SYS_PLATFORM = None SYS_ENCODING = None -VERBOSE = 1 +QUIET = True +VERBOSE = False DAEMON = False CREATEPID = False PIDFILE= None @@ -340,7 +341,7 @@ def initialize(): with INIT_LOCK: - global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \ + global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \ HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \ CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \ LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \ @@ -717,8 +718,8 @@ def initialize(): if VERBOSE: sys.stderr.write('Unable to create the log directory. Logging to screen only.\n') - # Start the logger, silence console logging if we need to - logger.initLogger(verbose=VERBOSE) + # Start the logger, disable console if needed + logger.initLogger(console=not QUIET, verbose=VERBOSE) if not CACHE_DIR: # Put the cache dir in the data dir for now diff --git a/headphones/logger.py b/headphones/logger.py index b2ea73db..0f2baf98 100644 --- a/headphones/logger.py +++ b/headphones/logger.py @@ -43,19 +43,21 @@ class LogListHandler(logging.Handler): headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName)) -def initLogger(verbose=1): +def initLogger(console=False, verbose=False): """ Setup logging for Headphones. It uses the logger instance with the name 'headphones'. Three log handlers are added: * RotatingFileHandler: for the file headphones.log * LogListHandler: for Web UI - * StreamHandler: for console (if verbose > 0) + * StreamHandler: for console (if console) + + Console logging is only enabled if console is set to True. """ # Configure the logger to accept all messages logger.propagate = False - logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO) + logger.setLevel(logging.DEBUG if verbose else logging.INFO) # Setup file logger filename = os.path.join(headphones.LOG_DIR, FILENAME) @@ -74,7 +76,7 @@ def initLogger(verbose=1): logger.addHandler(loglist_handler) # Setup console logger - if verbose: + if console: console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S') console_handler = logging.StreamHandler() console_handler.setFormatter(console_formatter) From 45868bc523213ed0afaa1272cefc1da1a3adf201 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:45:28 +0200 Subject: [PATCH 10/16] Toggle VERBOSE at runtime. Just surf to /toggleVerbose. Comes in handy for the ones who don't want to change startup scripts --- Headphones.py | 2 +- headphones/__init__.py | 3 ++- headphones/logger.py | 11 +++++++++++ headphones/webserve.py | 8 ++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Headphones.py b/Headphones.py index ce67b2b1..1a7c1df7 100755 --- a/Headphones.py +++ b/Headphones.py @@ -101,7 +101,7 @@ def main(): try: file(headphones.PIDFILE, 'w').write("pid\n") except IOError, e: - raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno)) + raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno) else: logger.warn("Not running in daemon mode. PID file creation disabled.") diff --git a/headphones/__init__.py b/headphones/__init__.py index 3b785efc..ac685164 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -38,7 +38,7 @@ SIGNAL = None SYS_PLATFORM = None SYS_ENCODING = None -QUIET = True +QUIET = False VERBOSE = False DAEMON = False CREATEPID = False @@ -720,6 +720,7 @@ def initialize(): # Start the logger, disable console if needed logger.initLogger(console=not QUIET, verbose=VERBOSE) + logger.initLogger(console=not QUIET, verbose=False) if not CACHE_DIR: # Put the cache dir in the data dir for now diff --git a/headphones/logger.py b/headphones/logger.py index 0f2baf98..4a21f3f8 100644 --- a/headphones/logger.py +++ b/headphones/logger.py @@ -55,6 +55,17 @@ def initLogger(console=False, verbose=False): Console logging is only enabled if console is set to True. """ + # Close and remove old handlers. This is required to reinit the loggers + # at runtime + for handler in logger.handlers[:]: + # Just make sure it is cleaned up. + if isinstance(handler, handlers.RotatingFileHandler): + handler.close() + elif isinstance(handler, logging.StreamHandler): + handler.flush() + + logger.removeHandler(handler) + # Configure the logger to accept all messages logger.propagate = False logger.setLevel(logging.DEBUG if verbose else logging.INFO) diff --git a/headphones/webserve.py b/headphones/webserve.py index 6db4c1f3..b4dd0c9a 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -736,6 +736,14 @@ class WebInterface(object): raise cherrypy.HTTPRedirect("logs") clearLogs.exposed = True + def toggleVerbose(self): + headphones.VERBOSE = not headphones.VERBOSE + logger.initLogger(not headphones.QUIET, headphones.VERBOSE) + logger.info("Verbose toggled, set to %s", headphones.VERBOSE) + logger.debug("If you read this message, debug logging is available") + raise cherrypy.HTTPRedirect("logs") + toggleVerbose.exposed = True + def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs): iDisplayStart = int(iDisplayStart) From f92f8928b08d0d4be062099a2fe849ed8dc8b12e Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 11:48:57 +0200 Subject: [PATCH 11/16] Remove the validator, since it returns a dict on error and a list on results. Validator always expected a dict --- headphones/searcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 1cc78b84..6dc6741f 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -537,8 +537,7 @@ def searchNZB(album, new=False, losslessOnly=False): data = request.request_json( url='http://api.omgwtfnzbs.org/json/', - params=params, headers=headers, - validator=lambda x: type(x) == dict + params=params, headers=headers ) # Parse response From 674617c0f80cb1f46c8f62e84d34851cab7bf5b4 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 12:09:26 +0200 Subject: [PATCH 12/16] Ensure correct types --- headphones/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index ac685164..a136efa8 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -646,7 +646,7 @@ def initialize(): ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80) - VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', True)) + VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1)) # update folder formats in the config & bump up config version if CONFIG_VERSION == '0': @@ -1098,7 +1098,7 @@ def config_write(): new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB new_config['Advanced']['journal_mode'] = JOURNAL_MODE - new_config['Advanced']['verify_ssl_cert'] = VERIFY_SSL_CERT + new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT) new_config.write() From fe2834f4a65b0c398d4430e9f78159db763dc639 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 May 2014 12:12:40 +0200 Subject: [PATCH 13/16] Be quiet when running daemon --- Headphones.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Headphones.py b/Headphones.py index 1a7c1df7..10ab2e44 100755 --- a/Headphones.py +++ b/Headphones.py @@ -85,8 +85,8 @@ def main(): if sys.platform == 'win32': print "Daemonize not supported under Windows, starting normally" else: - headphones.DAEMON=True - headphones.VERBOSE = False + headphones.DAEMON = True + headphones.QUIET = True if args.pidfile: headphones.PIDFILE = str(args.pidfile) From 10cf36401fd6befa15e769e1a2db5b4ac3bc05c0 Mon Sep 17 00:00:00 2001 From: BenM Date: Tue, 13 May 2014 21:11:31 -0400 Subject: [PATCH 14/16] Changed Add Artist and View MB --- data/interfaces/default/searchresults.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/searchresults.html b/data/interfaces/default/searchresults.html index b790f644..b6083c51 100644 --- a/data/interfaces/default/searchresults.html +++ b/data/interfaces/default/searchresults.html @@ -35,12 +35,12 @@ %if type == 'album': ${result['title']} %endif - ${result['uniquename']} + ${result['uniquename']}
${result['score']}
- %if type == 'album': + %if type == 'album': Add this album %else: - Add this artist + View on MusicBrainz %endif %endfor @@ -57,10 +57,10 @@ <%def name="javascriptIncludes()"> - +