Compare commits

...
9 Commits
Author SHA1 Message Date
Bas Stottelaar f9d331041d Release v0.5.3 2015-01-15 11:38:51 +01:00
Bas Stottelaar 4b76e156a9 Merge pull request #2075 from theguardian/ArtistUpdateAPI
Active Artist Update can be launched via API
2015-01-11 12:24:25 +01:00
Justin Evans 65660e57ca Active Artist Update can be launched via API 2015-01-10 14:14:24 -08:00
Bas Stottelaar 48e2722d9e Add more logging for issue #2058 2014-12-31 11:44:31 +01:00
Bas Stottelaar 91ec945a37 Minor code fixes 2014-12-28 19:59:20 +01:00
Bas Stottelaar ba62e297b5 Mark download as frozen if it should not be added 2014-12-28 16:13:20 +01:00
Bas Stottelaar 7f3d89aaa7 Improve logging message. 2014-12-28 14:55:35 +01:00
Bas Stottelaar b7321e3121 Add support for custom rename tags. 2014-12-28 14:55:08 +01:00
Bas Stottelaar 2ecf0bcb41 Potential fix for #2055 2014-12-28 13:36:45 +01:00
8 changed files with 67 additions and 41 deletions
+2
View File
@@ -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
+13 -3
View File
@@ -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)
+5 -2
View File
@@ -10,9 +10,10 @@
<a id="menu_link_delete" href="#" onclick="doAjaxCall('clearhistory?type=all',$(this),'table')" data-success="All History cleared"><i class="fa fa-trash-o"></i> Clear All History</a>
<a id="menu_link_delete" href="#" onclick="doAjaxCall('clearhistory?type=Processed',$(this),'table')" data-success="All Processed cleared"><i class="fa fa-trash-o"></i> Clear Processed</a>
<a id="menu_link_delete" href="#" onclick="doAjaxCall('clearhistory?type=Unprocessed',$(this),'table')" data-success="All Unprocessed cleared"><i class="fa fa-trash-o"></i> Clear Unprocessed</a>
<a id="menu_link_delete" href="#" onclick="doAjaxCall('clearhistory?type=Frozen',$(this),'table')" data-success="All Frozen cleared"><i class="fa fa-trash-o"></i> Clear Frozen</a>
<a id="menu_link_delete" href="#" onclick="doAjaxCall('clearhistory?type=Snatched',$(this),'table')" data-success="All Snatched cleared"><i class="fa fa-trash-o"></i> Clear Snatched</a>
</div>
</div>
</div>
</%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'
+8 -4
View File
@@ -13,16 +13,17 @@
# You should have received a copy of the GNU General Public License
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
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,
+5 -8
View File
@@ -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)
+5 -6
View File
@@ -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):
+20 -14
View File
@@ -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
+9 -4
View File
@@ -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 "