diff --git a/headphones/__init__.py b/headphones/__init__.py index 2bb5e983..616c42a3 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -169,7 +169,7 @@ def initialize(config_file): fp.write(CURRENT_VERSION) except IOError as e: logger.error("Unable to write current version to file '%s': %s", - version_lock_file, e) + version_lock_file, e) # Check for new versions if CONFIG.CHECK_GITHUB_ON_STARTUP: @@ -297,8 +297,8 @@ def initialize_scheduler(): # Remove Torrent + data if Post Processed and finished Seeding if CONFIG.TORRENT_REMOVAL_INTERVAL > 0: SCHED.add_job(torrentfinished.checkTorrentFinished, - trigger=IntervalTrigger( - minutes=CONFIG.TORRENT_REMOVAL_INTERVAL)) + trigger=IntervalTrigger( + minutes=CONFIG.TORRENT_REMOVAL_INTERVAL)) # Start scheduler logger.info("(Re-)Scheduling background tasks") diff --git a/headphones/cuesplit.py b/headphones/cuesplit.py index 19414332..d2f9da7d 100755 --- a/headphones/cuesplit.py +++ b/headphones/cuesplit.py @@ -424,7 +424,7 @@ class CueFile(File): elif t >= 1: t_index = self.tracks[t]['index'] content += t_index[1] - if (t < len(self.tracks) - 1): + if t < (len(self.tracks) - 1): content += '\n' return content diff --git a/headphones/db.py b/headphones/db.py index cf4cdeae..02d003e9 100644 --- a/headphones/db.py +++ b/headphones/db.py @@ -96,11 +96,16 @@ class DBConnection: genParams = lambda myDict: [x + " = ?" for x in myDict.keys()] - query = "UPDATE " + tableName + " SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict)) + update_query = "UPDATE " + tableName + " SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict)) - self.action(query, valueDict.values() + keyDict.values()) + self.action(update_query, valueDict.values() + keyDict.values()) if self.connection.total_changes == changesBefore: - query = "INSERT INTO " + tableName + " (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \ - " VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")" - self.action(query, valueDict.values() + keyDict.values()) + insert_query = ( + "INSERT INTO " + tableName + " (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + + " VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")" + ) + try: + self.action(insert_query, valueDict.values() + keyDict.values()) + except sqlite3.IntegrityError: + logger.info('Queries failed: %s and %s', update_query, insert_query) diff --git a/headphones/helpers.py b/headphones/helpers.py index 7823bd59..bc15b015 100644 --- a/headphones/helpers.py +++ b/headphones/helpers.py @@ -65,7 +65,8 @@ def latinToAscii(unicrap): """ From couch potato """ - xlate = {0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A', + xlate = { + 0xc0: 'A', 0xc1: 'A', 0xc2: 'A', 0xc3: 'A', 0xc4: 'A', 0xc5: 'A', 0xc6: 'Ae', 0xc7: 'C', 0xc8: 'E', 0xc9: 'E', 0xca: 'E', 0xcb: 'E', 0x86: 'e', 0xcc: 'I', 0xcd: 'I', 0xce: 'I', 0xcf: 'I', @@ -90,7 +91,7 @@ def latinToAscii(unicrap): 0xb9: '{^1}', 0xba: '{^o}', 0xbb: '>>', 0xbc: '{1/4}', 0xbd: '{1/2}', 0xbe: '{3/4}', 0xbf: '?', 0xd7: '*', 0xf7: '/' - } + } r = '' for i in unicrap: diff --git a/headphones/importer.py b/headphones/importer.py index 758470ad..4730a8c7 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -48,7 +48,7 @@ def artistlist_to_mbids(artistlist, forced=False): for artist in artistlist: - if not artist and not (artist == ' '): + if not artist and artist != ' ': continue # If adding artists through Manage New Artists, they're coming through as non-unicode (utf-8?) @@ -466,7 +466,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False): myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']]) marked_as_downloaded = True else: - if ((have_track_count / float(total_track_count)) >= (headphones.CONFIG.ALBUM_COMPLETION_PCT / 100.0)): + if (have_track_count / float(total_track_count)) >= (headphones.CONFIG.ALBUM_COMPLETION_PCT / 100.0): myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', rg['id']]) marked_as_downloaded = True diff --git a/headphones/lock.py b/headphones/lock.py index 90d860e0..2fcc7ab7 100644 --- a/headphones/lock.py +++ b/headphones/lock.py @@ -2,6 +2,7 @@ Locking-related classes """ +import headphones.logger import time import threading import Queue @@ -38,10 +39,12 @@ class TimedLock(object): sleep_amount = self.minimum_delta - delta if sleep_amount >= 0: # zero sleeps give the cpu a chance to task-switch + headphones.logger.info('Sleeping %s (interval)', sleep_amount) time.sleep(sleep_amount) while not self.queue.empty(): try: seconds = self.queue.get(False) + headphones.logger.info('Sleeping %s (queued)', seconds) time.sleep(seconds) except Queue.Empty: continue @@ -63,6 +66,7 @@ class TimedLock(object): """ # we use a queue so that we don't have to synchronize # across threads and with or without locks + headphones.logger.info('Adding %s to queue', seconds) self.queue.add(seconds) diff --git a/headphones/music_encoder.py b/headphones/music_encoder.py index 1612a695..286a47e0 100644 --- a/headphones/music_encoder.py +++ b/headphones/music_encoder.py @@ -66,7 +66,7 @@ def encode(albumPath): xldInfoMusic = MediaFile(xldMusicFile) encoderFormat = xldFormat - if (headphones.CONFIG.ENCODERLOSSLESS): + if headphones.CONFIG.ENCODERLOSSLESS: ext = os.path.normpath(os.path.splitext(music)[1].lstrip(".")).lower() if not use_xld and ext == 'flac' or use_xld and (ext != xldFormat and (xldInfoMusic.bitrate / 1000 > 400)): musicFiles.append(os.path.join(r, music)) @@ -118,7 +118,7 @@ def encode(albumPath): if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]): logger.warn('Lame cannot encode %s format for %s, use ffmpeg', os.path.splitext(music)[1], music) else: - if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE)): + if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE): logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.CONFIG.BITRATE) else: encode = True @@ -128,8 +128,8 @@ def encode(albumPath): logger.warn('Cannot re-encode .ogg %s', music.decode(headphones.SYS_ENCODING, 'replace')) else: encode = True - elif (headphones.CONFIG.ENCODEROUTPUTFORMAT == 'mp3' or headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a'): - if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + headphones.CONFIG.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE)): + elif headphones.CONFIG.ENCODEROUTPUTFORMAT == 'mp3' or headphones.CONFIG.ENCODEROUTPUTFORMAT == 'm4a': + if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + headphones.CONFIG.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000) <= headphones.CONFIG.BITRATE): logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.CONFIG.BITRATE) else: encode = True diff --git a/headphones/nzbget.py b/headphones/nzbget.py index 50240f35..4ed18b7e 100644 --- a/headphones/nzbget.py +++ b/headphones/nzbget.py @@ -59,7 +59,7 @@ def sendNZB(nzb): return False except xmlrpclib.ProtocolError, e: - if (e.errmsg == "Unauthorized"): + if e.errmsg == "Unauthorized": logger.error(u"NZBget password is incorrect.") else: logger.error(u"Protocol Error: " + e.errmsg) diff --git a/headphones/request.py b/headphones/request.py index 22ba09bf..26da11b4 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -68,8 +68,9 @@ def request_response(url, method="get", auto_raise=True, try: response.raise_for_status() except: - logger.debug("Response status code %d is not white " \ - "listed, raised exception", response.status_code) + logger.debug( + "Response status code %d is not white " + "listed, raised exception", response.status_code) raise elif auto_raise: response.raise_for_status() @@ -85,7 +86,7 @@ def request_response(url, method="get", auto_raise=True, "host is up and running.") except requests.Timeout: logger.error( - "Request timed out. The remote host did not respeond timely.") + "Request timed out. The remote host did not respond timely.") except requests.HTTPError as e: if e.response is not None: if e.response.status_code >= 500: diff --git a/headphones/searcher.py b/headphones/searcher.py index 5de7d785..5c1765b0 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -593,7 +593,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None): logger.info("Album type is audiobook/spokenword. Using audiobook category") # Request results - logger.info('Parsing results from nzbs.org') + logger.info('Requesting from nzbs.org') headers = {'User-Agent': USER_AGENT} params = { @@ -606,9 +606,11 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None): data = request.request_feed( url='http://beta.nzbs.org/api', - params=params, headers=headers + params=params, headers=headers, + timeout=5 ) + logger.info('Parsing results from nzbs.org') # Process feed if data: if not len(data.entries): diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 67d9bad7..e74f6958 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -215,7 +215,7 @@ def dirTorrent(hash, cacheid=None, return_name=None): cacheid = torrentList['torrentc'] for torrent in torrents: - if (torrent[0].lower() == hash): + if torrent[0].lower() == hash: if not return_name: return torrent[26], cacheid else: diff --git a/headphones/webserve.py b/headphones/webserve.py index 7763f81f..d54c3a56 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -23,13 +23,15 @@ from mako import exceptions from operator import itemgetter +import cherrypy +import headphones +import json import os import sys -import json import time -import cherrypy +import urllib +import urllib2 import threading -import headphones try: # pylint:disable=E0611 @@ -70,23 +72,21 @@ class WebInterface(object): def artistPage(self, ArtistID): myDB = db.DBConnection() artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone() - albums = myDB.select('SELECT * from albums WHERE ArtistID=? order by ReleaseDate DESC', [ArtistID]) # Don't redirect to the artist page until it has the bare minimum info inserted # Redirect to the home page if we still can't get it after 5 seconds retry = 0 - while retry < 5: - if not artist: - time.sleep(1) - artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone() - retry += 1 - else: - break + while not artist and retry < 5: + time.sleep(1) + artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone() + retry += 1 if not artist: raise cherrypy.HTTPRedirect("home") + albums = myDB.select('SELECT * from albums WHERE ArtistID=? order by ReleaseDate DESC', [ArtistID]) + # Serve the extras up as a dict to make things easier for new templates (append new extras to the end) extras_list = headphones.POSSIBLE_EXTRAS if artist['Extras']: @@ -110,8 +110,6 @@ class WebInterface(object): def albumPage(self, AlbumID): myDB = db.DBConnection() album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone() - tracks = myDB.select('SELECT * from tracks WHERE AlbumID=? ORDER BY CAST(TrackNumber AS INTEGER)', [AlbumID]) - description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone() retry = 0 while retry < 5: @@ -125,6 +123,9 @@ class WebInterface(object): if not album: raise cherrypy.HTTPRedirect("home") + tracks = myDB.select('SELECT * from tracks WHERE AlbumID=? ORDER BY CAST(TrackNumber AS INTEGER)', [AlbumID]) + description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone() + if not album['ArtistName']: title = ' - ' else: @@ -147,7 +148,9 @@ class WebInterface(object): search.exposed = True def addArtist(self, artistid): - threading.Thread(target=importer.addArtisttoDB, args=[artistid]).start() + thread = threading.Thread(target=importer.addArtisttoDB, args=[artistid]) + thread.start() + thread.join(1) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % artistid) addArtist.exposed = True @@ -172,7 +175,9 @@ class WebInterface(object): newValueDict = {'IncludeExtras': 1, 'Extras': extras} myDB.upsert("artists", newValueDict, controlValueDict) - threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, True, False]).start() + thread = threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, True, False]) + thread.start() + thread.join(1) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) getExtras.exposed = True @@ -256,7 +261,9 @@ class WebInterface(object): deleteEmptyArtists.exposed = True def refreshArtist(self, ArtistID): - threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, False, True]).start() + thread = threading.Thread(target=importer.addArtisttoDB, args=[ArtistID, False, True]) + thread.start() + thread.join(1) raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) refreshArtist.exposed = True @@ -312,9 +319,8 @@ class WebInterface(object): myDB.upsert("albums", newValueDict, controlValueDict) searcher.searchforalbum(AlbumID, new) if ArtistID: - raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID) - else: - raise cherrypy.HTTPRedirect(redirect) + redirect = "artistPage?ArtistID=%s" % ArtistID + raise cherrypy.HTTPRedirect(redirect) queueAlbum.exposed = True def choose_specific_download(self, AlbumID): @@ -332,7 +338,6 @@ class WebInterface(object): 'kind': result[4] } results_as_dicts.append(result_dict) - s = json.dumps(results_as_dicts) cherrypy.response.headers['Content-type'] = 'application/json' return s @@ -340,13 +345,9 @@ class WebInterface(object): choose_specific_download.exposed = True def download_specific_release(self, AlbumID, title, size, url, provider, kind, **kwargs): - # Handle situations where the torrent url contains arguments that are parsed if kwargs: - import urllib - import urllib2 url = urllib2.quote(url, safe=":?/=&") + '&' + urllib.urlencode(kwargs) - try: result = [(title, int(size), url, provider, kind)] except ValueError: @@ -376,8 +377,9 @@ class WebInterface(object): myDB = db.DBConnection() myDB.action('DELETE from have WHERE Matched=?', [AlbumID]) - album = myDB.action('SELECT ArtistName, AlbumTitle from albums where AlbumID=?', [AlbumID]).fetchone() + album = myDB.action('SELECT ArtistID, ArtistName, AlbumTitle from albums where AlbumID=?', [AlbumID]).fetchone() if album: + ArtistID = album['ArtistID'] myDB.action('DELETE from have WHERE ArtistName=? AND AlbumTitle=?', [album['ArtistName'], album['AlbumTitle']]) myDB.action('DELETE from albums WHERE AlbumID=?', [AlbumID]) @@ -398,9 +400,10 @@ class WebInterface(object): deleteAlbum.exposed = True def switchAlbum(self, AlbumID, ReleaseID): - ''' - Take the values from allalbums/alltracks (based on the ReleaseID) and swap it into the album & track tables - ''' + """ + Take the values from allalbums/alltracks (based on the ReleaseID) and + swap it into the album & track tables + """ from headphones import albumswitcher albumswitcher.switch(AlbumID, ReleaseID) raise cherrypy.HTTPRedirect("albumPage?AlbumID=%s" % AlbumID) diff --git a/pylintrc b/pylintrc index 3137b71b..0b147d4e 100644 --- a/pylintrc +++ b/pylintrc @@ -38,9 +38,6 @@ load-plugins= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -#C0303 whitespace between the end of a line and the newline. -#C0325 a single item in parentheses follows an if, for, or other keyword -#C0326 wrong number of spaces is used around an operator, bracket or block opener #I0011 an inline option disables a pylint message or a messages category #R0801 a set of similar lines has been detected among multiple file #W0142 a function or method is called using *args or **kwargs to dispatch argument @@ -49,7 +46,7 @@ load-plugins= # C0330(bad-continuation) # E1205(logging-too-many-args) -disable=C0303,C0325,C0326,I0011,R0801,W0142,C0103,C0111,C0301,C0302,C0304,C0321,C1001,E0101,E0203,E0602,E1101,E1123,R0201,R0401,R0911,R0912,R0914,R0915,R0923,W0102,W0109,W0120,W0141,W0201,W0212,W0231,W0232,W0233,W0301,W0311,W0401,W0403,W0404,W0511,W0601,W0602,W0603,W0611,W0612,W0613,W0621,W0622,W0633,W0702,W0703,W1401,W1201,C0330 +disable=I0011,R0801,W0142,C0103,C0111,C0301,C0302,C0304,C0321,C1001,E0101,E0203,E0602,E1101,E1123,R0201,R0401,R0911,R0912,R0914,R0915,R0923,W0102,W0109,W0120,W0141,W0201,W0212,W0231,W0232,W0233,W0301,W0311,W0401,W0403,W0404,W0511,W0601,W0602,W0603,W0611,W0612,W0613,W0621,W0622,W0633,W0702,W0703,W1401,W1201,C0330 [REPORTS]