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 @@
+
-
+
%def>
<%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 "