From a66aa5f41888e8d0102d5ea7f9a915fc5902ee57 Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 05:08:45 +0800 Subject: [PATCH 01/21] Fixed wanted albums showing Retry Download subhead --- data/interfaces/default/album.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/album.html b/data/interfaces/default/album.html index e8406f63..2308f32b 100644 --- a/data/interfaces/default/album.html +++ b/data/interfaces/default/album.html @@ -9,11 +9,11 @@ From bdd576f8b29c3d72f63c7aaa53b3c1c83bb12abb Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 05:09:37 +0800 Subject: [PATCH 02/21] Changed artist already in db message to debug --- headphones/importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/importer.py b/headphones/importer.py index 5ddbb20e..43726f59 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -96,7 +96,7 @@ def is_exists(artistid): artistlist = myDB.select('SELECT ArtistID, ArtistName from artists WHERE ArtistID=?', [artistid]) if any(artistid in x for x in artistlist): - logger.info(artistlist[0][1] + u" is already in the database. Updating 'have tracks', but not artist information") + logger.debug(artistlist[0][1] + u" is already in the database. Updating 'have tracks', but not artist information") return True else: return False From fd74af60ee9545e5f5407fb3f2ec6db33d0ff3b4 Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 05:13:30 +0800 Subject: [PATCH 03/21] Fixed default logging to show results by most recent --- data/interfaces/default/logs.html | 1 + 1 file changed, 1 insertion(+) diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index 8434e3b6..cc6436f3 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -57,6 +57,7 @@ "bStateSave": true, "iDisplayLength": 100, "sPaginationType": "full_numbers", + "aaSorting": [] }); }); From e70f893ff3e94edf6aa38df23aa3e9dcff3fd5a1 Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 9 Aug 2011 16:22:51 +0800 Subject: [PATCH 04/21] Fixed album title exception in post processor --- headphones/postprocessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 313f30af..e28033c3 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -306,7 +306,7 @@ def moveFiles(albumpath, release, tracks): os.chmod(temp_f, 0755) except Exception, e: - logger.error('Could not create folder for %s. Not moving' % release['AlbumName']) + logger.error('Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e)) return albumpath for r,d,f in os.walk(albumpath): From 2dd5ab94eee8e2f78bfd5331fd18e20e25b29981 Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 05:49:25 +0800 Subject: [PATCH 05/21] Fixed bug with log page not displaying if non-utf8 characters --- data/css/style.css | 2 +- data/interfaces/default/logs.html | 23 +++++++++-------------- headphones/__init__.py | 2 ++ headphones/helpers.py | 9 +++++++-- headphones/logger.py | 24 ++++++++++++++---------- headphones/webserve.py | 8 +------- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/data/css/style.css b/data/css/style.css index 511c8ae7..c8b0eb22 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -190,7 +190,7 @@ table#log_table { background-color: white; } table#log_table th#timestamp { text-align: left; min-width: 165px; } table#log_table th#level { text-align: left; min-width: 75px; } -table#log_table th#message { text-align: left; min-width: 200px; } +table#log_table th#message { text-align: left; min-width: 500px; } table#upcoming_table th#albumart { text-align: center; min-width: 50px; } table#upcoming_table th#albumname { text-align: center; min-width: 200px; } diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index cc6436f3..98004034 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -15,23 +15,18 @@ %for line in lineList: <% - out_tuple = helpers.extract_logline(line) + timestamp, message, level, threadname = line + + if level == 'WARNING' or level == 'ERROR': + grade = 'X' + else: + grade = 'Z' %> - %if out_tuple: - <% - if out_tuple[1] == 'DEBUG': - continue - elif out_tuple[1] == 'WARNING' or out_tuple[1] == 'ERROR': - grade = 'X' - else: - grade = 'Z' - %> - ${out_tuple[0]} - ${out_tuple[1]} - ${out_tuple[3].decode('utf-8')} + ${timestamp} + ${level} + ${message} - %endif %endfor diff --git a/headphones/__init__.py b/headphones/__init__.py index 47e8ed29..4671177a 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -37,6 +37,8 @@ CFG = None DB_FILE = None LOG_DIR = None +LOG_LIST = [] + CACHE_DIR = None HTTP_PORT = None diff --git a/headphones/helpers.py b/headphones/helpers.py index 9dc3aa10..4ecc6941 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -5,8 +5,6 @@ import re import headphones -from headphones import logger - def multikeysort(items, columns): comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns] @@ -91,6 +89,10 @@ def today(): yyyymmdd = datetime.date.isoformat(today) return yyyymmdd +def now(): + now = datetime.datetime.now() + return now.strftime("%Y-%m-%d %H:%M:%S") + def bytes_to_mb(bytes): mb = int(bytes)/1048576 @@ -103,6 +105,9 @@ def replace_all(text, dic): return text def extract_data(s): + + from headphones import logger + #headphones default format pattern = re.compile(r'(?P.*?)\s\-\s(?P.*?)\s\[(?P.*?)\]', re.VERBOSE) match = pattern.match(s) diff --git a/headphones/logger.py b/headphones/logger.py index f861a4c3..93c77c57 100644 --- a/headphones/logger.py +++ b/headphones/logger.py @@ -4,6 +4,7 @@ import logging from logging import handlers import headphones +from headphones import helpers MAX_SIZE = 1000000 # 1mb MAX_FILES = 5 @@ -45,33 +46,36 @@ class RotatingLogger(object): l.addHandler(consolehandler) def log(self, message, level): - + logger = logging.getLogger('headphones') threadname = threading.currentThread().getName() - message = threadname + ' : ' + message - if level == 'debug': + if level != 'DEBUG': + headphones.LOG_LIST.insert(0, (helpers.now(), message, level, threadname)) + + message = threadname + ' : ' + message + + if level == 'DEBUG': logger.debug(message) - elif level == 'info': + elif level == 'INFO': logger.info(message) - elif level == 'warn': + elif level == 'WARN': logger.warn(message) else: logger.error(message) - headphones_log = RotatingLogger('headphones.log', MAX_SIZE, MAX_FILES) def debug(message): - headphones_log.log(message, level='debug') + headphones_log.log(message, level='DEBUG') def info(message): - headphones_log.log(message, level='info') + headphones_log.log(message, level='INFO') def warn(message): - headphones_log.log(message, level='warn') + headphones_log.log(message, level='WARN') def error(message): - headphones_log.log(message, level='error') + headphones_log.log(message, level='ERROR') diff --git a/headphones/webserve.py b/headphones/webserve.py index fd406306..779f6da0 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -231,13 +231,7 @@ class WebInterface(object): history.exposed = True def logs(self): - log_file = os.path.join(headphones.LOG_DIR, 'headphones.log') - if os.path.isfile(log_file): - fileHandle = open(log_file) - lineList = fileHandle.readlines() - fileHandle.close() - lineList.reverse() - return serve_template(templatename="logs.html", title="Log", lineList=lineList[0:500]) + return serve_template(templatename="logs.html", title="Log", lineList=headphones.LOG_LIST) logs.exposed = True def clearhistory(self, type=None): From 24c6ea9e5bac6d0f2597d51dff773c8c067b824e Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 05:58:56 +0800 Subject: [PATCH 06/21] Added download directory scan interval as a config option in config.ini --- headphones/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index 4671177a..e5c8ba27 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -76,6 +76,7 @@ INCLUDE_EXTRAS = False NZB_SEARCH_INTERVAL = 360 LIBRARYSCAN_INTERVAL = 60 +DOWNLOAD_SCAN_INTERVAL = 5 SAB_HOST = None SAB_USERNAME = None @@ -158,7 +159,7 @@ def initialize(): CURRENT_VERSION, LATEST_VERSION, MUSIC_DIR, DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, \ CORRECT_METADATA, MOVE_FILES, RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, CLEANUP_FILES, INCLUDE_EXTRAS, \ ADD_ALBUM_ART, EMBED_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, NZB_SEARCH_INTERVAL, \ - LIBRARYSCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ + LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME @@ -211,6 +212,7 @@ def initialize(): NZB_SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'nzb_search_interval', 360) LIBRARYSCAN_INTERVAL = check_setting_int(CFG, 'General', 'libraryscan_interval', 180) + DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5) SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '') SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '') @@ -371,6 +373,7 @@ def config_write(): new_config['General']['nzb_search_interval'] = NZB_SEARCH_INTERVAL new_config['General']['libraryscan_interval'] = LIBRARYSCAN_INTERVAL + new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL new_config['SABnzbd'] = {} new_config['SABnzbd']['sab_host'] = SAB_HOST @@ -416,7 +419,7 @@ def start(): SCHED.add_interval_job(searcher.searchNZB, minutes=NZB_SEARCH_INTERVAL) SCHED.add_interval_job(importer.scanMusic, minutes=LIBRARYSCAN_INTERVAL) SCHED.add_interval_job(versioncheck.checkGithub, minutes=300) - SCHED.add_interval_job(postprocessor.checkFolder, minutes=5) + SCHED.add_interval_job(postprocessor.checkFolder, minutes=DOWNLOAD_SCAN_INTERVAL) SCHED.start() From 957a7522dfadf1824159effae95427eae714e724 Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 10:12:11 -0500 Subject: [PATCH 07/21] Added exceptions.py to throw our own exceptions. Attempted to mimic sickbeard exception handling of newzbin throttling. --- headphones/exceptions.py | 26 ++++++++++++++++++++++++++ headphones/searcher.py | 14 +++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 headphones/exceptions.py diff --git a/headphones/exceptions.py b/headphones/exceptions.py new file mode 100644 index 00000000..d591edff --- /dev/null +++ b/headphones/exceptions.py @@ -0,0 +1,26 @@ +def ex(e): + """ + Returns a string from the exception text if it exists. + """ + + # sanity check + if not e.args or not e.args[0]: + return "" + + e_message = e.args[0] + + # if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway + if not e_message: + try: + e_message = str(e.args[0]) + except: + e_message = "" + + return e_message + + +class HeadphonesException(Exception): + "Generic Headphones Exception - should never be thrown, only subclassed" + +class NewzbinAPIThrottled(HeadphonesException): + "Newzbin has throttled us, deal with it" diff --git a/headphones/searcher.py b/headphones/searcher.py index 00cbfeb0..c2070ee9 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -4,7 +4,7 @@ from xml.dom import minidom from xml.parsers.expat import ExpatError import os, re, time -import headphones +import headphones, exceptions from headphones import logger, db, helpers, classes, sab class NewzbinDownloader(urllib.FancyURLopener): @@ -31,11 +31,9 @@ class NewzbinDownloader(urllib.FancyURLopener): #raise exceptions.AuthException("Newzbin account not premium status, can't download NZBs") logger.info("Newzbin error 402") - logger.info("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + "seconds") - + logger.info("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + " seconds") time.sleep(int(result.group(1))) - - #raise exceptions.NewzbinAPIThrottled() + raise exceptions.NewzbinAPIThrottled() #this should be in a class somewhere def getNewzbinURL(url): @@ -459,6 +457,12 @@ def getresultNZB(result): nzb = urllib.urlopen(url, data=params).read() except urllib2.URLError, e: logger.warn('Error fetching nzb from url: %s. Error: %s' % (url, e)) + except exceptions.NewzbinAPIThrottled: + #TODO: This has created a potentially infinite loop? As long as they keep throttling we keep trying. + logger.info("Done waiting for Newzbin API throttle limit, starting downloads again") + getresultNZB(result) + except AttributeError: + logger.warn("AttributeError in getresultNZB.") else: try: nzb = urllib2.urlopen(result[2], timeout=30).read() From fd5d937cc4625109673564cc0b9da37e4b99e079 Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 10:16:38 -0500 Subject: [PATCH 08/21] Fix for TypeError: object of type 'NoneType' has no len() in Newzbin results. --- headphones/searcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index c2070ee9..3f7e77ee 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -304,7 +304,7 @@ def searchNZB(albumid=None, new=False): items = d.getElementsByTagName("item") except ExpatError: logger.info('Unable to get the NEWZBIN feed. Check that your settings are correct - post a bug if they are') - items = None + items = [] if len(items): From 5eac6d466ecfe945a43917c61af9b435288ac50b Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 10:25:31 -0500 Subject: [PATCH 09/21] More Newzbin throttling fixes. --- headphones/searcher.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index 3f7e77ee..c08ed287 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -23,6 +23,10 @@ class NewzbinDownloader(urllib.FancyURLopener): rtext = str(headers.getheader('X-DNZB-RText')) result = re.search("wait (\d+) seconds", rtext) + logger.info("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + " seconds") + time.sleep(int(result.group(1))) + raise exceptions.NewzbinAPIThrottled() + elif newzbinErrCode == 401: logger.info("Newzbin error 401") #raise exceptions.AuthException("Newzbin username or password incorrect") @@ -31,10 +35,6 @@ class NewzbinDownloader(urllib.FancyURLopener): #raise exceptions.AuthException("Newzbin account not premium status, can't download NZBs") logger.info("Newzbin error 402") - logger.info("Newzbin throttled our NZB downloading, pausing for " + result.group(1) + " seconds") - time.sleep(int(result.group(1))) - raise exceptions.NewzbinAPIThrottled() - #this should be in a class somewhere def getNewzbinURL(url): @@ -294,7 +294,11 @@ def searchNZB(albumid=None, new=False): "q": term } searchURL = providerurl + "search/?%s" % urllib.urlencode(params) - data = getNewzbinURL(searchURL) + try: + data = getNewzbinURL(searchURL) + except exceptions.NewzbinAPIThrottled: + #try again if we were throttled + data = getNewzbinURL(searchURL) if data: logger.info(u'Parsing results from %s' % (searchURL, providerurl)) From 45e38bc74f97bc548c4e48e67cef8a748b990801 Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 16:16:15 -0700 Subject: [PATCH 10/21] Reverted to style.css from master --- data/css/style.css | 596 ++++++++++++++++++++++----------------------- 1 file changed, 298 insertions(+), 298 deletions(-) mode change 100644 => 100755 data/css/style.css diff --git a/data/css/style.css b/data/css/style.css old mode 100644 new mode 100755 index 26651152..8b7803e7 --- a/data/css/style.css +++ b/data/css/style.css @@ -1,298 +1,298 @@ -/* HTML5 ✰ Boilerplate */ - -html, body, div, span, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, -small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, figcaption, figure, -footer, header, hgroup, menu, nav, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} - -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} - -blockquote, q { quotes: none; } -blockquote:before, blockquote:after, -q:before, q:after { content: ''; content: none; } -ins { background-color: #ff9; color: #000; text-decoration: none; } -mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } -del { text-decoration: line-through; } -abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } -table { border-collapse: collapse; border-spacing: 0; } -hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } -input, select { vertical-align: middle; } - -body { font:13px/1.231 sans-serif; *font-size:small; } -select, input, textarea, button { font:99% sans-serif; } -pre, code, kbd, samp { font-family: monospace, sans-serif; } - -html { overflow-y: scroll; } -a:hover, a:active { outline: none; } -ul, ol { margin-left: 2em; } -ol { list-style-type: decimal; } -nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } -small { font-size: 85%; } -strong, th { font-weight: bold; } -td { vertical-align: top; } - -sub, sup { font-size: 75%; line-height: 0; position: relative; } -sup { top: -0.5em; } -sub { bottom: -0.25em; } - -pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } -textarea { overflow: auto; } -.ie6 legend, .ie7 legend { margin-left: -7px; } -input[type="radio"] { vertical-align: text-bottom; } -input[type="checkbox"] { vertical-align: bottom; } -.ie7 input[type="checkbox"] { vertical-align: baseline; } -.ie6 input { vertical-align: text-bottom; } -label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } -button, input, select, textarea { margin: 0; } -input:valid, textarea:valid { } -input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } -.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } - -::-moz-selection{ background: grey; color:#fff; text-shadow: none; } -::selection { background: grey; color:#fff; text-shadow: none; } - -button { width: auto; overflow: visible; } -.ie7 img { -ms-interpolation-mode: bicubic; } - -body, select, input, textarea { color: #444; } -h1, h2, h3, h4, h5, h6 { font-weight: bold; } - -/* - // ========================================== \\ - || || - || Custom Styles || - || || - \\ ========================================== // -*/ - -a:link { - color: #5E2612; - text-decoration: none; - } -a:visited { - color: #5E2612; - text-decoration: none; - } -a:hover { /*this effect is not shown in NN4.xx*/ - color: #999999; - text-decoration: underline; - } -a:active {/*colour in NN4.xx is red*/ - color: #5E2612; - text-decoration: underline; - } -a.blue { - color: blue; - } - -container { } - -body { background-color: #EBF4FB; min-width: 935px; } - -header { min-height: 68px; width: 100%; min-width: 935px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } - -h1 { font-size: 24px; } -h2 { font-size: 20px; } -h3 { font-size: 16px; } - -p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; } -p.center { text-align: center; font-size: 18px; } -.smalltext2 { font-size: 11px; margin-left: 45px; } - -div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } -div#logo { float: left; padding-left: 10px; } - -ul#nav { margin: 25px 0 0 0; float: left; list-style-type: none; } -ul#nav li { margin: 40px 0px auto 10px; display: inline; } -ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } -ul#nav li a:hover { background-color: #a3e532; } - -div#subhead_container { height: 30px; width:100%; min-width: 1000px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } -ul#subhead_menu { margin-top: 5px; } -ul#subhead_menu li { width: 100%; height: 100%; display: inline; } -ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } -ul#subhead_menu li a:hover { width: 100%; height: 100%; background-color: #grey; } - -div#searchbar { margin: 24px 30px auto auto; float: right; } - -div#main { margin: 0; padding: 80px 0 0 0; } - -.table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } -.manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; } -.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; min-height: 100px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } -.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; min-height: 100px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } - -.configtable { font-size: 14px; line-height:18px; } -.configtable td { width: 350px; padding: 10px; vertical-align: middle; } -.configtable tr { vertical-align: text-top; } - -table#artist_table { background-color: white; width: 100%; padding: 20px; } - -table#artist_table th#name { text-align: left; min-width: 200px; } -table#artist_table th#status { text-align: left; min-width: 50px; } -table#artist_table th#album { text-align: left; min-width: 300px; } -table#artist_table th#have { text-align: center; } -table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } -table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; } -table#artist_table td#album { vertical-align: middle; text-align: left; min-width: 300px; } -table#artist_table td#have { vertical-align: middle; } - -div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } -div#nopaddingheader { font-size: 24px; font-weight: bold; text-align: center; } -table#album_table { background-color: white; } - -table#album_table th#select { vertical-align: middle; text-align: left; min-width: 25px; } -table#album_table th#albumart { text-align: left; min-width: 50px; } -table#album_table th#albumname { text-align: center; min-width: 150px; } -table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } -table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } -table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } -table#album_table td#select { vertical-align: middle; text-align: left; } -table#album_table td#albumart { vertical-align: middle; text-align: left; } -table#album_table td#albumname { vertical-align: middle; text-align: center; } -table#album_table td#reldate { vertical-align: middle; text-align: center; } -table#album_table td#status { vertical-align: middle; text-align: center; } -table#album_table td#type { vertical-align: middle; text-align: center; } -table#album_table td#have { vertical-align: middle; } - -img.albumArt { float: left; padding-right: 5px; } -div#albumheader { padding-top: 48px; height: 200px; } -div#track_wrapper { padding-top: 20px; text-align: center; font-size: 16px; } - -table#track_table th#number { text-align: right; min-width: 20px; } -table#track_table th#name { text-align: center; min-width: 350px; } -table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; } -table#track_table th#have { width: 175px; text-align: center; min-width: 100px; } - -table#track_table td#number { vertical-align: middle; text-align: right; } -table#track_table td#name { vertical-align: middle; text-align: center; } -table#track_table td#duration { vertical-align: middle; text-align: center; } -table#track_table td#have { vertical-align: middle; text-align: center; } - -table#history_table { background-color: white; width: 100%; } - -table#log_table { background-color: white; } - -table#log_table th#timestamp { text-align: left; min-width: 165px; } -table#log_table th#level { text-align: left; min-width: 75px; } -table#log_table th#message { text-align: left; min-width: 200px; } - -table#upcoming_table th#albumart { text-align: center; min-width: 50px; } -table#upcoming_table th#albumname { text-align: center; min-width: 200px; } -table#upcoming_table th#artistname { text-align: center; min-width: 150px; } -table#upcoming_table th#reldate { text-align: center; min-width: 100px; } -table#upcoming_table th#type { text-align: center; min-width: 75px; } - -table#upcoming_table td#select { vertical-align: middle; text-align: center; } -table#upcoming_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } -table#upcoming_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } -table#upcoming_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } -table#upcoming_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } -table#upcoming_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } -table#upcoming_table td#status { vertical-align: middle; text-align: center; } - -table#wanted_table th#albumart { text-align: center; min-width: 50px; } -table#wanted_table th#albumname { text-align: center; min-width: 200px; } -table#wanted_table th#artistname { text-align: center; min-width: 150px; } -table#wanted_table th#reldate { text-align: center; min-width: 100px; } -table#wanted_table th#type { text-align: center; min-width: 75px; } - -table#wanted_table td#select { vertical-align: middle; text-align: center; } -table#wanted_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } -table#wanted_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } -table#wanted_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } -table#wanted_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } -table#wanted_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } -table#wanted_table td#status { vertical-align: middle; text-align: center; } - -table#searchresults_table th#albumname { text-align: left; min-width: 225px; } -table#searchresults_table th#artistname { text-align: center; min-width: 325px; } -table#searchresults_table th#score { text-align: center; min-width: 75px; } - -table#searchresults_table td#albumname { vertical-align: middle; text-align: left; min-width: 200px; } -table#searchresults_table td#artistname { vertical-align: middle; text-align: left; min-width: 300px; } -table#searchresults_table td#score { vertical-align: middle; text-align: center; min-width: 75px; } - -div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } -div.progress-container > div { background-color: #a3e532; height: 14px; } -.havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; } - -footer { margin: 20px auto 20px auto; } -div#version { text-align: center; font-weight: bold; } -div#donate { text-align: center; margin: 20px auto 20px auto; } - -div#shutdown{ text-align: center; vertical-align: middle; } - -.cloudtag { padding-top: 30px; font-size:16px; } -#cloud a.tag1 { font-size: 0.7em; font-weight: 100; } -#cloud a.tag2 { font-size: 0.8em; font-weight: 200; } -#cloud a.tag3 { font-size: 0.9em; font-weight: 300; } -#cloud a.tag4 { font-size: 1.0em; font-weight: 400; } -#cloud a.tag5 { font-size: 1.2em; font-weight: 500; } -#cloud a.tag6 { font-size: 1.4em; font-weight: 600; } -#cloud a.tag7 { font-size: 1.6em; font-weight: 700; } -#cloud a.tag8 { font-size: 1.8em; font-weight: 800; } -#cloud a.tag9 { font-size: 2.2em; font-weight: 900; } -#cloud a.tag10 { font-size: 2.5em; font-weight: 900; } - -#cloud { padding: 2px; line-height: 1.5em; text-align: center; } -#cloud a { padding: 0px; } -#cloud { margin: 0; } -#cloud li { display: inline; } - - - -.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } -.hidden { display: none; visibility: hidden; } -.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } -.visuallyhidden.focusable:active, -.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } -.invisible { visibility: hidden; } -.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } -.clearfix:after { clear: both; } -.clearfix { zoom: 1; } - - -@media all and (orientation:portrait) { - -} - -@media all and (orientation:landscape) { - -} - -@media screen and (max-device-width: 480px) { - - html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } -} - - -@media print { - * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; - -ms-filter: none !important; } - a, a:visited { color: #444 !important; text-decoration: underline; } - a[href]:after { content: " (" attr(href) ")"; } - abbr[title]:after { content: " (" attr(title) ")"; } - .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } - pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } - thead { display: table-header-group; } - tr, img { page-break-inside: avoid; } - @page { margin: 0.5cm; } - p, h2, h3 { orphans: 3; widows: 3; } - h2, h3{ page-break-after: avoid; } -} +/* HTML5 ✰ Boilerplate */ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +blockquote, q { quotes: none; } +blockquote:before, blockquote:after, +q:before, q:after { content: ''; content: none; } +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +input, select { vertical-align: middle; } + +body { font:13px/1.231 sans-serif; *font-size:small; } +select, input, textarea, button { font:99% sans-serif; } +pre, code, kbd, samp { font-family: monospace, sans-serif; } + +html { overflow-y: scroll; } +a:hover, a:active { outline: none; } +ul, ol { margin-left: 2em; } +ol { list-style-type: decimal; } +nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } +small { font-size: 85%; } +strong, th { font-weight: bold; } +td { vertical-align: top; } + +sub, sup { font-size: 75%; line-height: 0; position: relative; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } + +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } +textarea { overflow: auto; } +.ie6 legend, .ie7 legend { margin-left: -7px; } +input[type="radio"] { vertical-align: text-bottom; } +input[type="checkbox"] { vertical-align: bottom; } +.ie7 input[type="checkbox"] { vertical-align: baseline; } +.ie6 input { vertical-align: text-bottom; } +label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } +button, input, select, textarea { margin: 0; } +input:valid, textarea:valid { } +input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } +.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } + +::-moz-selection{ background: grey; color:#fff; text-shadow: none; } +::selection { background: grey; color:#fff; text-shadow: none; } + +button { width: auto; overflow: visible; } +.ie7 img { -ms-interpolation-mode: bicubic; } + +body, select, input, textarea { color: #444; } +h1, h2, h3, h4, h5, h6 { font-weight: bold; } + +/* + // ========================================== \\ + || || + || Custom Styles || + || || + \\ ========================================== // +*/ + +a:link { + color: #5E2612; + text-decoration: none; + } +a:visited { + color: #5E2612; + text-decoration: none; + } +a:hover { /*this effect is not shown in NN4.xx*/ + color: #999999; + text-decoration: underline; + } +a:active {/*colour in NN4.xx is red*/ + color: #5E2612; + text-decoration: underline; + } +a.blue { + color: blue; + } + +container { } + +body { background-color: #EBF4FB; min-width: 935px; } + +header { min-height: 68px; width: 100%; min-width: 935px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; } + +h1 { font-size: 24px; } +h2 { font-size: 20px; } +h3 { font-size: 16px; } + +p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; } +p.center { text-align: center; font-size: 18px; } +.smalltext2 { font-size: 11px; margin-left: 45px; } + +div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; } +div#logo { float: left; padding-left: 10px; } + +ul#nav { margin: 25px 0 0 0; float: left; list-style-type: none; } +ul#nav li { margin: 40px 0px auto 10px; display: inline; } +ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; } +ul#nav li a:hover { background-color: #a3e532; } + +div#subhead_container { height: 30px; width:100%; min-width: 1000px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; } +ul#subhead_menu { margin-top: 5px; } +ul#subhead_menu li { width: 100%; height: 100%; display: inline; } +ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; } +ul#subhead_menu li a:hover { width: 100%; height: 100%; background-color: #grey; } + +div#searchbar { margin: 24px 30px auto auto; float: right; } + +div#main { margin: 0; padding: 80px 0 0 0; } + +.table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; } +.manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; } +.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; min-height: 100px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; } +.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; min-height: 100px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; } + +.configtable { font-size: 14px; line-height:18px; } +.configtable td { width: 350px; padding: 10px; } +.configtable td#middle { vertical-align: middle; } + +table#artist_table { background-color: white; width: 100%; padding: 20px; } + +table#artist_table th#name { text-align: left; min-width: 200px; } +table#artist_table th#status { text-align: left; min-width: 50px; } +table#artist_table th#album { text-align: left; min-width: 300px; } +table#artist_table th#have { text-align: center; } +table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; } +table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; } +table#artist_table td#album { vertical-align: middle; text-align: left; min-width: 300px; } +table#artist_table td#have { vertical-align: middle; } + +div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; } +div#nopaddingheader { font-size: 24px; font-weight: bold; text-align: center; } +table#album_table { background-color: white; } + +table#album_table th#select { vertical-align: middle; text-align: left; min-width: 25px; } +table#album_table th#albumart { text-align: left; min-width: 50px; } +table#album_table th#albumname { text-align: center; min-width: 150px; } +table#album_table th#reldate { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#status { width: 175px; text-align: center; min-width: 100px; } +table#album_table th#type { width: 175px; text-align: center; min-width: 100px; } +table#album_table td#select { vertical-align: middle; text-align: left; } +table#album_table td#albumart { vertical-align: middle; text-align: left; } +table#album_table td#albumname { vertical-align: middle; text-align: center; } +table#album_table td#reldate { vertical-align: middle; text-align: center; } +table#album_table td#status { vertical-align: middle; text-align: center; } +table#album_table td#type { vertical-align: middle; text-align: center; } +table#album_table td#have { vertical-align: middle; } + +img.albumArt { float: left; padding-right: 5px; } +div#albumheader { padding-top: 48px; height: 200px; } +div#track_wrapper { padding-top: 20px; text-align: center; font-size: 16px; } + +table#track_table th#number { text-align: right; min-width: 20px; } +table#track_table th#name { text-align: center; min-width: 350px; } +table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; } +table#track_table th#have { width: 175px; text-align: center; min-width: 100px; } + +table#track_table td#number { vertical-align: middle; text-align: right; } +table#track_table td#name { vertical-align: middle; text-align: center; } +table#track_table td#duration { vertical-align: middle; text-align: center; } +table#track_table td#have { vertical-align: middle; text-align: center; } + +table#history_table { background-color: white; width: 100%; } + +table#log_table { background-color: white; } + +table#log_table th#timestamp { text-align: left; min-width: 150px; } +table#log_table th#level { text-align: left; min-width: 60px; } +table#log_table th#message { text-align: left; min-width: 500px; } + +table#upcoming_table th#albumart { text-align: center; min-width: 50px; } +table#upcoming_table th#albumname { text-align: center; min-width: 200px; } +table#upcoming_table th#artistname { text-align: center; min-width: 150px; } +table#upcoming_table th#reldate { text-align: center; min-width: 100px; } +table#upcoming_table th#type { text-align: center; min-width: 75px; } + +table#upcoming_table td#select { vertical-align: middle; text-align: center; } +table#upcoming_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } +table#upcoming_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } +table#upcoming_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } +table#upcoming_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#upcoming_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } +table#upcoming_table td#status { vertical-align: middle; text-align: center; } + +table#wanted_table th#albumart { text-align: center; min-width: 50px; } +table#wanted_table th#albumname { text-align: center; min-width: 200px; } +table#wanted_table th#artistname { text-align: center; min-width: 150px; } +table#wanted_table th#reldate { text-align: center; min-width: 100px; } +table#wanted_table th#type { text-align: center; min-width: 75px; } + +table#wanted_table td#select { vertical-align: middle; text-align: center; } +table#wanted_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; } +table#wanted_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; } +table#wanted_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; } +table#wanted_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; } +table#wanted_table td#type { vertical-align: middle; text-align: center; min-width: 75px; } +table#wanted_table td#status { vertical-align: middle; text-align: center; } + +table#searchresults_table th#albumname { text-align: left; min-width: 225px; } +table#searchresults_table th#artistname { text-align: center; min-width: 325px; } +table#searchresults_table th#score { text-align: center; min-width: 75px; } + +table#searchresults_table td#albumname { vertical-align: middle; text-align: left; min-width: 200px; } +table#searchresults_table td#artistname { vertical-align: middle; text-align: left; min-width: 300px; } +table#searchresults_table td#score { vertical-align: middle; text-align: center; min-width: 75px; } + +div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; } +div.progress-container > div { background-color: #a3e532; height: 14px; } +.havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; } + +footer { margin: 20px auto 20px auto; } +div#version { text-align: center; font-weight: bold; } +div#donate { text-align: center; margin: 20px auto 20px auto; } + +div#shutdown{ text-align: center; vertical-align: middle; } + +.cloudtag { padding-top: 30px; font-size:16px; } +#cloud a.tag1 { font-size: 0.7em; font-weight: 100; } +#cloud a.tag2 { font-size: 0.8em; font-weight: 200; } +#cloud a.tag3 { font-size: 0.9em; font-weight: 300; } +#cloud a.tag4 { font-size: 1.0em; font-weight: 400; } +#cloud a.tag5 { font-size: 1.2em; font-weight: 500; } +#cloud a.tag6 { font-size: 1.4em; font-weight: 600; } +#cloud a.tag7 { font-size: 1.6em; font-weight: 700; } +#cloud a.tag8 { font-size: 1.8em; font-weight: 800; } +#cloud a.tag9 { font-size: 2.2em; font-weight: 900; } +#cloud a.tag10 { font-size: 2.5em; font-weight: 900; } + +#cloud { padding: 2px; line-height: 1.5em; text-align: center; } +#cloud a { padding: 0px; } +#cloud { margin: 0; } +#cloud li { display: inline; } + + + +.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } +.hidden { display: none; visibility: hidden; } +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } +.visuallyhidden.focusable:active, +.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } +.invisible { visibility: hidden; } +.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } + + +@media all and (orientation:portrait) { + +} + +@media all and (orientation:landscape) { + +} + +@media screen and (max-device-width: 480px) { + + html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } +} + + +@media print { + * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; + -ms-filter: none !important; } + a, a:visited { color: #444 !important; text-decoration: underline; } + a[href]:after { content: " (" attr(href) ")"; } + abbr[title]:after { content: " (" attr(title) ")"; } + .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } + pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } + thead { display: table-header-group; } + tr, img { page-break-inside: avoid; } + @page { margin: 0.5cm; } + p, h2, h3 { orphans: 3; widows: 3; } + h2, h3{ page-break-after: avoid; } +} \ No newline at end of file From 2183ce49b810072c7acff76bc66f85fc0dec3a2c Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 16:20:51 -0700 Subject: [PATCH 11/21] Fixed UnboundLocalError/nzb referenced before assignment --- headphones/searcher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/headphones/searcher.py b/headphones/searcher.py index c08ed287..d2a76fc2 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -453,6 +453,9 @@ def verifyresult(title, term): return True def getresultNZB(result): + + nzb = None + if result[3] == 'newzbin': params = urllib.urlencode({"username": headphones.NEWZBIN_UID, "password": headphones.NEWZBIN_PASSWORD, "reportid": result[2]}) url = "https://www.newzbin.com" + "/api/dnzb/" From b9d1412bb143bc0523814395171f558867d62ef0 Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 17:16:54 -0700 Subject: [PATCH 12/21] Config option for folder permissions --- headphones/__init__.py | 5 ++++- headphones/postprocessor.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/headphones/__init__.py b/headphones/__init__.py index e916057a..7f4d7e6b 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -105,6 +105,7 @@ LASTFM_USERNAME = None MEDIA_FORMATS = ["mp3", "flac", "aac", "ogg", "ape", "m4a"] INTERFACE = None +FOLDER_PERMISSIONS = None def CheckSection(sec): """ Check if INI section exists, if not create it """ @@ -163,7 +164,7 @@ def initialize(): ADD_ALBUM_ART, EMBED_ALBUM_ART, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, NZB_SEARCH_INTERVAL, \ LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ NZBMATRIX, NZBMATRIX_USERNAME, NZBMATRIX_APIKEY, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, \ - NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE + NZBSORG, NZBSORG_UID, NZBSORG_HASH, NEWZBIN, NEWZBIN_UID, NEWZBIN_PASSWORD, LASTFM_USERNAME, INTERFACE, FOLDER_PERMISSIONS if __INITIALIZED__: return False @@ -241,6 +242,7 @@ def initialize(): LASTFM_USERNAME = check_setting_str(CFG, 'General', 'lastfm_username', '') INTERFACE = check_setting_str(CFG, 'General', 'interface', 'default') + FOLDER_PERMISSIONS = check_setting_str(CFG, 'General', 'folder_permissions', '0755') if not LOG_DIR: LOG_DIR = os.path.join(DATA_DIR, 'logs') @@ -408,6 +410,7 @@ def config_write(): new_config['General']['lastfm_username'] = LASTFM_USERNAME new_config['General']['interface'] = INTERFACE + new_config['General']['folder_permissions'] = FOLDER_PERMISSIONS new_config.write() diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index d8b8cd14..fef6fbba 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -306,10 +306,12 @@ def moveFiles(albumpath, release, tracks): # Chmod the directories using the folder_format (script courtesy of premiso!) folder_list = folder.split('/') + + temp_f = os.path.join(headphones.DESTINATION_DIR); for f in folder_list: temp_f = os.path.join(temp_f, f) - os.chmod(temp_f, 0755) + os.chmod(temp_f, int(headphones.FOLDER_PERMISSIONS, 8)) except Exception, e: logger.error('Could not create folder for %s. Not moving: %s' % (release['AlbumTitle'], e)) From 7600016fe329b8c3881573744a8900eaf21366ad Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 17:18:15 -0700 Subject: [PATCH 13/21] Changed disambiguation message to debug --- headphones/mb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/mb.py b/headphones/mb.py index 1d6b20cd..9ded6600 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -47,7 +47,7 @@ def findArtist(name, limit=1): if result.artist.name != result.artist.getUniqueName() and limit == 1: - logger.info('Found an artist with a disambiguation: %s - doing an album based search' % name) + logger.debug('Found an artist with a disambiguation: %s - doing an album based search' % name) artistdict = findArtistbyAlbum(name) if not artistdict: From 74b8a88f29b6971eb795777fa88fa5d823822a76 Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 20:06:09 -0500 Subject: [PATCH 14/21] More substring result prevention. This time on the full term to prevent snatches on "Vol III" when we want "Vol II." --- headphones/searcher.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index c08ed287..c9f0fc68 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -345,7 +345,7 @@ def searchNZB(albumid=None, new=False): #when looking for "Foo - Foo" we don't want "Foobar" #this should be less of an issue when it isn't a self-titled album so we'll only check vs artist if len(resultlist): - resultlist[:] = [result for result in resultlist if verifyresult(result[0], artistterm)] + resultlist[:] = [result for result in resultlist if verifyresult(result[0], artistterm, term)] if len(resultlist): @@ -433,24 +433,31 @@ def searchNZB(albumid=None, new=False): myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", nzb_folder_name]) -def verifyresult(title, term): +def verifyresult(title, artistterm, term): title = re.sub('[\.\-\/\_]', ' ', title) - if term == 'Various Artists': - return True - - if not re.search('^' + re.escape(term), title, re.IGNORECASE): - logger.info("Removed from results: " + title + " (artist not at string start).") - return False - elif re.search(re.escape(term) + '\w', title, re.IGNORECASE | re.UNICODE): - logger.info("Removed from results: " + title + " (post substring result).") - return False - elif re.search('\w' + re.escape(term), title, re.IGNORECASE | re.UNICODE): - logger.info("Removed from results: " + title + " (pre substring result).") - return False - else: - return True + if artistterm <> 'Various Artists': + + if not re.search('^' + re.escape(artistterm), title, re.IGNORECASE): + logger.info("Removed from results: " + title + " (artist not at string start).") + return False + elif re.search(re.escape(artistterm) + '\w', title, re.IGNORECASE | re.UNICODE): + logger.info("Removed from results: " + title + " (post substring result).") + return False + elif re.search('\w' + re.escape(artistterm), title, re.IGNORECASE | re.UNICODE): + logger.info("Removed from results: " + title + " (pre substring result).") + return False + + #another attempt to weed out substrings. We don't want "Vol III" when we were looking for "Vol II" + tokens = re.split('\W', term, re.IGNORECASE | re.UNICODE) + for token in tokens: + if token == 'Various' or token == 'Artists' or token == 'VA': + continue + if not re.search('(?:\W|^)+' + token + '(?:\W|$)+', title, re.IGNORECASE | re.UNICODE): + logger.info("Removed from results: " + title + " (missing token: " + token + ")") + return False + return True def getresultNZB(result): if result[3] == 'newzbin': From 976311613b4e50031954805ab0b2d1dc348fce9d Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 20:11:14 -0500 Subject: [PATCH 15/21] Added '*' to the list of characters removed from search terms. This is a wildcard at NZBMatrix that was returning 100 hits. --- headphones/searcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/searcher.py b/headphones/searcher.py index c9f0fc68..2ac8015a 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -70,7 +70,7 @@ def searchNZB(albumid=None, new=False): except TypeError: year = '' - dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':''} + dic = {'...':'', ' & ':' ', ' = ': ' ', '?':'', '$':'s', ' + ':' ', '"':'', ',':'', '*':''} cleanalbum = helpers.latinToAscii(helpers.replace_all(albums[1], dic)) cleanartist = helpers.latinToAscii(helpers.replace_all(albums[0], dic)) From c2768ae1f06e3a9a487014909773eb65bf45f709 Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 20:37:30 -0500 Subject: [PATCH 16/21] Added special character '*' to list of exceptions in mb.findArtist query. Why are you only quoting the queries with special characters? Better results are had with quoting every time because of how lucene works imo --- headphones/mb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headphones/mb.py b/headphones/mb.py index 1d6b20cd..7acb339f 100644 --- a/headphones/mb.py +++ b/headphones/mb.py @@ -24,7 +24,7 @@ def findArtist(name, limit=1): attempt = 0 artistResults = None - chars = set('!?') + chars = set('!?*') if any((c in chars) for c in name): name = '"'+name+'"' From 05ee998de71cb307e58a98e62879e3735166d376 Mon Sep 17 00:00:00 2001 From: sbuser Date: Wed, 10 Aug 2011 20:48:09 -0500 Subject: [PATCH 17/21] Error message for people who don't set their download directory and then wonder why there's no post-processing. --- headphones/postprocessor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index d8b8cd14..a7c4a737 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -442,6 +442,7 @@ def renameUnprocessedFolder(albumpath): def forcePostProcess(): if not headphones.DOWNLOAD_DIR: + logger.error('No DOWNLOAD_DIR has been set. Set "Music Download Directory:" to your SAB download directory on the settings page.') return else: download_dir = headphones.DOWNLOAD_DIR From 8dddc92e9d86b6d0c7b7c6ed0a547b73380bf03b Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 20:22:09 -0700 Subject: [PATCH 18/21] Took out the str(download_dir)/unicode escape clause in postprocessor, changed fallback retention to 2000 days, changed if term <> various artists to if term != various artists --- headphones/postprocessor.py | 9 ++++----- headphones/searcher.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index d84f1aac..7d72e520 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -111,11 +111,6 @@ def verify(albumid, albumpath): tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid]) downloaded_track_list = [] - - try: - albumpath = str(albumpath) - except UnicodeEncodeError: - albumpath = unicode(albumpath).encode('unicode_escape') for r,d,f in os.walk(albumpath): for files in f: @@ -203,6 +198,10 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list) if headphones.MOVE_FILES and headphones.DESTINATION_DIR: albumpath = moveFiles(albumpath, release, tracks) + if headphones.MOVE_FILES and not headphones.DESTINATION_DIR: + logger.error('No DESTINATION_DIR has been set. Set "Destination Directory" to the parent directory you want to move the files to') + pass + myDB = db.DBConnection() # There's gotta be a better way to update the have tracks - sqlite diff --git a/headphones/searcher.py b/headphones/searcher.py index 6fbb2a09..c83f975d 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -437,7 +437,7 @@ def verifyresult(title, artistterm, term): title = re.sub('[\.\-\/\_]', ' ', title) - if artistterm <> 'Various Artists': + if artistterm != 'Various Artists': if not re.search('^' + re.escape(artistterm), title, re.IGNORECASE): logger.info("Removed from results: " + title + " (artist not at string start).") @@ -487,7 +487,7 @@ def getresultNZB(result): def preprocess(resultlist): if not headphones.USENET_RETENTION: - usenet_retention = 1000 + usenet_retention = 2000 else: usenet_retention = int(headphones.USENET_RETENTION) From eae79a35727933a930adfd85a91c84c9824fe65a Mon Sep 17 00:00:00 2001 From: Remy Date: Wed, 10 Aug 2011 22:20:59 -0700 Subject: [PATCH 19/21] Possible fix for unicodedecodeerrors in post processor --- headphones/postprocessor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 7d72e520..2b1f87c9 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -461,8 +461,11 @@ def forcePostProcess(): # Parse the folder names to get artist album info for folder in folders: + + folder = unicode(folder) - albumpath = unicode(os.path.join(download_dir, folder)) + albumpath = os.path.join(download_dir, folder) + try: name, album, year = helpers.extract_data(folder) except: @@ -484,7 +487,6 @@ def forcePostProcess(): logger.error('Can not get release information for this album') continue if rgid: - rgid = unicode(rgid) verify(rgid, albumpath) \ No newline at end of file From 4e8c837f733236d30b6201b4e6a74f05496adc35 Mon Sep 17 00:00:00 2001 From: Remy Date: Thu, 11 Aug 2011 00:42:27 -0700 Subject: [PATCH 20/21] Added Manage Artist section under Manage --- data/css/style.css | 1 + data/interfaces/default/artist.html | 2 +- data/interfaces/default/manage.html | 10 ++++ data/interfaces/default/manageartists.html | 69 ++++++++++++++++++++++ headphones/webserve.py | 28 +++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 data/interfaces/default/manageartists.html diff --git a/data/css/style.css b/data/css/style.css index 8b7803e7..552c9a5b 100755 --- a/data/css/style.css +++ b/data/css/style.css @@ -143,6 +143,7 @@ div#main { margin: 0; padding: 80px 0 0 0; } table#artist_table { background-color: white; width: 100%; padding: 20px; } +table#artist_table th#select { text-align: left; } table#artist_table th#name { text-align: left; min-width: 200px; } table#artist_table th#status { text-align: left; min-width: 50px; } table#artist_table th#album { text-align: left; min-width: 300px; } diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index 241df532..4b296eda 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -74,7 +74,7 @@ %> - + ${album['AlbumTitle']} ${album['ReleaseDate']} diff --git a/data/interfaces/default/manage.html b/data/interfaces/default/manage.html index 8b891317..3e6f7452 100644 --- a/data/interfaces/default/manage.html +++ b/data/interfaces/default/manage.html @@ -2,8 +2,18 @@ <%! import headphones %> +<%def name="headerIncludes()"> +
+ +
+ <%def name="body()"> +
+

+

Scan Music Library


Where do you keep your music?

diff --git a/data/interfaces/default/manageartists.html b/data/interfaces/default/manageartists.html new file mode 100644 index 00000000..d1762cb2 --- /dev/null +++ b/data/interfaces/default/manageartists.html @@ -0,0 +1,69 @@ +<%inherit file="base.html" /> + +<%def name="body()"> +
+

Manage Artists

+

+
+

+ + selected artists + +

+ + + + + + + + + + %for artist in artists: + <% + if artist['Status'] == 'Paused': + grade = 'X' + elif artist['Status'] == 'Loading': + grade = 'C' + else: + grade = 'Z' + %> + + + + + + %endfor + +
Artist NameStatus
${artist['ArtistName']}${artist['Status']}
+
+ + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file diff --git a/headphones/webserve.py b/headphones/webserve.py index 7be70060..c03c2790 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -183,6 +183,34 @@ class WebInterface(object): return serve_template(templatename="manage.html", title="Manage") manage.exposed = True + def manageArtists(self): + myDB = db.DBConnection() + artists = myDB.select('SELECT * from artists order by ArtistSortName COLLATE NOCASE') + return serve_template(templatename="manageartists.html", title="Manage Artists", artists=artists) + manageArtists.exposed = True + + def markArtists(self, action=None, **args): + myDB = db.DBConnection() + for ArtistID in args: + if action == 'delete': + myDB.action('DELETE from artists WHERE ArtistID=?', [ArtistID]) + myDB.action('DELETE from albums WHERE ArtistID=?', [ArtistID]) + myDB.action('DELETE from tracks WHERE ArtistID=?', [ArtistID]) + elif action == 'pause': + controlValueDict = {'ArtistID': ArtistID} + newValueDict = {'Status': 'Paused'} + myDB.upsert("artists", newValueDict, controlValueDict) + elif action == 'resume': + controlValueDict = {'ArtistID': ArtistID} + newValueDict = {'Status': 'Active'} + myDB.upsert("artists", newValueDict, controlValueDict) + else: + # These may and probably will collide - need to make a better way to queue musicbrainz queries + threading.Thread(target=importer.addArtisttoDB, args=[ArtistID]).start() + time.sleep(30) + raise cherrypy.HTTPRedirect("home") + markArtists.exposed = True + def importLastFM(self, username): headphones.LASTFM_USERNAME = username headphones.config_write() From 4a233f01267cda81c6e141ae2db5ef8d28454094 Mon Sep 17 00:00:00 2001 From: Remy Date: Thu, 11 Aug 2011 00:44:29 -0700 Subject: [PATCH 21/21] Updated remix template to reflect changes in default template --- data/interfaces/remix/artist.html | 2 +- data/interfaces/remix/manage.html | 10 ++++ data/interfaces/remix/manageartists.html | 69 ++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 data/interfaces/remix/manageartists.html diff --git a/data/interfaces/remix/artist.html b/data/interfaces/remix/artist.html index 241df532..4b296eda 100644 --- a/data/interfaces/remix/artist.html +++ b/data/interfaces/remix/artist.html @@ -74,7 +74,7 @@ %> - + ${album['AlbumTitle']} ${album['ReleaseDate']} diff --git a/data/interfaces/remix/manage.html b/data/interfaces/remix/manage.html index 8b891317..3e6f7452 100644 --- a/data/interfaces/remix/manage.html +++ b/data/interfaces/remix/manage.html @@ -2,8 +2,18 @@ <%! import headphones %> +<%def name="headerIncludes()"> +
+ +
+ <%def name="body()"> +
+

+

Scan Music Library


Where do you keep your music?

diff --git a/data/interfaces/remix/manageartists.html b/data/interfaces/remix/manageartists.html new file mode 100644 index 00000000..d1762cb2 --- /dev/null +++ b/data/interfaces/remix/manageartists.html @@ -0,0 +1,69 @@ +<%inherit file="base.html" /> + +<%def name="body()"> +
+

Manage Artists

+

+
+

+ + selected artists + +

+ + + + + + + + + + %for artist in artists: + <% + if artist['Status'] == 'Paused': + grade = 'X' + elif artist['Status'] == 'Loading': + grade = 'C' + else: + grade = 'Z' + %> + + + + + + %endfor + +
Artist NameStatus
${artist['ArtistName']}${artist['Status']}
+
+ + +<%def name="headIncludes()"> + + + +<%def name="javascriptIncludes()"> + + + \ No newline at end of file