From 2ecf0bcb41d827cf885bc2b95f6b387f4b95aa26 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 28 Dec 2014 13:36:03 +0100 Subject: [PATCH 1/8] Potential fix for #2055 --- headphones/lock.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/headphones/lock.py b/headphones/lock.py index 2fcc7ab7..1ec3f2c8 100644 --- a/headphones/lock.py +++ b/headphones/lock.py @@ -39,12 +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) + headphones.logger.debug('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) + headphones.logger.debug('Sleeping %s (queued)', seconds) time.sleep(seconds) except Queue.Empty: continue @@ -59,15 +59,14 @@ class TimedLock(object): def snooze(self, seconds): """ - Asynchronously add time to the next request. - Can be called outside + Asynchronously add time to the next request. Can be called outside of the lock context, but it is possible for the next lock holder to not check the queue until after something adds time to it. """ - # we use a queue so that we don't have to synchronize + # 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) + self.queue.put(seconds) class FakeLock(object): From b7321e312130c8e0d1124d8b5818affc4c0fc813 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 28 Dec 2014 14:55:08 +0100 Subject: [PATCH 2/8] Add support for custom rename tags. --- headphones/postprocessor.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 64ac1f13..16b9b4f7 100755 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -19,6 +19,7 @@ import shutil import uuid import beets import threading +import itertools import headphones from beets import autotag @@ -188,7 +189,7 @@ def verify(albumid, albumpath, Kind=None, forced=False): myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid]) processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath) if not processed: - renameUnprocessedFolder(albumpath) + renameUnprocessedFolder(albumpath, tag="Unprocessed") return # test #1: metadata - usually works @@ -272,7 +273,7 @@ def verify(albumid, albumpath, Kind=None, forced=False): myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid]) processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath) if not processed: - renameUnprocessedFolder(albumpath) + renameUnprocessedFolder(albumpath, tag="Unprocessed") else: logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace')) @@ -1024,20 +1025,22 @@ def updateFilePermissions(albumpaths): continue -def renameUnprocessedFolder(albumpath): +def renameUnprocessedFolder(path, tag): + """ + Rename a unprocessed folder to a new unique name to indicate a certain + status. + """ - i = 0 - while True: + for i in itertools.count(): if i == 0: - new_folder_name = albumpath + ' (Unprocessed)' + new_path = "%s (%s)" % (path, tag) else: - new_folder_name = albumpath + ' (Unprocessed)[%i]' % i + new_path = "%s (%s[%d])" % (path, tag, i) - if os.path.exists(new_folder_name): + if os.path.exists(new_path): i += 1 - else: - os.rename(albumpath, new_folder_name) + os.rename(path, new_path) return From 7f3d89aaa7b1c59df32414f7726f40acb69ce133 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 28 Dec 2014 14:55:35 +0100 Subject: [PATCH 3/8] Improve logging message. --- headphones/postprocessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 16b9b4f7..6fa4fd1d 100755 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -34,7 +34,7 @@ postprocessor_lock = threading.Lock() def checkFolder(): - logger.info("Checking download folder for completed downloads") + logger.info("Checking download folder for completed downloads (only snatched ones).") with postprocessor_lock: myDB = db.DBConnection() @@ -56,7 +56,7 @@ def checkFolder(): else: logger.info("No folder name found for " + album['Title']) - logger.info("Checking download folder finished") + logger.info("Checking download folder finished.") def verify(albumid, albumpath, Kind=None, forced=False): From ba62e297b5a6dab8ae191d922e7733d12f1e915a Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 28 Dec 2014 16:13:20 +0100 Subject: [PATCH 4/8] Mark download as frozen if it should not be added --- data/interfaces/default/history.html | 7 +++++-- headphones/postprocessor.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 4a4531b7..0b7cd6ee 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -10,9 +10,10 @@ Clear All History Clear Processed Clear Unprocessed + Clear Frozen Clear Snatched - + <%def name="body()"> @@ -39,9 +40,11 @@ grade = 'C' elif item['Status'] == 'Unprocessed': grade = 'X' + elif item['Status'] == 'Frozen': + grade = 'X' else: grade = 'U' - + fileid = 'unknown' if item['URL'].find('nzb') != -1: fileid = 'nzb' diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 6fa4fd1d..3627438c 100755 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -99,6 +99,11 @@ def verify(albumid, albumpath, Kind=None, forced=False): "but database is frozen. Will skip postprocessing for " \ "album with rgid: %s", release_dict['artist_name'], release_dict['artist_id'], albumid) + + myDB.action('UPDATE snatched SET status = "Frozen" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid]) + frozen = re.search(r' \(Frozen\)(?:\[\d+\])?', albumpath) + if not frozen: + renameUnprocessedFolder(albumpath, tag="Frozen") return logger.info(u"Now adding/updating artist: " + release_dict['artist_name']) @@ -274,8 +279,6 @@ def verify(albumid, albumpath, Kind=None, forced=False): processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath) if not processed: renameUnprocessedFolder(albumpath, tag="Unprocessed") - else: - logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace')) def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None): From 91ec945a37ec4587a3c943b9fcfaba4ca88fa0e3 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 28 Dec 2014 19:59:20 +0100 Subject: [PATCH 5/8] Minor code fixes --- headphones/librarysync.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/headphones/librarysync.py b/headphones/librarysync.py index d80b903a..08e5ee90 100644 --- a/headphones/librarysync.py +++ b/headphones/librarysync.py @@ -64,20 +64,18 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING, 'replace') if not os.path.isfile(encoded_track_string): if track['ArtistName']: - #Make sure deleted files get accounted for when updating artist track counts + # Make sure deleted files get accounted for when updating artist track counts new_artists.append(track['ArtistName']) myDB.action('DELETE FROM have WHERE Location=?', [track['Location']]) logger.info('File %s removed from Headphones, as it is no longer on disk' % encoded_track_string.decode(headphones.SYS_ENCODING, 'replace')) - ###############myDB.action('DELETE from have') bitrates = [] - song_list = [] + latest_subdirectory = [] + new_song_count = 0 file_count = 0 - latest_subdirectory = [] - for r, d, f in helpers.walk_directory(dir): # Filter paths based on config. Note that these methods work directly # on the inputs @@ -313,7 +311,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, ] # Update track counts - for artist in artists_checked: # Have tracks are selected from tracks table and not all tracks because of duplicates # We update the track count upon an album switch to compliment this @@ -321,13 +318,13 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL', [artist])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artist])) ) - #Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases + # Note: some people complain about having "artist have tracks" > # of tracks total in artist official releases # (can fix by getting rid of second len statement) myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistName=?', [havetracks, artist]) logger.info('Found %i new artists' % len(artist_list)) - if len(artist_list): + if artist_list: if headphones.CONFIG.AUTO_ADD_ARTISTS: logger.info('Importing %i new artists' % len(artist_list)) importer.artistlist_to_mbids(artist_list) From 48e2722d9e55de0c624521d114d62e641a15616a Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Wed, 31 Dec 2014 11:44:31 +0100 Subject: [PATCH 6/8] Add more logging for issue #2058 --- headphones/request.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/headphones/request.py b/headphones/request.py index 26da11b4..e577faad 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -76,10 +76,15 @@ def request_response(url, method="get", auto_raise=True, response.raise_for_status() return response - except requests.exceptions.SSLError: - logger.error("Unable to connect to remote host because of a SSL " \ - "error. It's likely the remote certificate is untrusted by your " \ - "system. This check can be disabled (advanced users only).") + except requests.exceptions.SSLError as e: + if not kwargs["verify"]: + logger.error("Unable to connect to remote host because of a SSL " \ + "error. It's likely the remote certificate is untrusted by " \ + "your system. This check can be disabled (advanced users " \ + "only).") + else: + logger.error("SSL error raised during connection, even with " \ + "SSL certificate verification turned off: %s", e) except requests.ConnectionError: logger.error( "Unable to connect to remote host. Check if the remote " From 65660e57cae49d7613aea0f6329482e61e3279fe Mon Sep 17 00:00:00 2001 From: Justin Evans Date: Sat, 10 Jan 2015 14:14:24 -0800 Subject: [PATCH 7/8] Active Artist Update can be launched via API --- API.md | 2 ++ headphones/api.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/API.md b/API.md index cf69ef66..0fc0946d 100644 --- a/API.md +++ b/API.md @@ -68,6 +68,8 @@ Unmark album as wanted / i.e. mark as skipped force search for wanted albums - not launched in a separate thread so it may take a bit to complete ### forceProcess Force post process albums in download directory - also not launched in a separate thread +### forceActiveArtistsUpdate +force Active Artist Update - also not launched in a separate thread ### getVersion Returns some version information: git_path, install_type, current_version, installed_version, commits_behind diff --git a/headphones/api.py b/headphones/api.py index cb429ea6..7273c2ad 100644 --- a/headphones/api.py +++ b/headphones/api.py @@ -13,16 +13,17 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . -from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger +from headphones import db, mb, updater, importer, searcher, cache, postprocessor, versioncheck, logger import headphones import json cmd_list = ['getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs', 'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist', - 'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub', - 'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo', - 'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release'] + 'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'forceActiveArtistsUpdate', + 'getVersion', 'checkGithub','shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', + 'getArtistInfo', 'getAlbumInfo', 'getArtistThumb', 'getAlbumThumb', + 'choose_specific_download', 'download_specific_release'] class Api(object): @@ -321,6 +322,9 @@ class Api(object): self.dir = kwargs['dir'] postprocessor.forcePostProcess(self.dir) + def _forceActiveArtistsUpdate(self, **kwargs): + updater.dbUpdate() + def _getVersion(self, **kwargs): self.data = { 'git_path': headphones.CONFIG.GIT_PATH, From f9d331041d03c8c19bdde8b4b90ae4764a64317a Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Thu, 15 Jan 2015 11:38:51 +0100 Subject: [PATCH 8/8] Release v0.5.3 --- CHANGELOG.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04bdf573..d0305eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,19 @@ # Changelog -## v0.5.2 -Released 28 december 2014 +## v0.5.3 +Released 15 January 2015 -Highlight: +Highlights: +* Added: update active artists via API (#2075) +* Fixed: queue instance lacks `add` method (#2055) +* Improved: better SSL error messages (#2058) + +The full list of commits can be found [here](https://github.com/rembo10/headphones/compare/v0.5.2...v0.5.3). + +## v0.5.2 +Released 28 December 2014 + +Highlights: * Added: advanced option to ignore certain folders by patterns. (#2037) * Added: advanced option to ignore certain files by patterns (library only) * Added: specify optional paths to CUE splitting tools (#1938)