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/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) 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/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, 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) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 64ac1f13..3627438c 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 @@ -33,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() @@ -55,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): @@ -98,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']) @@ -188,7 +194,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,9 +278,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) - else: - logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace')) + renameUnprocessedFolder(albumpath, tag="Unprocessed") def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None): @@ -1024,20 +1028,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 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 "