From 7bbb137fd90aa83685db20c757a7f3dda8627223 Mon Sep 17 00:00:00 2001 From: delphiactual Date: Tue, 6 May 2014 02:12:17 -0600 Subject: [PATCH 01/23] uTorrent changes... --- headphones/__init__.py | 2 +- headphones/searcher.py | 31 +++++-- headphones/utorrent.py | 200 ++++++++++++++++++++++++++--------------- 3 files changed, 156 insertions(+), 77 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 380a31bf..40e085e1 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -38,7 +38,7 @@ SIGNAL = None SYS_PLATFORM = None SYS_ENCODING = None -VERBOSE = 1 +VERBOSE = 2 DAEMON = False CREATEPID = False PIDFILE= None diff --git a/headphones/searcher.py b/headphones/searcher.py index 20b5f16b..1cc78b84 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -21,6 +21,8 @@ from lib.pygazelle import encoding as gazelleencoding from lib.pygazelle import format as gazelleformat from lib.pygazelle import media as gazellemedia from xml.dom import minidom +from base64 import b16encode, b32decode +from hashlib import sha1 import os, re, time import string @@ -28,12 +30,13 @@ import shutil import requests import subprocess + import headphones from headphones.common import USER_AGENT from headphones import logger, db, helpers, classes, sab, nzbget, request from headphones import utorrent, transmission, notifiers -import lib.bencode as bencode +from lib.bencode import bencode as bencode, bdecode import headphones.searcher_rutracker as rutrackersearch rutracker = rutrackersearch.Rutracker() @@ -659,7 +662,7 @@ def send_to_downloader(data, bestqual, album): #Open the fresh torrent file again so we can extract the proper torrent name #Used later in post-processing. with open(download_path, 'rb') as fp: - torrent_info = bencode.bdecode(fp.read()) + torrent_info = bdecode(fp.read()) folder_name = torrent_info['info'].get('name', '') logger.info('Torrent folder name: %s' % folder_name) @@ -698,14 +701,16 @@ def send_to_downloader(data, bestqual, album): else: logger.info("Sending torrent to uTorrent") - + # 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] - folder_name = utorrent.addTorrent(file_or_url) + _hash = CalculateTorrentHash(file_or_url, data) + + folder_name = utorrent.addTorrent(file_or_url, _hash) if folder_name: logger.info('Torrent folder name: %s' % folder_name) @@ -1401,7 +1406,7 @@ def preprocess(resultlist): if result[4] == 'torrent': #Get out of here if we're using Transmission or uTorrent - if headphones.TORRENT_DOWNLOADER != 0: + 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 if result[3] == 'rutracker.org': @@ -1451,3 +1456,19 @@ def preprocess(resultlist): continue return (None, None) + + + +def CalculateTorrentHash(link, data): + + if link.startswith('magnet'): + tor_hash = re.findall('urn:btih:([\w]{32,40})', link)[0] + if len(tor_hash) == 32: + tor_hash = b16encode(b32decode(tor_hash)).lower() + else: + info = bdecode(data)["info"] + tor_hash = sha1(bencode(info)).hexdigest() + + logger.info('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 7aad16f5..3ce07906 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -13,86 +13,144 @@ # 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 base64 import headphones -import simplejson as json +from headphones import logger, notifiers -from headphones import logger, notifiers, request +class utorrentclient(object): + TOKEN_REGEX = "" -# This is just a simple script to send torrents to transmission. The -# intention is to turn this into a class where we can check the state -# of the download, set the download dir, etc. -# TODO: Store the session id so we don't need to make 2 calls -# Store torrent id so we can check up on it + def __init__(self, base_url = None, username = None, password = None,): + + host = headphones.UTORRENT_HOST + if not host.startswith('http'): + host = 'http://' + host + if host.endswith('/'): + host = host[:-1] -def addTorrent(link): + if host.endswith('/gui'): + host = host[:-4] + + self.base_url = host + self.username = headphones.UTORRENT_USERNAME + self.password = headphones.UTORRENT_PASSWORD + self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password) + self.token = self._get_token() + #TODO refresh token, when necessary + + def _make_opener(self, realm, base_url, username, password): + """uTorrent API need HTTP Basic Auth and cookie support for token verify.""" + auth = urllib2.HTTPBasicAuthHandler() + auth.add_password(realm=realm,uri=base_url,user=username,passwd=password) + opener = urllib2.build_opener(auth) + urllib2.install_opener(opener) + + cookie_jar = cookielib.CookieJar() + cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar) + + handlers = [auth, cookie_handler] + opener = urllib2.build_opener(*handlers) + return opener + + def _get_token(self): + url = urlparse.urljoin(self.base_url, 'gui/token.html') + try: + response = self.opener.open(url) + except urllib2.HTTPError as err: + logger.debug('URL: ' + str(url)) + logger.debug('Error getting Token. uTorrent responded with error: ' + str(err)) + match = re.search(utorrentclient.TOKEN_REGEX, response.read()) + return match.group(1) + + def list(self, **kwargs): + params = [('list', '1')] + params += kwargs.items() + return self._action(params) + + def add_url(self, url): + #can recieve magnet or normal .torrent link + params = [('action', 'add-url'), ('s', url)] + return self._action(params) + + def start(self, *hashes): + params = [('action', 'start'), ] + for hash in hashes: + params.append(('hash', hash)) + return self._action(params) + + def stop(self, *hashes): + params = [('action', 'stop'), ] + for hash in hashes: + params.append(('hash', hash)) + return self._action(params) + + def pause(self, *hashes): + params = [('action', 'pause'), ] + for hash in hashes: + params.append(('hash', hash)) + return self._action(params) + + def forcestart(self, *hashes): + params = [('action', 'forcestart'), ] + for hash in hashes: + params.append(('hash', hash)) + return self._action(params) + + def getfiles(self, hash): + params = [('action', 'getfiles'), ('hash', hash)] + return self._action(params) + + def getprops(self, hash): + params = [('action', 'getprops'), ('hash', hash)] + return self._action(params) + + def setprops(self, hash, s, v): + params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)] + return self._action(params) + + def setprio(self, hash, priority, *files): + params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))] + for file_index in files: + params.append(('f', str(file_index))) + + return self._action(params) + + def _action(self, params, body=None, content_type=None): + url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.urlencode(params) + request = urllib2.Request(url) + + if body: + request.add_data(body) + request.add_header('Content-length', len(body)) + if content_type: + request.add_header('Content-type', content_type) + + try: + response = self.opener.open(request) + return response.code, json.loads(response.read()) + except urllib2.HTTPError as err: + logger.debug('URL: ' + str(url)) + logger.debug('uTorrent webUI raised the following error: ' + str(err)) + +def addTorrent(link, hash): - host = headphones.UTORRENT_HOST - username = headphones.UTORRENT_USERNAME - password = headphones.UTORRENT_PASSWORD label = headphones.UTORRENT_LABEL - token = '' + uTorrentClient = utorrentclient() + uTorrentClient.add_url(link) + time.sleep(1) #need to ensure file is loaded uTorrent... + uTorrentClient.setprops(hash,'label', label) + torrentList = uTorrentClient.list() + for torrent in torrentList[1].get('torrents'): + if (torrent[0].lower()==hash): + return torrent[26] - if not host.startswith('http'): - host = 'http://' + host - - if host.endswith('/'): - host = host[:-1] - - if host.endswith('/gui'): - host = host + '/' - else: - host = host + '/gui/' - - # Retrieve session id - auth = (username, password) if username and password else None - token_request = request.request_response(host + 'token.html', auth=auth) - - token = re.findall('(.*?) Date: Tue, 6 May 2014 11:06:31 -0700 Subject: [PATCH 02/23] Changed VERBOSE back to 1 --- headphones/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 40e085e1..380a31bf 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -38,7 +38,7 @@ SIGNAL = None SYS_PLATFORM = None SYS_ENCODING = None -VERBOSE = 2 +VERBOSE = 1 DAEMON = False CREATEPID = False PIDFILE= None From 9fce4edf002620831e7a2f6bbee3ce5f63c6469c Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 6 May 2014 11:21:37 -0700 Subject: [PATCH 03/23] Fix for #1598, everything bring updated, even albums older than pause_delta --- headphones/importer.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/headphones/importer.py b/headphones/importer.py index d73293ef..6a7f79ed 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -231,22 +231,32 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): if not forcefull: + new_release_group = False + try: check_release_date = rg_exists['ReleaseDate'] except TypeError: check_release_date = None + new_release_group = True - if check_release_date: - if check_release_date[0] is None: + + if new_release_group: + + logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title'])) + new_releases = mb.get_new_releases(rgid,includeExtras) + + else: + + if check_release_date is None or check_release_date == u"None": logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title'])) new_releases = mb.get_new_releases(rgid,includeExtras,True) else: - if len(check_release_date[0]) == 10: - release_date = check_release_date[0] - elif len(check_release_date[0]) == 7: - release_date = check_release_date[0]+"-31" - elif len(check_release_date[0]) == 4: - release_date = check_release_date[0]+"-12-31" + if len(check_release_date) == 10: + release_date = check_release_date + elif len(check_release_date) == 7: + release_date = check_release_date+"-31" + elif len(check_release_date) == 4: + release_date = check_release_date+"-12-31" else: release_date = today if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta: @@ -256,9 +266,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta)) skip_log = 1 new_releases = 0 - else: - logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title'])) - new_releases = mb.get_new_releases(rgid,includeExtras) if force_repackage == 1: new_releases = -1 From bbde74db8d7f44a3de8844dafd6e3ef71299e482 Mon Sep 17 00:00:00 2001 From: Jens Rogier Date: Tue, 6 May 2014 20:41:18 +0200 Subject: [PATCH 04/23] 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 e10893543062a86bf310a54869bebec969c373c3 Mon Sep 17 00:00:00 2001 From: rembo10 Date: Tue, 6 May 2014 17:01:30 -0700 Subject: [PATCH 05/23] Fix for spinner not showing up anymore when marking albums as wanted --- data/interfaces/default/artist.html | 8 ++++---- data/interfaces/default/base.html | 2 +- data/interfaces/default/css/style.css | 12 ++++++------ data/interfaces/default/js/script.js | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index 863a4717..c5f21ccf 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -240,7 +240,8 @@ refreshTable(); $('#artistnamelink').text(data["ArtistName"]); if (loadingMessage == false){ - showMsg("Getting artist information",true); + $("#ajaxMsg").after( "
" ); + showArtistMsg("Getting artist information"); loadingMessage = true; } if (spinner_active == false){ @@ -255,8 +256,7 @@ else{ $('#artistnamespinner').remove() $('#loadingtext').remove() - $('#ajaxMsg').empty() - $('#ajaxMsg').removeAttr('style') + $('#ajaxMsg2').remove() spinner_active = false loadingtext_active = false loadingMessage = false @@ -303,7 +303,7 @@ resetFilters("albums"); setTimeout(function(){ initFancybox(); - },1500) + },1500); } $(document).ready(function() { diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 326a02cf..e606a02b 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -29,7 +29,7 @@
-
+
% if not headphones.CURRENT_VERSION:
You're running an unknown version of Headphones. Update or diff --git a/data/interfaces/default/css/style.css b/data/interfaces/default/css/style.css index 0bcf383f..6789726f 100644 --- a/data/interfaces/default/css/style.css +++ b/data/interfaces/default/css/style.css @@ -545,7 +545,7 @@ footer { position: relative; top: 4px; } -#ajaxMsg { +.ajaxMsg { border: 1px solid #cccccc; background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important; background-image: linear-gradient(#ffffff, #eeeeee) !important; @@ -576,16 +576,16 @@ footer { -o-opacity: 0.8 !important; opacity: 0.8 !important; } -#ajaxMsg .msg { +.ajaxMsg .msg { font-family: "Trebuchet MS", Helvetica, Arial, sans-serif; line-height: normal; padding-left: 20px; } -#ajaxMsg .loader { +.ajaxMsg .loader { position: relative; top: 2px; } -#ajaxMsg.success { +.ajaxMsg .success { background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important; background-image: linear-gradient(#d3ffd7, #c2edc6) !important; background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important; @@ -595,7 +595,7 @@ footer { padding: 15px 10px; text-align: left; } -#ajaxMsg.error { +.ajaxMsg .error { background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important; background-image: linear-gradient(#ffd3d3, #edc4c4) !important; background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important; @@ -605,7 +605,7 @@ footer { padding: 15px 10px; text-align: left; } -#ajaxMsg .ui-icon { +.ajaxMsg .ui-icon { display: inline-block; margin-left: -20px; top: 2px; diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index b2e56b52..33eec003 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -228,6 +228,21 @@ function showMsg(msg,loader,timeout,ms) { } } +function showArtistMsg(msg) { + var feedback = $("#ajaxMsg2"); + update = $("#updatebar"); + if ( update.is(":visible") ) { + var height = update.height() + 35; + feedback.css("bottom",height + "px"); + } else { + feedback.removeAttr("style"); + } + feedback.fadeIn(); + var message = $(" " + msg + "
"); + feedback.css("padding","14px 10px") + $(feedback).prepend(message); +} + function doAjaxCall(url,elem,reload,form) { // Set Message feedback = $("#ajaxMsg"); From 9da75a777b59092c92bc1ab174f54da6e3dc1cdf Mon Sep 17 00:00:00 2001 From: delphiactual Date: Tue, 6 May 2014 20:22:13 -0600 Subject: [PATCH 06/23] 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 07/23] 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 08/23] 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 98385ff4c8f423e17698b5e7f3fffeb516db42a8 Mon Sep 17 00:00:00 2001 From: Ade Date: Sat, 10 May 2014 09:32:05 +1200 Subject: [PATCH 09/23] Transmission change for rutracker use metainfo for local .torrent files (will only be rutracker) --- headphones/transmission.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/headphones/transmission.py b/headphones/transmission.py index e2e321b3..17ad434e 100644 --- a/headphones/transmission.py +++ b/headphones/transmission.py @@ -30,7 +30,14 @@ from headphones import logger, notifiers, request def addTorrent(link): method = 'torrent-add' - arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR} + + if link.endswith('.torrent'): + f = open(link,'rb') + metainfo = str(base64.b64encode(f.read())) + f.close() + arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR} + else: + arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR} response = torrentAction(method,arguments) From 6f3c8c68d4b3a5dc4d790460f70f2d62ada6da35 Mon Sep 17 00:00:00 2001 From: Ade Date: Sat, 10 May 2014 18:53:58 +1200 Subject: [PATCH 10/23] Lossless min/max filter filter results if calculated target size outside bitrate range --- data/interfaces/default/config.html | 26 +++++++++++++++++++++++--- headphones/__init__.py | 8 +++++++- headphones/searcher.py | 28 ++++++++++++++++++++++++++++ headphones/webserve.py | 8 ++++++-- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index d38fa710..9e48eadb 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -470,7 +470,14 @@
- + +
+ + Reject if target size is not in bitrate range: \ + to\ + kbps + +
Preferred Bitrate: kbps
Reject if less than % or more than % of the target size (leave blank for no limit)

@@ -1587,6 +1594,15 @@ } }); + if ($("#lossless_only").is(":checked")) + { + $("#lossless_only_options").show(); + } + else + { + $("#lossless_only_options").hide(); + } + if ($("#preferred_bitrate").is(":checked")) { $("#preferred_bitrate_options").show(); @@ -1632,18 +1648,22 @@ if ($("#preferred_bitrate").is(":checked")) { $("#preferred_bitrate_options").slideDown("fast"); + $("#lossless_only_options").slideUp("fast"); } if ($("#preferred_quality0").is(":checked")) { $("#preferred_bitrate_options").slideUp("fast"); + $("#lossless_only_options").slideUp("fast"); } if ($("#preferred_quality1").is(":checked")) { $("#preferred_bitrate_options").slideUp("fast"); + $("#lossless_only_options").slideUp("fast"); } - if ($("#preferred_quality3").is(":checked")) + if ($("#lossless_only").is(":checked")) { - $("#preferred_bitrate_options").slideUp("fast"); + $("#lossless_only_options").slideDown("fast"); + $("#preferred_bitrate_options").slideUp("fast"); } if ($("#nzb_downloader_sabnzbd").is(":checked")) { diff --git a/headphones/__init__.py b/headphones/__init__.py index 380a31bf..4a9ab38f 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -103,6 +103,8 @@ PREFERRED_BITRATE_HIGH_BUFFER = None PREFERRED_BITRATE_LOW_BUFFER = None PREFERRED_BITRATE_ALLOW_LOSSLESS = False DETECT_BITRATE = False +LOSSLESS_BITRATE_FROM = None +LOSSLESS_BITRATE_TO = None ADD_ARTISTS = False CORRECT_METADATA = False MOVE_FILES = False @@ -358,7 +360,7 @@ def initialize(): PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \ MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \ 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, \ + PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, 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 @@ -438,6 +440,8 @@ def initialize(): PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '') PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0)) DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0)) + LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '') + LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '') ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1)) CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0)) MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0)) @@ -862,6 +866,8 @@ def config_write(): new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS) new_config['General']['detect_bitrate'] = int(DETECT_BITRATE) + new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM + new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO new_config['General']['auto_add_artists'] = int(ADD_ARTISTS) new_config['General']['correct_metadata'] = int(CORRECT_METADATA) new_config['General']['move_files'] = int(MOVE_FILES) diff --git a/headphones/searcher.py b/headphones/searcher.py index 1cc78b84..8c2a699d 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -218,6 +218,34 @@ def sort_search_results(resultlist, album, new): finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) + # lossless - ignore results if target size outside bitrate range + elif headphones.PREFERRED_QUALITY == 3 and (headphones.LOSSLESS_BITRATE_FROM or headphones.LOSSLESS_BITRATE_TO): + + finallist = [] + tracks = myDB.select('SELECT TrackDuration from tracks WHERE AlbumID=?', [album['AlbumID']]) + + if len(tracks): + + albumlength = sum([pair[0] for pair in tracks]) + mintargetsize = 0 + maxtargetsize = 0 + if headphones.LOSSLESS_BITRATE_FROM: + mintargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_FROM) * 128 + if headphones.LOSSLESS_BITRATE_TO: + maxtargetsize = albumlength/1000 * int(headphones.LOSSLESS_BITRATE_TO) * 128 + + if mintargetsize > 0 or maxtargetsize > 0: + for i, result in reversed(list(enumerate(resultlist))): + if int(result[1]) < mintargetsize and mintargetsize > 0 or int(result[1]) > maxtargetsize and maxtargetsize > 0: + if int(result[1]) < mintargetsize: + logger.info("%s is too small for this album - not considering it. (Size: %s, Minsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(mintargetsize)) + else: + logger.info("%s is too large for this album - not considering it. (Size: %s, Maxsize: %s)", result[0], helpers.bytes_to_mb(result[1]), helpers.bytes_to_mb(maxtargetsize)) + del resultlist[i] + + if len (resultlist): + finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) + else: finallist = sorted(resultlist, key=lambda title: (title[5], int(title[1])), reverse=True) diff --git a/headphones/webserve.py b/headphones/webserve.py index 6db4c1f3..709de4f0 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -1000,6 +1000,8 @@ class WebInterface(object): "pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER, "pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS), "detect_bitrate" : checked(headphones.DETECT_BITRATE), + "lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM, + "lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO, "move_files" : checked(headphones.MOVE_FILES), "rename_files" : checked(headphones.RENAME_FILES), "correct_metadata" : checked(headphones.CORRECT_METADATA), @@ -1136,8 +1138,8 @@ class WebInterface(object): xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None, pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0, osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None, - preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None, - plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0, + preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, + file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0, songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ): headphones.HTTP_HOST = http_host @@ -1216,6 +1218,8 @@ class WebInterface(object): headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless headphones.DETECT_BITRATE = detect_bitrate + headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from + headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to headphones.MOVE_FILES = move_files headphones.CORRECT_METADATA = correct_metadata headphones.RENAME_FILES = rename_files From 8ab19f8121344651aeb9a643d76da89664a22e44 Mon Sep 17 00:00:00 2001 From: Ade Date: Sat, 10 May 2014 19:06:09 +1200 Subject: [PATCH 11/23] remove rutracker track count check should be able to filter out unwanted entries with Quality and Search Word settings --- headphones/searcher_rutracker.py | 220 +++++++++++++++---------------- 1 file changed, 107 insertions(+), 113 deletions(-) diff --git a/headphones/searcher_rutracker.py b/headphones/searcher_rutracker.py index 8837d205..3c799f24 100644 --- a/headphones/searcher_rutracker.py +++ b/headphones/searcher_rutracker.py @@ -18,6 +18,7 @@ from tempfile import mkdtemp class Rutracker(): logged_in = False + # Stores a number of login attempts to prevent recursion. #login_counter = 0 @@ -29,7 +30,7 @@ class Rutracker(): def login(self, login, password): """Implements tracker login procedure.""" - + self.logged_in = False if login is None or password is None: @@ -51,7 +52,6 @@ class Rutracker(): pass # Check if we're logged in - for cookie in self.cookiejar: if cookie.name == 'bb_data': self.logged_in = True @@ -64,7 +64,6 @@ class Rutracker(): """ # Build search url - searchterm = '' if artist != 'Various Artists': searchterm = artist @@ -82,8 +81,7 @@ class Rutracker(): else: format = '+mp3||aac' - # sort by size, descending. - + # sort by size, descending. sort = '&o=7&s=2' searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort) @@ -111,25 +109,21 @@ class Rutracker(): #logger.debug (soup.prettify()) # Title - for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}): title = link.get_text() titles.append(title) # Download URL - for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}): url = link.get('href') urls.append(url) # Seeders - for link in soup.find_all('b', attrs={'class' : 'seedmed'}): seeder = link.get_text() seeders.append(seeder) # Size - for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}): size = link.u.string sizes.append(size) @@ -138,30 +132,33 @@ class Rutracker(): pass # Combine lists - torrentlist = zip(titles, urls, seeders, sizes) # return if nothing found - if not torrentlist: return False - - # get headphones track count for album, return if not found - - myDB = db.DBConnection() - tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) - hptrackcount = len(tracks) - - if not hptrackcount: - logger.info('headphones track info not found, cannot compare to torrent') - return False - - # Return all valid entries, ignored, required words now checked in searcher.py - - #unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd'] - formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif'] - deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive'] + # don't bother checking track counts anymore, let searcher filter instead + # leave code in just in case + check_track_count = False + + if check_track_count: + + # get headphones track count for album, return if not found + myDB = db.DBConnection() + tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) + hptrackcount = len(tracks) + + if not hptrackcount: + logger.info('headphones track info not found, cannot compare to torrent') + return False + + # Return all valid entries, ignored, required words now checked in searcher.py + + #unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd'] + + formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif'] + deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive'] for torrent in torrentlist: @@ -169,105 +166,102 @@ class Rutracker(): url = torrent[1] seeders = torrent[2] size = torrent[3] - - title = returntitle.lower() - - if int(size) <= maxsize and int(seeders) >= minseeders: - - # Check torrent info - - torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t'] - self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)) - - # Debug - #for cookie in self.cookiejar: - # logger.debug ('Cookie: %s' % cookie) - - try: - page = self.opener.open(url) - torrent = page.read() - if torrent: - decoded = bencode.bdecode(torrent) - metainfo = decoded['info'] - page.close () - except Exception, e: - logger.error('Error getting torrent: %s' % e) - return False - - # get torrent track count and check for cue - - trackcount = 0 - cuecount = 0 - - if 'files' in metainfo: # multi - for pathfile in metainfo['files']: - path = pathfile['path'] - for file in path: - if any(file.lower().endswith('.' + x.lower()) for x in formatlist): - trackcount += 1 - if '.cue' in file: - cuecount += 1 - - #Torrent topic page - - topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id - logger.debug ('torrent title: %s' % title) - logger.debug ('headphones trackcount: %s' % hptrackcount) - logger.debug ('rutracker trackcount: %s' % trackcount) - # If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s) - # This is for the case where we have a single .flac/.wav which can be split by cue - # Not great, but shouldn't be doing this too often - - totallogcount = 0 - if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount: - page = self.opener.open(topicurl, timeout=60) - soup = BeautifulSoup(page.read()) - findtoc = soup.find_all(text='TOC of the extracted CD') - if not findtoc: - findtoc = soup.find_all(text='TOC извлечённого CD') - for toc in findtoc: - logcount = 0 - for toccontent in toc.find_all_next(text=True): - cut_string = toccontent.split('|') - new_string = cut_string[0].lstrip().rstrip() - if new_string == '1' or new_string == '01': - logcount = 1 - elif logcount > 0: - if new_string.isdigit(): - logcount += 1 - else: - break - totallogcount = totallogcount + logcount - - if totallogcount > 0: - trackcount = totallogcount - logger.debug ('rutracker logtrackcount: %s' % totallogcount) - - # If torrent track count = hp track count then return torrent, - # if greater, check for deluxe/special/foreign editions - # if less, then allow if it's a single track with a cue - - valid = False - - if trackcount == hptrackcount: + if int(size) <= maxsize and int(seeders) >= minseeders: + + #Torrent topic page + torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t'] + topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id + + # add to list + if not check_track_count: valid = True - elif trackcount > hptrackcount: - if any(deluxe in title for deluxe in deluxelist): + else: + + # Check torrent info + self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)) + + # Debug + #for cookie in self.cookiejar: + # logger.debug ('Cookie: %s' % cookie) + + try: + page = self.opener.open(url) + torrent = page.read() + if torrent: + decoded = bencode.bdecode(torrent) + metainfo = decoded['info'] + page.close () + except Exception, e: + logger.error('Error getting torrent: %s' % e) + return False + + # get torrent track count and check for cue + trackcount = 0 + cuecount = 0 + + if 'files' in metainfo: # multi + for pathfile in metainfo['files']: + path = pathfile['path'] + for file in path: + if any(file.lower().endswith('.' + x.lower()) for x in formatlist): + trackcount += 1 + if '.cue' in file: + cuecount += 1 + + title = returntitle.lower() + logger.debug ('torrent title: %s' % title) + logger.debug ('headphones trackcount: %s' % hptrackcount) + logger.debug ('rutracker trackcount: %s' % trackcount) + + # If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s) + # This is for the case where we have a single .flac/.wav which can be split by cue + # Not great, but shouldn't be doing this too often + totallogcount = 0 + if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount: + page = self.opener.open(topicurl, timeout=60) + soup = BeautifulSoup(page.read()) + findtoc = soup.find_all(text='TOC of the extracted CD') + if not findtoc: + findtoc = soup.find_all(text='TOC извлечённого CD') + for toc in findtoc: + logcount = 0 + for toccontent in toc.find_all_next(text=True): + cut_string = toccontent.split('|') + new_string = cut_string[0].lstrip().rstrip() + if new_string == '1' or new_string == '01': + logcount = 1 + elif logcount > 0: + if new_string.isdigit(): + logcount += 1 + else: + break + totallogcount = totallogcount + logcount + + if totallogcount > 0: + trackcount = totallogcount + logger.debug ('rutracker logtrackcount: %s' % totallogcount) + + # If torrent track count = hp track count then return torrent, + # if greater, check for deluxe/special/foreign editions + # if less, then allow if it's a single track with a cue + valid = False + + if trackcount == hptrackcount: valid = True + elif trackcount > hptrackcount: + if any(deluxe in title for deluxe in deluxelist): + valid = True # Add to list - if valid: rulist.append((returntitle, size, topicurl)) else: if topicurl: logger.info(u'Torrent found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount)) - else: logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders))) - return rulist def get_torrent(self, url, savelocation=None): From 52a76e87adcd33ab5cb873e787c9fa8465e19e12 Mon Sep 17 00:00:00 2001 From: Ade Date: Sun, 11 May 2014 19:35:41 +1200 Subject: [PATCH 12/23] Allow search provider names in preferred words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds to the priority if the word matches the search provider. The provider priority is weighted based on position in list. Preferred words in title take precedence over preferred providers. Would be great if we could do something like sb, so until then… --- data/interfaces/default/config.html | 2 +- headphones/searcher.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 9e48eadb..5ee7a95b 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -503,7 +503,7 @@
- Results with these words in the title will be preferred over results without them + Results with these words in the title will be preferred over results without them (search provider names can also be entered)
diff --git a/headphones/searcher.py b/headphones/searcher.py index 8c2a699d..d733a2f3 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -151,11 +151,20 @@ def sort_search_results(resultlist, album, new): # Add a priority if it has any of the preferred words temp_list = [] + preferred_words = None + if headphones.PREFERRED_WORDS: + preferred_words = helpers.split_string(headphones.PREFERRED_WORDS) for result in resultlist: - if headphones.PREFERRED_WORDS and any(word.lower() in result[0].lower() for word in helpers.split_string(headphones.PREFERRED_WORDS)): - temp_list.append((result[0],result[1],result[2],result[3],result[4],1)) - else: - temp_list.append((result[0],result[1],result[2],result[3],result[4],0)) + priority = 0 + if preferred_words: + if any(word.lower() in result[0].lower() for word in preferred_words): + priority = 1 + # add a search provider priority (weighted based on position) + i = next((i for i, word in enumerate(preferred_words) if word in result[3].lower()), None) + if i != None: + priority += round((len(preferred_words) - i) / float(len(preferred_words)),2) + + temp_list.append((result[0],result[1],result[2],result[3],result[4],priority)) resultlist = temp_list From e972a86233fc9a72ef4b9ca7ac482dd600c85511 Mon Sep 17 00:00:00 2001 From: delphiactual Date: Sun, 11 May 2014 21:26:58 -0600 Subject: [PATCH 13/23] 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 14/23] 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 15/23] 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 16/23] 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 17/23] 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 18/23] 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 19/23] 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 20/23] 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 21/23] 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 22/23] 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()"> - +