diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 8363381..ca96ae5 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -29,187 +29,187 @@ from supysonic.managers.user import UserManager @app.before_request def set_formatter(): - if not request.path.startswith('/rest/'): - return + if not request.path.startswith('/rest/'): + return - """Return a function to create the response.""" - (f, callback) = map(request.values.get, ['f', 'callback']) - if f == 'jsonp': - # Some clients (MiniSub, Perisonic) set f to jsonp without callback for streamed data - if not callback and request.endpoint not in [ 'stream_media', 'cover_art' ]: - return ResponseHelper.responsize_json({ - 'error': { - 'code': 0, - 'message': 'Missing callback' - } - }, error = True), 400 - request.formatter = lambda x, **kwargs: ResponseHelper.responsize_jsonp(x, callback, kwargs) - elif f == "json": - request.formatter = ResponseHelper.responsize_json - else: - request.formatter = ResponseHelper.responsize_xml + """Return a function to create the response.""" + (f, callback) = map(request.values.get, ['f', 'callback']) + if f == 'jsonp': + # Some clients (MiniSub, Perisonic) set f to jsonp without callback for streamed data + if not callback and request.endpoint not in [ 'stream_media', 'cover_art' ]: + return ResponseHelper.responsize_json({ + 'error': { + 'code': 0, + 'message': 'Missing callback' + } + }, error = True), 400 + request.formatter = lambda x, **kwargs: ResponseHelper.responsize_jsonp(x, callback, kwargs) + elif f == "json": + request.formatter = ResponseHelper.responsize_json + else: + request.formatter = ResponseHelper.responsize_xml - request.error_formatter = lambda code, msg: request.formatter({ 'error': { 'code': code, 'message': msg } }, error = True) + request.error_formatter = lambda code, msg: request.formatter({ 'error': { 'code': code, 'message': msg } }, error = True) @app.before_request def authorize(): - if not request.path.startswith('/rest/'): - return + if not request.path.startswith('/rest/'): + return - error = request.error_formatter(40, 'Unauthorized'), 401 + error = request.error_formatter(40, 'Unauthorized'), 401 - if request.authorization: - status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password) - if status == UserManager.SUCCESS: - request.username = request.authorization.username - request.user = user - return + if request.authorization: + status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password) + if status == UserManager.SUCCESS: + request.username = request.authorization.username + request.user = user + return - (username, password) = map(request.values.get, [ 'u', 'p' ]) - if not username or not password: - return error + (username, password) = map(request.values.get, [ 'u', 'p' ]) + if not username or not password: + return error - status, user = UserManager.try_auth(store, username, password) - if status != UserManager.SUCCESS: - return error + status, user = UserManager.try_auth(store, username, password) + if status != UserManager.SUCCESS: + return error - request.username = username - request.user = user + request.username = username + request.user = user @app.before_request def get_client_prefs(): - if not request.path.startswith('/rest/'): - return + if not request.path.startswith('/rest/'): + return - if 'c' not in request.values: - return request.error_formatter(10, 'Missing required parameter') + if 'c' not in request.values: + return request.error_formatter(10, 'Missing required parameter') - client = request.values.get('c') - prefs = store.get(ClientPrefs, (request.user.id, client)) - if not prefs: - prefs = ClientPrefs() - prefs.user_id = request.user.id - prefs.client_name = client - store.add(prefs) - store.commit() + client = request.values.get('c') + prefs = store.get(ClientPrefs, (request.user.id, client)) + if not prefs: + prefs = ClientPrefs() + prefs.user_id = request.user.id + prefs.client_name = client + store.add(prefs) + store.commit() - request.prefs = prefs + request.prefs = prefs @app.after_request def set_headers(response): - if not request.path.startswith('/rest/'): - return response + if not request.path.startswith('/rest/'): + return response - if response.mimetype.startswith('text'): - f = request.values.get('f') - response.headers['Content-Type'] = 'application/json' if f in [ 'jsonp', 'json' ] else 'text/xml' + if response.mimetype.startswith('text'): + f = request.values.get('f') + response.headers['Content-Type'] = 'application/json' if f in [ 'jsonp', 'json' ] else 'text/xml' - response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Origin'] = '*' - return response + return response @app.errorhandler(404) def not_found(error): - if not request.path.startswith('/rest/'): - return error + if not request.path.startswith('/rest/'): + return error - return request.error_formatter(0, 'Not implemented'), 501 + return request.error_formatter(0, 'Not implemented'), 501 class ResponseHelper: - @staticmethod - def responsize_json(ret, error = False, version = "1.8.0"): - def check_lists(d): - for key, value in d.items(): - if isinstance(value, dict): - d[key] = check_lists(value) - elif isinstance(value, list): - if len(value) == 0: - del d[key] - else: - d[key] = [ check_lists(item) if isinstance(item, dict) else item for item in value ] - return d + @staticmethod + def responsize_json(ret, error = False, version = "1.8.0"): + def check_lists(d): + for key, value in d.items(): + if isinstance(value, dict): + d[key] = check_lists(value) + elif isinstance(value, list): + if len(value) == 0: + del d[key] + else: + d[key] = [ check_lists(item) if isinstance(item, dict) else item for item in value ] + return d - ret = check_lists(ret) - # add headers to response - ret.update({ - 'status': 'failed' if error else 'ok', - 'version': version - }) - return simplejson.dumps({ 'subsonic-response': ret }, indent = True, encoding = 'utf-8') + ret = check_lists(ret) + # add headers to response + ret.update({ + 'status': 'failed' if error else 'ok', + 'version': version + }) + return simplejson.dumps({ 'subsonic-response': ret }, indent = True, encoding = 'utf-8') - @staticmethod - def responsize_jsonp(ret, callback, error = False, version = "1.8.0"): - return "%s(%s)" % (callback, ResponseHelper.responsize_json(ret, error, version)) + @staticmethod + def responsize_jsonp(ret, callback, error = False, version = "1.8.0"): + return "%s(%s)" % (callback, ResponseHelper.responsize_json(ret, error, version)) - @staticmethod - def responsize_xml(ret, error = False, version = "1.8.0"): - """Return an xml response from json and replace unsupported characters.""" - ret.update({ - 'status': 'failed' if error else 'ok', - 'version': version, - 'xmlns': "http://subsonic.org/restapi" - }) + @staticmethod + def responsize_xml(ret, error = False, version = "1.8.0"): + """Return an xml response from json and replace unsupported characters.""" + ret.update({ + 'status': 'failed' if error else 'ok', + 'version': version, + 'xmlns': "http://subsonic.org/restapi" + }) - elem = ElementTree.Element('subsonic-response') - ResponseHelper.dict2xml(elem, ret) + elem = ElementTree.Element('subsonic-response') + ResponseHelper.dict2xml(elem, ret) - return minidom.parseString(ElementTree.tostring(elem)).toprettyxml(indent = ' ', encoding = 'UTF-8') + return minidom.parseString(ElementTree.tostring(elem)).toprettyxml(indent = ' ', encoding = 'UTF-8') - @staticmethod - def dict2xml(elem, dictionary): - """Convert a json structure to xml. The game is trivial. Nesting uses the [] parenthesis. - ex. { 'musicFolder': {'id': 1234, 'name': "sss" } } - ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]} - ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } } - ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] } - ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]}, - "status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}} - """ - if not isinstance(dictionary, dict): - raise TypeError('Expecting a dict') - if not all(map(lambda x: isinstance(x, basestring), dictionary.keys())): - raise TypeError('Dictionary keys must be strings') + @staticmethod + def dict2xml(elem, dictionary): + """Convert a json structure to xml. The game is trivial. Nesting uses the [] parenthesis. + ex. { 'musicFolder': {'id': 1234, 'name': "sss" } } + ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]} + ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } } + ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] } + ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]}, + "status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}} + """ + if not isinstance(dictionary, dict): + raise TypeError('Expecting a dict') + if not all(map(lambda x: isinstance(x, basestring), dictionary.keys())): + raise TypeError('Dictionary keys must be strings') - subelems = { k: v for k, v in dictionary.iteritems() if isinstance(v, dict) } - sequences = { k: v for k, v in dictionary.iteritems() if isinstance(v, list) } - attributes = { k: v for k, v in dictionary.iteritems() if k != '_value_' and k not in subelems and k not in sequences } + subelems = { k: v for k, v in dictionary.iteritems() if isinstance(v, dict) } + sequences = { k: v for k, v in dictionary.iteritems() if isinstance(v, list) } + attributes = { k: v for k, v in dictionary.iteritems() if k != '_value_' and k not in subelems and k not in sequences } - if '_value_' in dictionary: - elem.text = ResponseHelper.value_tostring(dictionary['_value_']) - for attr, value in attributes.iteritems(): - elem.set(attr, ResponseHelper.value_tostring(value)) - for sub, subdict in subelems.iteritems(): - subelem = ElementTree.SubElement(elem, sub) - ResponseHelper.dict2xml(subelem, subdict) - for seq, dicts in sequences.iteritems(): - for subdict in dicts: - subelem = ElementTree.SubElement(elem, seq) - ResponseHelper.dict2xml(subelem, subdict) + if '_value_' in dictionary: + elem.text = ResponseHelper.value_tostring(dictionary['_value_']) + for attr, value in attributes.iteritems(): + elem.set(attr, ResponseHelper.value_tostring(value)) + for sub, subdict in subelems.iteritems(): + subelem = ElementTree.SubElement(elem, sub) + ResponseHelper.dict2xml(subelem, subdict) + for seq, dicts in sequences.iteritems(): + for subdict in dicts: + subelem = ElementTree.SubElement(elem, seq) + ResponseHelper.dict2xml(subelem, subdict) - @staticmethod - def value_tostring(value): - if isinstance(value, basestring): - return value - if isinstance(value, bool): - return str(value).lower() - return str(value) + @staticmethod + def value_tostring(value): + if isinstance(value, basestring): + return value + if isinstance(value, bool): + return str(value).lower() + return str(value) def get_entity(req, ent, param = 'id'): - eid = req.values.get(param) - if not eid: - return False, req.error_formatter(10, 'Missing %s id' % ent.__name__) + eid = req.values.get(param) + if not eid: + return False, req.error_formatter(10, 'Missing %s id' % ent.__name__) - try: - eid = uuid.UUID(eid) - except: - return False, req.error_formatter(0, 'Invalid %s id' % ent.__name__) + try: + eid = uuid.UUID(eid) + except: + return False, req.error_formatter(0, 'Invalid %s id' % ent.__name__) - entity = store.get(ent, eid) - if not entity: - return False, (req.error_formatter(70, '%s not found' % ent.__name__), 404) + entity = store.get(ent, eid) + if not entity: + return False, (req.error_formatter(70, '%s not found' % ent.__name__), 404) - return True, entity + return True, entity from .system import * from .browse import * diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py index 9bc94ba..05e93b4 100644 --- a/supysonic/api/albums_songs.py +++ b/supysonic/api/albums_songs.py @@ -31,171 +31,171 @@ from supysonic.db import now @app.route('/rest/getRandomSongs.view', methods = [ 'GET', 'POST' ]) def rand_songs(): - size = request.values.get('size', '10') - genre, fromYear, toYear, musicFolderId = map(request.values.get, [ 'genre', 'fromYear', 'toYear', 'musicFolderId' ]) + size = request.values.get('size', '10') + genre, fromYear, toYear, musicFolderId = map(request.values.get, [ 'genre', 'fromYear', 'toYear', 'musicFolderId' ]) - try: - size = int(size) if size else 10 - fromYear = int(fromYear) if fromYear else None - toYear = int(toYear) if toYear else None - fid = uuid.UUID(musicFolderId) if musicFolderId else None - except: - return request.error_formatter(0, 'Invalid parameter format') + try: + size = int(size) if size else 10 + fromYear = int(fromYear) if fromYear else None + toYear = int(toYear) if toYear else None + fid = uuid.UUID(musicFolderId) if musicFolderId else None + except: + return request.error_formatter(0, 'Invalid parameter format') - query = store.find(Track) - if fromYear: - query = query.find(Track.year >= fromYear) - if toYear: - query = query.find(Track.year <= toYear) - if genre: - query = query.find(Track.genre == genre) - if fid: - query = query.find(Track.root_folder_id == fid) - count = query.count() + query = store.find(Track) + if fromYear: + query = query.find(Track.year >= fromYear) + if toYear: + query = query.find(Track.year <= toYear) + if genre: + query = query.find(Track.genre == genre) + if fid: + query = query.find(Track.root_folder_id == fid) + count = query.count() - if not count: - return request.formatter({ 'randomSongs': {} }) + if not count: + return request.formatter({ 'randomSongs': {} }) - tracks = [] - for _ in xrange(size): - x = random.choice(xrange(count)) - tracks.append(query[x]) + tracks = [] + for _ in xrange(size): + x = random.choice(xrange(count)) + tracks.append(query[x]) - return request.formatter({ - 'randomSongs': { - 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in tracks ] - } - }) + return request.formatter({ + 'randomSongs': { + 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in tracks ] + } + }) @app.route('/rest/getAlbumList.view', methods = [ 'GET', 'POST' ]) def album_list(): - ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) - try: - size = int(size) if size else 10 - offset = int(offset) if offset else 0 - except: - return request.error_formatter(0, 'Invalid parameter format') + ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) + try: + size = int(size) if size else 10 + offset = int(offset) if offset else 0 + except: + return request.error_formatter(0, 'Invalid parameter format') - query = store.find(Folder, Track.folder_id == Folder.id) - if ltype == 'random': - albums = [] - count = query.count() + query = store.find(Folder, Track.folder_id == Folder.id) + if ltype == 'random': + albums = [] + count = query.count() - if not count: - return request.formatter({ 'albumList': {} }) + if not count: + return request.formatter({ 'albumList': {} }) - for _ in xrange(size): - x = random.choice(xrange(count)) - albums.append(query[x]) + for _ in xrange(size): + x = random.choice(xrange(count)) + albums.append(query[x]) - return request.formatter({ - 'albumList': { - 'album': [ a.as_subsonic_child(request.user) for a in albums ] - } - }) - elif ltype == 'newest': - query = query.order_by(Desc(Folder.created)).config(distinct = True) - elif ltype == 'highest': - query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating))) - elif ltype == 'frequent': - query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count))) - elif ltype == 'recent': - query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play))) - elif ltype == 'starred': - query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username) - elif ltype == 'alphabeticalByName': - query = query.order_by(Folder.name).config(distinct = True) - elif ltype == 'alphabeticalByArtist': - parent = ClassAlias(Folder) - query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True) - else: - return request.error_formatter(0, 'Unknown search type') + return request.formatter({ + 'albumList': { + 'album': [ a.as_subsonic_child(request.user) for a in albums ] + } + }) + elif ltype == 'newest': + query = query.order_by(Desc(Folder.created)).config(distinct = True) + elif ltype == 'highest': + query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating))) + elif ltype == 'frequent': + query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count))) + elif ltype == 'recent': + query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play))) + elif ltype == 'starred': + query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username) + elif ltype == 'alphabeticalByName': + query = query.order_by(Folder.name).config(distinct = True) + elif ltype == 'alphabeticalByArtist': + parent = ClassAlias(Folder) + query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True) + else: + return request.error_formatter(0, 'Unknown search type') - return request.formatter({ - 'albumList': { - 'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset+size] ] - } - }) + return request.formatter({ + 'albumList': { + 'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset+size] ] + } + }) @app.route('/rest/getAlbumList2.view', methods = [ 'GET', 'POST' ]) def album_list_id3(): - ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) - try: - size = int(size) if size else 10 - offset = int(offset) if offset else 0 - except: - return request.error_formatter(0, 'Invalid parameter format') + ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ]) + try: + size = int(size) if size else 10 + offset = int(offset) if offset else 0 + except: + return request.error_formatter(0, 'Invalid parameter format') - query = store.find(Album) - if ltype == 'random': - albums = [] - count = query.count() + query = store.find(Album) + if ltype == 'random': + albums = [] + count = query.count() - if not count: - return request.formatter({ 'albumList2': {} }) + if not count: + return request.formatter({ 'albumList2': {} }) - for _ in xrange(size): - x = random.choice(xrange(count)) - albums.append(query[x]) + for _ in xrange(size): + x = random.choice(xrange(count)) + albums.append(query[x]) - return request.formatter({ - 'albumList2': { - 'album': [ a.as_subsonic_album(request.user) for a in albums ] - } - }) - elif ltype == 'newest': - query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created))) - elif ltype == 'frequent': - query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count))) - elif ltype == 'recent': - query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play))) - elif ltype == 'starred': - query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username) - elif ltype == 'alphabeticalByName': - query = query.order_by(Album.name) - elif ltype == 'alphabeticalByArtist': - query = query.find(Artist.id == Album.artist_id).order_by(Artist.name, Album.name) - else: - return request.error_formatter(0, 'Unknown search type') + return request.formatter({ + 'albumList2': { + 'album': [ a.as_subsonic_album(request.user) for a in albums ] + } + }) + elif ltype == 'newest': + query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created))) + elif ltype == 'frequent': + query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count))) + elif ltype == 'recent': + query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play))) + elif ltype == 'starred': + query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username) + elif ltype == 'alphabeticalByName': + query = query.order_by(Album.name) + elif ltype == 'alphabeticalByArtist': + query = query.find(Artist.id == Album.artist_id).order_by(Artist.name, Album.name) + else: + return request.error_formatter(0, 'Unknown search type') - return request.formatter({ - 'albumList2': { - 'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset+size] ] - } - }) + return request.formatter({ + 'albumList2': { + 'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset+size] ] + } + }) @app.route('/rest/getNowPlaying.view', methods = [ 'GET', 'POST' ]) def now_playing(): - query = store.find(User, Track.id == User.last_play_id) + query = store.find(User, Track.id == User.last_play_id) - return request.formatter({ - 'nowPlaying': { - 'entry': [ dict( - u.last_play.as_subsonic_child(request.user, request.prefs).items() + - { 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items() - ) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ] - } - }) + return request.formatter({ + 'nowPlaying': { + 'entry': [ dict( + u.last_play.as_subsonic_child(request.user, request.prefs).items() + + { 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items() + ) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ] + } + }) @app.route('/rest/getStarred.view', methods = [ 'GET', 'POST' ]) def get_starred(): - folders = store.find(StarredFolder, StarredFolder.user_id == User.id, User.name == request.username) + folders = store.find(StarredFolder, StarredFolder.user_id == User.id, User.name == request.username) - return request.formatter({ - 'starred': { - 'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ], - 'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ], - 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] - } - }) + return request.formatter({ + 'starred': { + 'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ], + 'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ], + 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] + } + }) @app.route('/rest/getStarred2.view', methods = [ 'GET', 'POST' ]) def get_starred_id3(): - return request.formatter({ - 'starred2': { - 'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ], - 'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ], - 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] - } - }) + return request.formatter({ + 'starred2': { + 'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ], + 'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ], + 'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ] + } + }) diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index f1e169c..de1f8ff 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -30,145 +30,145 @@ from supysonic.db import RatingTrack, RatingFolder @app.route('/rest/star.view', methods = [ 'GET', 'POST' ]) def star(): - id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ]) + id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ]) - def try_star(ent, starred_ent, eid): - try: - uid = uuid.UUID(eid) - except: - return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__) + def try_star(ent, starred_ent, eid): + try: + uid = uuid.UUID(eid) + except: + return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__) - if store.get(starred_ent, (request.user.id, uid)): - return 2, request.error_formatter(0, '%s already starred' % ent.__name__) - e = store.get(ent, uid) - if e: - starred = starred_ent() - starred.user_id = request.user.id - starred.starred_id = uid - store.add(starred) - else: - return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__) + if store.get(starred_ent, (request.user.id, uid)): + return 2, request.error_formatter(0, '%s already starred' % ent.__name__) + e = store.get(ent, uid) + if e: + starred = starred_ent() + starred.user_id = request.user.id + starred.starred_id = uid + store.add(starred) + else: + return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__) - return 0, None + return 0, None - for eid in id: - err, ferror = try_star(Track, StarredTrack, eid) - if err == 1: - err, ferror = try_star(Folder, StarredFolder, eid) - if err: - return ferror - elif err == 2: - return ferror + for eid in id: + err, ferror = try_star(Track, StarredTrack, eid) + if err == 1: + err, ferror = try_star(Folder, StarredFolder, eid) + if err: + return ferror + elif err == 2: + return ferror - for alId in albumId: - err, ferror = try_star(Album, StarredAlbum, alId) - if err: - return ferror + for alId in albumId: + err, ferror = try_star(Album, StarredAlbum, alId) + if err: + return ferror - for arId in artistId: - err, ferror = try_star(Artist, StarredArtist, arId) - if err: - return ferror + for arId in artistId: + err, ferror = try_star(Artist, StarredArtist, arId) + if err: + return ferror - store.commit() - return request.formatter({}) + store.commit() + return request.formatter({}) @app.route('/rest/unstar.view', methods = [ 'GET', 'POST' ]) def unstar(): - id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ]) + id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ]) - def try_unstar(ent, eid): - try: - uid = uuid.UUID(eid) - except: - return request.error_formatter(0, 'Invalid id') + def try_unstar(ent, eid): + try: + uid = uuid.UUID(eid) + except: + return request.error_formatter(0, 'Invalid id') - store.find(ent, ent.user_id == request.user.id, ent.starred_id == uid).remove() - return None + store.find(ent, ent.user_id == request.user.id, ent.starred_id == uid).remove() + return None - for eid in id: - err = try_unstar(StarredTrack, eid) - if err: - return err - err = try_unstar(StarredFolder, eid) - if err: - return err + for eid in id: + err = try_unstar(StarredTrack, eid) + if err: + return err + err = try_unstar(StarredFolder, eid) + if err: + return err - for alId in albumId: - err = try_unstar(StarredAlbum, alId) - if err: - return err + for alId in albumId: + err = try_unstar(StarredAlbum, alId) + if err: + return err - for arId in artistId: - err = try_unstar(StarredArtist, arId) - if err: - return err + for arId in artistId: + err = try_unstar(StarredArtist, arId) + if err: + return err - store.commit() - return request.formatter({}) + store.commit() + return request.formatter({}) @app.route('/rest/setRating.view', methods = [ 'GET', 'POST' ]) def rate(): - id, rating = map(request.values.get, [ 'id', 'rating' ]) - if not id or not rating: - return request.error_formatter(10, 'Missing parameter') + id, rating = map(request.values.get, [ 'id', 'rating' ]) + if not id or not rating: + return request.error_formatter(10, 'Missing parameter') - try: - uid = uuid.UUID(id) - rating = int(rating) - except: - return request.error_formatter(0, 'Invalid parameter') + try: + uid = uuid.UUID(id) + rating = int(rating) + except: + return request.error_formatter(0, 'Invalid parameter') - if not rating in xrange(6): - return request.error_formatter(0, 'rating must be between 0 and 5 (inclusive)') + if not rating in xrange(6): + return request.error_formatter(0, 'rating must be between 0 and 5 (inclusive)') - if rating == 0: - store.find(RatingTrack, RatingTrack.user_id == request.user.id, RatingTrack.rated_id == uid).remove() - store.find(RatingFolder, RatingFolder.user_id == request.user.id, RatingFolder.rated_id == uid).remove() - else: - rated = store.get(Track, uid) - rating_ent = RatingTrack - if not rated: - rated = store.get(Folder, uid) - rating_ent = RatingFolder - if not rated: - return request.error_formatter(70, 'Unknown id') + if rating == 0: + store.find(RatingTrack, RatingTrack.user_id == request.user.id, RatingTrack.rated_id == uid).remove() + store.find(RatingFolder, RatingFolder.user_id == request.user.id, RatingFolder.rated_id == uid).remove() + else: + rated = store.get(Track, uid) + rating_ent = RatingTrack + if not rated: + rated = store.get(Folder, uid) + rating_ent = RatingFolder + if not rated: + return request.error_formatter(70, 'Unknown id') - rating_info = store.get(rating_ent, (request.user.id, uid)) - if rating_info: - rating_info.rating = rating - else: - rating_info = rating_ent() - rating_info.user_id = request.user.id - rating_info.rated_id = uid - rating_info.rating = rating - store.add(rating_info) + rating_info = store.get(rating_ent, (request.user.id, uid)) + if rating_info: + rating_info.rating = rating + else: + rating_info = rating_ent() + rating_info.user_id = request.user.id + rating_info.rated_id = uid + rating_info.rating = rating + store.add(rating_info) - store.commit() - return request.formatter({}) + store.commit() + return request.formatter({}) @app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ]) def scrobble(): - status, res = get_entity(request, Track) - if not status: - return res + status, res = get_entity(request, Track) + if not status: + return res - t, submission = map(request.values.get, [ 'time', 'submission' ]) + t, submission = map(request.values.get, [ 'time', 'submission' ]) - if t: - try: - t = int(t) / 1000 - except: - return request.error_formatter(0, 'Invalid time value') - else: - t = int(time.time()) + if t: + try: + t = int(t) / 1000 + except: + return request.error_formatter(0, 'Invalid time value') + else: + t = int(time.time()) - lfm = LastFm(request.user, app.logger) + lfm = LastFm(request.user, app.logger) - if submission in (None, '', True, 'true', 'True', 1, '1'): - lfm.scrobble(res, t) - else: - lfm.now_playing(res) + if submission in (None, '', True, 'true', 'True', 1, '1'): + lfm.scrobble(res, t) + else: + lfm.now_playing(res) - return request.formatter({}) + return request.formatter({}) diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index aaa431c..53ba84e 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -26,155 +26,155 @@ import uuid, string @app.route('/rest/getMusicFolders.view', methods = [ 'GET', 'POST' ]) def list_folders(): - return request.formatter({ - 'musicFolders': { - 'musicFolder': [ { - 'id': str(f.id), - 'name': f.name - } for f in store.find(Folder, Folder.root == True).order_by(Folder.name) ] - } - }) + return request.formatter({ + 'musicFolders': { + 'musicFolder': [ { + 'id': str(f.id), + 'name': f.name + } for f in store.find(Folder, Folder.root == True).order_by(Folder.name) ] + } + }) @app.route('/rest/getIndexes.view', methods = [ 'GET', 'POST' ]) def list_indexes(): - musicFolderId = request.values.get('musicFolderId') - ifModifiedSince = request.values.get('ifModifiedSince') - if ifModifiedSince: - try: - ifModifiedSince = int(ifModifiedSince) / 1000 - except: - return request.error_formatter(0, 'Invalid timestamp') + musicFolderId = request.values.get('musicFolderId') + ifModifiedSince = request.values.get('ifModifiedSince') + if ifModifiedSince: + try: + ifModifiedSince = int(ifModifiedSince) / 1000 + except: + return request.error_formatter(0, 'Invalid timestamp') - if musicFolderId is None: - folder = store.find(Folder, Folder.root == True) - else: - try: - mfid = uuid.UUID(musicFolderId) - except: - return request.error_formatter(0, 'Invalid id') + if musicFolderId is None: + folder = store.find(Folder, Folder.root == True) + else: + try: + mfid = uuid.UUID(musicFolderId) + except: + return request.error_formatter(0, 'Invalid id') - folder = store.get(Folder, mfid) + folder = store.get(Folder, mfid) - if not folder or (type(folder) is Folder and not folder.root): - return request.error_formatter(70, 'Folder not found') + if not folder or (type(folder) is Folder and not folder.root): + return request.error_formatter(70, 'Folder not found') - last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is not Folder else folder.last_scan + last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is not Folder else folder.last_scan - if (not ifModifiedSince is None) and last_modif < ifModifiedSince: - return request.formatter({ 'indexes': { 'lastModified': last_modif * 1000 } }) + if (not ifModifiedSince is None) and last_modif < ifModifiedSince: + return request.formatter({ 'indexes': { 'lastModified': last_modif * 1000 } }) - # The XSD lies, we don't return artists but a directory structure - if type(folder) is not Folder: - artists = [] - childs = [] - for f in folder: - artists += f.children - childs += f.tracks - else: - artists = folder.children - childs = folder.tracks + # The XSD lies, we don't return artists but a directory structure + if type(folder) is not Folder: + artists = [] + childs = [] + for f in folder: + artists += f.children + childs += f.tracks + else: + artists = folder.children + childs = folder.tracks - indexes = {} - for artist in artists: - index = artist.name[0].upper() - if index in map(str, xrange(10)): - index = '#' - elif index not in string.letters: - index = '?' + indexes = {} + for artist in artists: + index = artist.name[0].upper() + if index in map(str, xrange(10)): + index = '#' + elif index not in string.letters: + index = '?' - if index not in indexes: - indexes[index] = [] + if index not in indexes: + indexes[index] = [] - indexes[index].append(artist) + indexes[index].append(artist) - return request.formatter({ - 'indexes': { - 'lastModified': last_modif * 1000, - 'index': [ { - 'name': k, - 'artist': [ { - 'id': str(a.id), - 'name': a.name - } for a in sorted(v, key = lambda a: a.name.lower()) ] - } for k, v in sorted(indexes.iteritems()) ], - 'child': [ c.as_subsonic_child(request.user, request.prefs) for c in sorted(childs, key = lambda t: t.sort_key()) ] - } - }) + return request.formatter({ + 'indexes': { + 'lastModified': last_modif * 1000, + 'index': [ { + 'name': k, + 'artist': [ { + 'id': str(a.id), + 'name': a.name + } for a in sorted(v, key = lambda a: a.name.lower()) ] + } for k, v in sorted(indexes.iteritems()) ], + 'child': [ c.as_subsonic_child(request.user, request.prefs) for c in sorted(childs, key = lambda t: t.sort_key()) ] + } + }) @app.route('/rest/getMusicDirectory.view', methods = [ 'GET', 'POST' ]) def show_directory(): - status, res = get_entity(request, Folder) - if not status: - return res + status, res = get_entity(request, Folder) + if not status: + return res - directory = { - 'id': str(res.id), - 'name': res.name, - 'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] - } - if not res.root: - directory['parent'] = str(res.parent_id) + directory = { + 'id': str(res.id), + 'name': res.name, + 'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] + } + if not res.root: + directory['parent'] = str(res.parent_id) - return request.formatter({ 'directory': directory }) + return request.formatter({ 'directory': directory }) @app.route('/rest/getArtists.view', methods = [ 'GET', 'POST' ]) def list_artists(): - # According to the API page, there are no parameters? - indexes = {} - for artist in store.find(Artist): - index = artist.name[0].upper() if artist.name else '?' - if index in map(str, xrange(10)): - index = '#' - elif index not in string.letters: - index = '?' + # According to the API page, there are no parameters? + indexes = {} + for artist in store.find(Artist): + index = artist.name[0].upper() if artist.name else '?' + if index in map(str, xrange(10)): + index = '#' + elif index not in string.letters: + index = '?' - if index not in indexes: - indexes[index] = [] + if index not in indexes: + indexes[index] = [] - indexes[index].append(artist) + indexes[index].append(artist) - return request.formatter({ - 'artists': { - 'index': [ { - 'name': k, - 'artist': [ a.as_subsonic_artist(request.user) for a in sorted(v, key = lambda a: a.name.lower()) ] - } for k, v in sorted(indexes.iteritems()) ] - } - }) + return request.formatter({ + 'artists': { + 'index': [ { + 'name': k, + 'artist': [ a.as_subsonic_artist(request.user) for a in sorted(v, key = lambda a: a.name.lower()) ] + } for k, v in sorted(indexes.iteritems()) ] + } + }) @app.route('/rest/getArtist.view', methods = [ 'GET', 'POST' ]) def artist_info(): - status, res = get_entity(request, Artist) - if not status: - return res + status, res = get_entity(request, Artist) + if not status: + return res - info = res.as_subsonic_artist(request.user) - albums = set(res.albums) - albums |= { t.album for t in res.tracks } - info['album'] = [ a.as_subsonic_album(request.user) for a in sorted(albums, key = lambda a: a.sort_key()) ] + info = res.as_subsonic_artist(request.user) + albums = set(res.albums) + albums |= { t.album for t in res.tracks } + info['album'] = [ a.as_subsonic_album(request.user) for a in sorted(albums, key = lambda a: a.sort_key()) ] - return request.formatter({ 'artist': info }) + return request.formatter({ 'artist': info }) @app.route('/rest/getAlbum.view', methods = [ 'GET', 'POST' ]) def album_info(): - status, res = get_entity(request, Album) - if not status: - return res + status, res = get_entity(request, Album) + if not status: + return res - info = res.as_subsonic_album(request.user) - info['song'] = [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] + info = res.as_subsonic_album(request.user) + info['song'] = [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ] - return request.formatter({ 'album': info }) + return request.formatter({ 'album': info }) @app.route('/rest/getSong.view', methods = [ 'GET', 'POST' ]) def track_info(): - status, res = get_entity(request, Track) - if not status: - return res + status, res = get_entity(request, Track) + if not status: + return res - return request.formatter({ 'song': res.as_subsonic_child(request.user, request.prefs) }) + return request.formatter({ 'song': res.as_subsonic_child(request.user, request.prefs) }) @app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ]) def list_videos(): - return request.error_formatter(0, 'Video streaming not supported') + return request.error_formatter(0, 'Video streaming not supported') diff --git a/supysonic/api/chat.py b/supysonic/api/chat.py index c792caf..afb3e24 100644 --- a/supysonic/api/chat.py +++ b/supysonic/api/chat.py @@ -24,28 +24,28 @@ from supysonic.db import ChatMessage @app.route('/rest/getChatMessages.view', methods = [ 'GET', 'POST' ]) def get_chat(): - since = request.values.get('since') - try: - since = int(since) / 1000 if since else None - except: - return request.error_formatter(0, 'Invalid parameter') + since = request.values.get('since') + try: + since = int(since) / 1000 if since else None + except: + return request.error_formatter(0, 'Invalid parameter') - query = store.find(ChatMessage).order_by(ChatMessage.time) - if since: - query = query.find(ChatMessage.time > since) + query = store.find(ChatMessage).order_by(ChatMessage.time) + if since: + query = query.find(ChatMessage.time > since) - return request.formatter({ 'chatMessages': { 'chatMessage': [ msg.responsize() for msg in query ] }}) + return request.formatter({ 'chatMessages': { 'chatMessage': [ msg.responsize() for msg in query ] }}) @app.route('/rest/addChatMessage.view', methods = [ 'GET', 'POST' ]) def add_chat_message(): - msg = request.values.get('message') - if not msg: - return request.error_formatter(10, 'Missing message') + msg = request.values.get('message') + if not msg: + return request.error_formatter(10, 'Missing message') - chat = ChatMessage() - chat.user_id = request.user.id - chat.message = msg - store.add(chat) - store.commit() - return request.formatter({}) + chat = ChatMessage() + chat.user_id = request.user.id + chat.message = msg + store.add(chat) + store.commit() + return request.formatter({}) diff --git a/supysonic/api/media.py b/supysonic/api/media.py index a5e6245..bdc0833 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -32,197 +32,197 @@ from supysonic.db import Track, Album, Artist, Folder, User, ClientPrefs, now from . import get_entity def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate): - if not base_cmdline: - return None - ret = base_cmdline.split() - for i in xrange(len(ret)): - ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate)) - return ret + if not base_cmdline: + return None + ret = base_cmdline.split() + for i in xrange(len(ret)): + ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate)) + return ret @app.route('/rest/stream.view', methods = [ 'GET', 'POST' ]) def stream_media(): - status, res = get_entity(request, Track) - if not status: - return res + status, res = get_entity(request, Track) + if not status: + return res - maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ]) - if format: - format = format.lower() + maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ]) + if format: + format = format.lower() - src_suffix = res.suffix() - dst_suffix = res.suffix() - dst_bitrate = res.bitrate - dst_mimetype = res.content_type + src_suffix = res.suffix() + dst_suffix = res.suffix() + dst_bitrate = res.bitrate + dst_mimetype = res.content_type - if request.prefs.format: - dst_suffix = request.prefs.format - if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate: - dst_bitrate = request.prefs.bitrate + if request.prefs.format: + dst_suffix = request.prefs.format + if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate: + dst_bitrate = request.prefs.bitrate - if maxBitRate: - try: - maxBitRate = int(maxBitRate) - except: - return request.error_formatter(0, 'Invalid bitrate value') + if maxBitRate: + try: + maxBitRate = int(maxBitRate) + except: + return request.error_formatter(0, 'Invalid bitrate value') - if dst_bitrate > maxBitRate and maxBitRate != 0: - dst_bitrate = maxBitRate + if dst_bitrate > maxBitRate and maxBitRate != 0: + dst_bitrate = maxBitRate - if format and format != 'raw' and format != src_suffix: - dst_suffix = format - dst_mimetype = config.get_mime(dst_suffix) + if format and format != 'raw' and format != src_suffix: + dst_suffix = format + dst_mimetype = config.get_mime(dst_suffix) - if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate): - transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix)) - decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder') - encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder') - if not transcoder and (not decoder or not encoder): - transcoder = config.get('transcoding', 'transcoder') - if not transcoder: - message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix) - app.logger.info(message) - return request.error_formatter(0, message) + if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate): + transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix)) + decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder') + encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder') + if not transcoder and (not decoder or not encoder): + transcoder = config.get('transcoding', 'transcoder') + if not transcoder: + message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix) + app.logger.info(message) + return request.error_formatter(0, message) - transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ]) - try: - if transcoder: - dec_proc = None - proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE) - else: - dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE) - proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE) - except: - return request.error_formatter(0, 'Error while running the transcoding process') + transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ]) + try: + if transcoder: + dec_proc = None + proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE) + else: + dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE) + proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE) + except: + return request.error_formatter(0, 'Error while running the transcoding process') - def transcode(): - try: - while True: - data = proc.stdout.read(8192) - if not data: - break - yield data - except: - if dec_proc != None: - dec_proc.terminate() - proc.terminate() + def transcode(): + try: + while True: + data = proc.stdout.read(8192) + if not data: + break + yield data + except: + if dec_proc != None: + dec_proc.terminate() + proc.terminate() - if dec_proc != None: - dec_proc.wait() - proc.wait() + if dec_proc != None: + dec_proc.wait() + proc.wait() - app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate)) - response = Response(transcode(), mimetype = dst_mimetype) - else: - response = send_file(res.path, mimetype = dst_mimetype, conditional=True) + app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate)) + response = Response(transcode(), mimetype = dst_mimetype) + else: + response = send_file(res.path, mimetype = dst_mimetype, conditional=True) - res.play_count = res.play_count + 1 - res.last_play = now() - request.user.last_play = res - request.user.last_play_date = now() - store.commit() + res.play_count = res.play_count + 1 + res.last_play = now() + request.user.last_play = res + request.user.last_play_date = now() + store.commit() - return response + return response @app.route('/rest/download.view', methods = [ 'GET', 'POST' ]) def download_media(): - status, res = get_entity(request, Track) - if not status: - return res + status, res = get_entity(request, Track) + if not status: + return res - return send_file(res.path, conditional=True) + return send_file(res.path, conditional=True) @app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ]) def cover_art(): - status, res = get_entity(request, Folder) - if not status: - return res + status, res = get_entity(request, Folder) + if not status: + return res - if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')): - return request.error_formatter(70, 'Cover art not found') + if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')): + return request.error_formatter(70, 'Cover art not found') - size = request.values.get('size') - if size: - try: - size = int(size) - except: - return request.error_formatter(0, 'Invalid size value') - else: - return send_file(os.path.join(res.path, 'cover.jpg')) + size = request.values.get('size') + if size: + try: + size = int(size) + except: + return request.error_formatter(0, 'Invalid size value') + else: + return send_file(os.path.join(res.path, 'cover.jpg')) - im = Image.open(os.path.join(res.path, 'cover.jpg')) - if size > im.size[0] and size > im.size[1]: - return send_file(os.path.join(res.path, 'cover.jpg')) + im = Image.open(os.path.join(res.path, 'cover.jpg')) + if size > im.size[0] and size > im.size[1]: + return send_file(os.path.join(res.path, 'cover.jpg')) - size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size)) - path = os.path.abspath(os.path.join(size_path, str(res.id))) - if os.path.exists(path): - return send_file(path) - if not os.path.exists(size_path): - os.makedirs(size_path) + size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size)) + path = os.path.abspath(os.path.join(size_path, str(res.id))) + if os.path.exists(path): + return send_file(path) + if not os.path.exists(size_path): + os.makedirs(size_path) - im.thumbnail([size, size], Image.ANTIALIAS) - im.save(path, 'JPEG') - return send_file(path) + im.thumbnail([size, size], Image.ANTIALIAS) + im.save(path, 'JPEG') + return send_file(path) @app.route('/rest/getLyrics.view', methods = [ 'GET', 'POST' ]) def lyrics(): - artist, title = map(request.values.get, [ 'artist', 'title' ]) - if not artist: - return request.error_formatter(10, 'Missing artist parameter') - if not title: - return request.error_formatter(10, 'Missing title parameter') + artist, title = map(request.values.get, [ 'artist', 'title' ]) + if not artist: + return request.error_formatter(10, 'Missing artist parameter') + if not title: + return request.error_formatter(10, 'Missing title parameter') - query = store.find(Track, Album.id == Track.album_id, Artist.id == Album.artist_id, Track.title.like(title), Artist.name.like(artist)) - for track in query: - lyrics_path = os.path.splitext(track.path)[0] + '.txt' - if os.path.exists(lyrics_path): - app.logger.debug('Found lyrics file: ' + lyrics_path) + query = store.find(Track, Album.id == Track.album_id, Artist.id == Album.artist_id, Track.title.like(title), Artist.name.like(artist)) + for track in query: + lyrics_path = os.path.splitext(track.path)[0] + '.txt' + if os.path.exists(lyrics_path): + app.logger.debug('Found lyrics file: ' + lyrics_path) - try: - lyrics = read_file_as_unicode(lyrics_path) - except UnicodeError: - # Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or - # return no lyrics. Log it anyway. - app.logger.warn('Unsupported encoding for lyrics file ' + lyrics_path) - continue + try: + lyrics = read_file_as_unicode(lyrics_path) + except UnicodeError: + # Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or + # return no lyrics. Log it anyway. + app.logger.warn('Unsupported encoding for lyrics file ' + lyrics_path) + continue - return request.formatter({ 'lyrics': { - 'artist': track.album.artist.name, - 'title': track.title, - '_value_': lyrics - } }) + return request.formatter({ 'lyrics': { + 'artist': track.album.artist.name, + 'title': track.title, + '_value_': lyrics + } }) - try: - r = requests.get("http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect", - params = { 'artist': artist, 'song': title }) - root = ElementTree.fromstring(r.content) + try: + r = requests.get("http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect", + params = { 'artist': artist, 'song': title }) + root = ElementTree.fromstring(r.content) - ns = { 'cl': 'http://api.chartlyrics.com/' } - return request.formatter({ 'lyrics': { - 'artist': root.find('cl:LyricArtist', namespaces = ns).text, - 'title': root.find('cl:LyricSong', namespaces = ns).text, - '_value_': root.find('cl:Lyric', namespaces = ns).text - } }) - except requests.exceptions.RequestException, e: - app.logger.warn('Error while requesting the ChartLyrics API: ' + str(e)) + ns = { 'cl': 'http://api.chartlyrics.com/' } + return request.formatter({ 'lyrics': { + 'artist': root.find('cl:LyricArtist', namespaces = ns).text, + 'title': root.find('cl:LyricSong', namespaces = ns).text, + '_value_': root.find('cl:Lyric', namespaces = ns).text + } }) + except requests.exceptions.RequestException, e: + app.logger.warn('Error while requesting the ChartLyrics API: ' + str(e)) - return request.formatter({ 'lyrics': {} }) + return request.formatter({ 'lyrics': {} }) def read_file_as_unicode(path): - """ Opens a file trying with different encodings and returns the contents as a unicode string """ + """ Opens a file trying with different encodings and returns the contents as a unicode string """ - encodings = [ 'utf-8', 'latin1' ] # Should be extended to support more encodings + encodings = [ 'utf-8', 'latin1' ] # Should be extended to support more encodings - for enc in encodings: - try: - contents = codecs.open(path, 'r', encoding = enc).read() - app.logger.debug('Read file {} with {} encoding'.format(path, enc)) - # Maybe save the encoding somewhere to prevent going through this loop each time for the same file - return contents - except UnicodeError: - pass + for enc in encodings: + try: + contents = codecs.open(path, 'r', encoding = enc).read() + app.logger.debug('Read file {} with {} encoding'.format(path, enc)) + # Maybe save the encoding somewhere to prevent going through this loop each time for the same file + return contents + except UnicodeError: + pass - # Fallback to ASCII - app.logger.debug('Reading file {} with ascii encoding'.format(path)) - return unicode(open(path, 'r').read()) + # Fallback to ASCII + app.logger.debug('Reading file {} with ascii encoding'.format(path)) + return unicode(open(path, 'r').read()) diff --git a/supysonic/api/playlists.py b/supysonic/api/playlists.py index 07d7099..8a36409 100644 --- a/supysonic/api/playlists.py +++ b/supysonic/api/playlists.py @@ -27,114 +27,114 @@ from . import get_entity @app.route('/rest/getPlaylists.view', methods = [ 'GET', 'POST' ]) def list_playlists(): - query = store.find(Playlist, Or(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(Playlist.name) + query = store.find(Playlist, Or(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(Playlist.name) - username = request.values.get('username') - if username: - if not request.user.admin: - return request.error_formatter(50, 'Restricted to admins') + username = request.values.get('username') + if username: + if not request.user.admin: + return request.error_formatter(50, 'Restricted to admins') - query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name) + query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name) - return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } }) + return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } }) @app.route('/rest/getPlaylist.view', methods = [ 'GET', 'POST' ]) def show_playlist(): - status, res = get_entity(request, Playlist) - if not status: - return res + status, res = get_entity(request, Playlist) + if not status: + return res - info = res.as_subsonic_playlist(request.user) - info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ] - return request.formatter({ 'playlist': info }) + info = res.as_subsonic_playlist(request.user) + info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ] + return request.formatter({ 'playlist': info }) @app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ]) def create_playlist(): - # Only(?) method where the android client uses form data rather than GET params - playlist_id, name = map(request.values.get, [ 'playlistId', 'name' ]) - # songId actually doesn't seem to be required - songs = request.values.getlist('songId') - try: - playlist_id = uuid.UUID(playlist_id) if playlist_id else None - songs = map(uuid.UUID, songs) - except: - return request.error_formatter(0, 'Invalid parameter') + # Only(?) method where the android client uses form data rather than GET params + playlist_id, name = map(request.values.get, [ 'playlistId', 'name' ]) + # songId actually doesn't seem to be required + songs = request.values.getlist('songId') + try: + playlist_id = uuid.UUID(playlist_id) if playlist_id else None + songs = map(uuid.UUID, songs) + except: + return request.error_formatter(0, 'Invalid parameter') - if playlist_id: - playlist = store.get(Playlist, playlist_id) - if not playlist: - return request.error_formatter(70, 'Unknwon playlist') + if playlist_id: + playlist = store.get(Playlist, playlist_id) + if not playlist: + return request.error_formatter(70, 'Unknwon playlist') - if playlist.user_id != request.user.id and not request.user.admin: - return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours") + if playlist.user_id != request.user.id and not request.user.admin: + return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours") - playlist.clear() - if name: - playlist.name = name - elif name: - playlist = Playlist() - playlist.user_id = request.user.id - playlist.name = name - store.add(playlist) - else: - return request.error_formatter(10, 'Missing playlist id or name') + playlist.clear() + if name: + playlist.name = name + elif name: + playlist = Playlist() + playlist.user_id = request.user.id + playlist.name = name + store.add(playlist) + else: + return request.error_formatter(10, 'Missing playlist id or name') - for sid in songs: - track = store.get(Track, sid) - if not track: - return request.error_formatter(70, 'Unknown song') + for sid in songs: + track = store.get(Track, sid) + if not track: + return request.error_formatter(70, 'Unknown song') - playlist.add(track) + playlist.add(track) - store.commit() - return request.formatter({}) + store.commit() + return request.formatter({}) @app.route('/rest/deletePlaylist.view', methods = [ 'GET', 'POST' ]) def delete_playlist(): - status, res = get_entity(request, Playlist) - if not status: - return res + status, res = get_entity(request, Playlist) + if not status: + return res - if res.user_id != request.user.id and not request.user.admin: - return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") + if res.user_id != request.user.id and not request.user.admin: + return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") - store.remove(res) - store.commit() - return request.formatter({}) + store.remove(res) + store.commit() + return request.formatter({}) @app.route('/rest/updatePlaylist.view', methods = [ 'GET', 'POST' ]) def update_playlist(): - status, res = get_entity(request, Playlist, 'playlistId') - if not status: - return res + status, res = get_entity(request, Playlist, 'playlistId') + if not status: + return res - if res.user_id != request.user.id and not request.user.admin: - return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") + if res.user_id != request.user.id and not request.user.admin: + return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours") - playlist = res - name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ]) - to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ]) - try: - to_add = map(uuid.UUID, to_add) - to_remove = map(int, to_remove) - except: - return request.error_formatter(0, 'Invalid parameter') + playlist = res + name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ]) + to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ]) + try: + to_add = map(uuid.UUID, to_add) + to_remove = map(int, to_remove) + except: + return request.error_formatter(0, 'Invalid parameter') - if name: - playlist.name = name - if comment: - playlist.comment = comment - if public: - playlist.public = public in (True, 'True', 'true', 1, '1') + if name: + playlist.name = name + if comment: + playlist.comment = comment + if public: + playlist.public = public in (True, 'True', 'true', 1, '1') - for sid in to_add: - track = store.get(Track, sid) - if not track: - return request.error_formatter(70, 'Unknown song') - playlist.add(track) + for sid in to_add: + track = store.get(Track, sid) + if not track: + return request.error_formatter(70, 'Unknown song') + playlist.add(track) - playlist.remove_at_indexes(to_remove) + playlist.remove_at_indexes(to_remove) - store.commit() - return request.formatter({}) + store.commit() + return request.formatter({}) diff --git a/supysonic/api/search.py b/supysonic/api/search.py index 9bba11d..8a83467 100644 --- a/supysonic/api/search.py +++ b/supysonic/api/search.py @@ -25,98 +25,98 @@ from supysonic.db import Folder, Track, Artist, Album @app.route('/rest/search.view', methods = [ 'GET', 'POST' ]) def old_search(): - artist, album, title, anyf, count, offset, newer_than = map(request.values.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ]) - try: - count = int(count) if count else 20 - offset = int(offset) if offset else 0 - newer_than = int(newer_than) if newer_than else 0 - except: - return request.error_formatter(0, 'Invalid parameter') + artist, album, title, anyf, count, offset, newer_than = map(request.values.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ]) + try: + count = int(count) if count else 20 + offset = int(offset) if offset else 0 + newer_than = int(newer_than) if newer_than else 0 + except: + return request.error_formatter(0, 'Invalid parameter') - if artist: - parent = ClassAlias(Folder) - query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(artist)).config(distinct = True) - elif album: - query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(album)).config(distinct = True) - elif title: - query = store.find(Track, Track.title.contains_string(title)) - elif anyf: - folders = store.find(Folder, Folder.name.contains_string(anyf)) - tracks = store.find(Track, Track.title.contains_string(anyf)) - res = list(folders[offset : offset + count]) - if offset + count > folders.count(): - toff = max(0, offset - folders.count()) - tend = offset + count - folders.count() - res += list(tracks[toff : tend]) + if artist: + parent = ClassAlias(Folder) + query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(artist)).config(distinct = True) + elif album: + query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(album)).config(distinct = True) + elif title: + query = store.find(Track, Track.title.contains_string(title)) + elif anyf: + folders = store.find(Folder, Folder.name.contains_string(anyf)) + tracks = store.find(Track, Track.title.contains_string(anyf)) + res = list(folders[offset : offset + count]) + if offset + count > folders.count(): + toff = max(0, offset - folders.count()) + tend = offset + count - folders.count() + res += list(tracks[toff : tend]) - return request.formatter({ 'searchResult': { - 'totalHits': folders.count() + tracks.count(), - 'offset': offset, - 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in res ] - }}) - else: - return request.error_formatter(10, 'Missing search parameter') + return request.formatter({ 'searchResult': { + 'totalHits': folders.count() + tracks.count(), + 'offset': offset, + 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in res ] + }}) + else: + return request.error_formatter(10, 'Missing search parameter') - return request.formatter({ 'searchResult': { - 'totalHits': query.count(), - 'offset': offset, - 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ] - }}) + return request.formatter({ 'searchResult': { + 'totalHits': query.count(), + 'offset': offset, + 'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ] + }}) @app.route('/rest/search2.view', methods = [ 'GET', 'POST' ]) def new_search(): - query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( - request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) + query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( + request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) - try: - artist_count = int(artist_count) if artist_count else 20 - artist_offset = int(artist_offset) if artist_offset else 0 - album_count = int(album_count) if album_count else 20 - album_offset = int(album_offset) if album_offset else 0 - song_count = int(song_count) if song_count else 20 - song_offset = int(song_offset) if song_offset else 0 - except: - return request.error_formatter(0, 'Invalid parameter') + try: + artist_count = int(artist_count) if artist_count else 20 + artist_offset = int(artist_offset) if artist_offset else 0 + album_count = int(album_count) if album_count else 20 + album_offset = int(album_offset) if album_offset else 0 + song_count = int(song_count) if song_count else 20 + song_offset = int(song_offset) if song_offset else 0 + except: + return request.error_formatter(0, 'Invalid parameter') - if not query: - return request.error_formatter(10, 'Missing query parameter') + if not query: + return request.error_formatter(10, 'Missing query parameter') - parent = ClassAlias(Folder) - artist_query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct = True, offset = artist_offset, limit = artist_count) - album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config(distinct = True, offset = album_offset, limit = album_count) - song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] + parent = ClassAlias(Folder) + artist_query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct = True, offset = artist_offset, limit = artist_count) + album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config(distinct = True, offset = album_offset, limit = album_count) + song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] - return request.formatter({ 'searchResult2': { - 'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ], - 'album': [ f.as_subsonic_child(request.user) for f in album_query ], - 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] - }}) + return request.formatter({ 'searchResult2': { + 'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ], + 'album': [ f.as_subsonic_child(request.user) for f in album_query ], + 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] + }}) @app.route('/rest/search3.view', methods = [ 'GET', 'POST' ]) def search_id3(): - query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( - request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) + query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map( + request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ]) - try: - artist_count = int(artist_count) if artist_count else 20 - artist_offset = int(artist_offset) if artist_offset else 0 - album_count = int(album_count) if album_count else 20 - album_offset = int(album_offset) if album_offset else 0 - song_count = int(song_count) if song_count else 20 - song_offset = int(song_offset) if song_offset else 0 - except: - return request.error_formatter(0, 'Invalid parameter') + try: + artist_count = int(artist_count) if artist_count else 20 + artist_offset = int(artist_offset) if artist_offset else 0 + album_count = int(album_count) if album_count else 20 + album_offset = int(album_offset) if album_offset else 0 + song_count = int(song_count) if song_count else 20 + song_offset = int(song_offset) if song_offset else 0 + except: + return request.error_formatter(0, 'Invalid parameter') - if not query: - return request.error_formatter(10, 'Missing query parameter') + if not query: + return request.error_formatter(10, 'Missing query parameter') - artist_query = store.find(Artist, Artist.name.contains_string(query))[artist_offset : artist_offset + artist_count] - album_query = store.find(Album, Album.name.contains_string(query))[album_offset : album_offset + album_count] - song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] + artist_query = store.find(Artist, Artist.name.contains_string(query))[artist_offset : artist_offset + artist_count] + album_query = store.find(Album, Album.name.contains_string(query))[album_offset : album_offset + album_count] + song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count] - return request.formatter({ 'searchResult3': { - 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], - 'album': [ a.as_subsonic_album(request.user) for a in album_query ], - 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] - }}) + return request.formatter({ 'searchResult3': { + 'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ], + 'album': [ a.as_subsonic_album(request.user) for a in album_query ], + 'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ] + }}) diff --git a/supysonic/api/system.py b/supysonic/api/system.py index 3f8871c..228400f 100644 --- a/supysonic/api/system.py +++ b/supysonic/api/system.py @@ -23,9 +23,9 @@ from supysonic.web import app @app.route('/rest/ping.view', methods = [ 'GET', 'POST' ]) def ping(): - return request.formatter({}) + return request.formatter({}) @app.route('/rest/getLicense.view', methods = [ 'GET', 'POST' ]) def license(): - return request.formatter({ 'license': { 'valid': True } }) + return request.formatter({ 'license': { 'valid': True } }) diff --git a/supysonic/api/user.py b/supysonic/api/user.py index 376417b..1303e88 100644 --- a/supysonic/api/user.py +++ b/supysonic/api/user.py @@ -25,70 +25,70 @@ from supysonic.managers.user import UserManager @app.route('/rest/getUser.view', methods = [ 'GET', 'POST' ]) def user_info(): - username = request.values.get('username') - if username is None: - return request.error_formatter(10, 'Missing username') + username = request.values.get('username') + if username is None: + return request.error_formatter(10, 'Missing username') - if username != request.username and not request.user.admin: - return request.error_formatter(50, 'Admin restricted') + if username != request.username and not request.user.admin: + return request.error_formatter(50, 'Admin restricted') - user = store.find(User, User.name == username).one() - if user is None: - return request.error_formatter(0, 'Unknown user') + user = store.find(User, User.name == username).one() + if user is None: + return request.error_formatter(0, 'Unknown user') - return request.formatter({ 'user': user.as_subsonic_user() }) + return request.formatter({ 'user': user.as_subsonic_user() }) @app.route('/rest/getUsers.view', methods = [ 'GET', 'POST' ]) def users_info(): - if not request.user.admin: - return request.error_formatter(50, 'Admin restricted') + if not request.user.admin: + return request.error_formatter(50, 'Admin restricted') - return request.formatter({ 'users': { 'user': [ u.as_subsonic_user() for u in store.find(User) ] } }) + return request.formatter({ 'users': { 'user': [ u.as_subsonic_user() for u in store.find(User) ] } }) @app.route('/rest/createUser.view', methods = [ 'GET', 'POST' ]) def user_add(): - if not request.user.admin: - return request.error_formatter(50, 'Admin restricted') + if not request.user.admin: + return request.error_formatter(50, 'Admin restricted') - username, password, email, admin = map(request.values.get, [ 'username', 'password', 'email', 'adminRole' ]) - if not username or not password or not email: - return request.error_formatter(10, 'Missing parameter') - admin = True if admin in (True, 'True', 'true', 1, '1') else False + username, password, email, admin = map(request.values.get, [ 'username', 'password', 'email', 'adminRole' ]) + if not username or not password or not email: + return request.error_formatter(10, 'Missing parameter') + admin = True if admin in (True, 'True', 'true', 1, '1') else False - status = UserManager.add(store, username, password, email, admin) - if status == UserManager.NAME_EXISTS: - return request.error_formatter(0, 'There is already a user with that username') + status = UserManager.add(store, username, password, email, admin) + if status == UserManager.NAME_EXISTS: + return request.error_formatter(0, 'There is already a user with that username') - return request.formatter({}) + return request.formatter({}) @app.route('/rest/deleteUser.view', methods = [ 'GET', 'POST' ]) def user_del(): - if not request.user.admin: - return request.error_formatter(50, 'Admin restricted') + if not request.user.admin: + return request.error_formatter(50, 'Admin restricted') - username = request.values.get('username') - user = store.find(User, User.name == username).one() - if not user: - return request.error_formatter(70, 'Unknown user') + username = request.values.get('username') + user = store.find(User, User.name == username).one() + if not user: + return request.error_formatter(70, 'Unknown user') - status = UserManager.delete(store, user.id) - if status != UserManager.SUCCESS: - return request.error_formatter(0, UserManager.error_str(status)) + status = UserManager.delete(store, user.id) + if status != UserManager.SUCCESS: + return request.error_formatter(0, UserManager.error_str(status)) - return request.formatter({}) + return request.formatter({}) @app.route('/rest/changePassword.view', methods = [ 'GET', 'POST' ]) def user_changepass(): - username, password = map(request.values.get, [ 'username', 'password' ]) - if not username or not password: - return request.error_formatter(10, 'Missing parameter') + username, password = map(request.values.get, [ 'username', 'password' ]) + if not username or not password: + return request.error_formatter(10, 'Missing parameter') - if username != request.username and not request.user.admin: - return request.error_formatter(50, 'Admin restricted') + if username != request.username and not request.user.admin: + return request.error_formatter(50, 'Admin restricted') - status = UserManager.change_password2(store, username, password) - if status != UserManager.SUCCESS: - return request.error_formatter(0, UserManager.error_str(status)) + status = UserManager.change_password2(store, username, password) + if status != UserManager.SUCCESS: + return request.error_formatter(0, UserManager.error_str(status)) - return request.formatter({}) + return request.formatter({}) diff --git a/supysonic/db.py b/supysonic/db.py index 2dd3492..d1e9593 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -30,397 +30,397 @@ import os.path from supysonic import config def now(): - return datetime.datetime.now().replace(microsecond = 0) + return datetime.datetime.now().replace(microsecond = 0) class UnicodeOrStrVariable(Variable): - __slots__ = () + __slots__ = () - def parse_set(self, value, from_db): - if isinstance(value, unicode): - return value - elif isinstance(value, str): - return unicode(value) - raise TypeError("Expected unicode, found %r: %r" % (type(value), value)) + def parse_set(self, value, from_db): + if isinstance(value, unicode): + return value + elif isinstance(value, str): + return unicode(value) + raise TypeError("Expected unicode, found %r: %r" % (type(value), value)) Unicode.variable_class = UnicodeOrStrVariable class Folder(object): - __storm_table__ = 'folder' + __storm_table__ = 'folder' - id = UUID(primary = True, default_factory = uuid.uuid4) - root = Bool(default = False) - name = Unicode() - path = Unicode() # unique - created = DateTime(default_factory = now) - has_cover_art = Bool(default = False) - last_scan = Int(default = 0) + id = UUID(primary = True, default_factory = uuid.uuid4) + root = Bool(default = False) + name = Unicode() + path = Unicode() # unique + created = DateTime(default_factory = now) + has_cover_art = Bool(default = False) + last_scan = Int(default = 0) - parent_id = UUID() # nullable - parent = Reference(parent_id, id) - children = ReferenceSet(id, parent_id) + parent_id = UUID() # nullable + parent = Reference(parent_id, id) + children = ReferenceSet(id, parent_id) - def as_subsonic_child(self, user): - info = { - 'id': str(self.id), - 'isDir': True, - 'title': self.name, - 'album': self.name, - 'created': self.created.isoformat() - } - if not self.root: - info['parent'] = str(self.parent_id) - info['artist'] = self.parent.name - if self.has_cover_art: - info['coverArt'] = str(self.id) + def as_subsonic_child(self, user): + info = { + 'id': str(self.id), + 'isDir': True, + 'title': self.name, + 'album': self.name, + 'created': self.created.isoformat() + } + if not self.root: + info['parent'] = str(self.parent_id) + info['artist'] = self.parent.name + if self.has_cover_art: + info['coverArt'] = str(self.id) - starred = Store.of(self).get(StarredFolder, (user.id, self.id)) - if starred: - info['starred'] = starred.date.isoformat() + starred = Store.of(self).get(StarredFolder, (user.id, self.id)) + if starred: + info['starred'] = starred.date.isoformat() - rating = Store.of(self).get(RatingFolder, (user.id, self.id)) - if rating: - info['userRating'] = rating.rating - avgRating = Store.of(self).find(RatingFolder, RatingFolder.rated_id == self.id).avg(RatingFolder.rating) - if avgRating: - info['averageRating'] = avgRating + rating = Store.of(self).get(RatingFolder, (user.id, self.id)) + if rating: + info['userRating'] = rating.rating + avgRating = Store.of(self).find(RatingFolder, RatingFolder.rated_id == self.id).avg(RatingFolder.rating) + if avgRating: + info['averageRating'] = avgRating - return info + return info class Artist(object): - __storm_table__ = 'artist' + __storm_table__ = 'artist' - id = UUID(primary = True, default_factory = uuid.uuid4) - name = Unicode() # unique + id = UUID(primary = True, default_factory = uuid.uuid4) + name = Unicode() # unique - def as_subsonic_artist(self, user): - info = { - 'id': str(self.id), - 'name': self.name, - # coverArt - 'albumCount': self.albums.count() - } + def as_subsonic_artist(self, user): + info = { + 'id': str(self.id), + 'name': self.name, + # coverArt + 'albumCount': self.albums.count() + } - starred = Store.of(self).get(StarredArtist, (user.id, self.id)) - if starred: - info['starred'] = starred.date.isoformat() + starred = Store.of(self).get(StarredArtist, (user.id, self.id)) + if starred: + info['starred'] = starred.date.isoformat() - return info + return info class Album(object): - __storm_table__ = 'album' + __storm_table__ = 'album' - id = UUID(primary = True, default_factory = uuid.uuid4) - name = Unicode() - artist_id = UUID() - artist = Reference(artist_id, Artist.id) + id = UUID(primary = True, default_factory = uuid.uuid4) + name = Unicode() + artist_id = UUID() + artist = Reference(artist_id, Artist.id) - def as_subsonic_album(self, user): - info = { - 'id': str(self.id), - 'name': self.name, - 'artist': self.artist.name, - 'artistId': str(self.artist_id), - 'songCount': self.tracks.count(), - 'duration': sum(self.tracks.values(Track.duration)), - 'created': min(self.tracks.values(Track.created)).isoformat() - } + def as_subsonic_album(self, user): + info = { + 'id': str(self.id), + 'name': self.name, + 'artist': self.artist.name, + 'artistId': str(self.artist_id), + 'songCount': self.tracks.count(), + 'duration': sum(self.tracks.values(Track.duration)), + 'created': min(self.tracks.values(Track.created)).isoformat() + } - track_with_cover = self.tracks.find(Track.folder_id == Folder.id, Folder.has_cover_art).any() - if track_with_cover: - info['coverArt'] = str(track_with_cover.folder_id) + track_with_cover = self.tracks.find(Track.folder_id == Folder.id, Folder.has_cover_art).any() + if track_with_cover: + info['coverArt'] = str(track_with_cover.folder_id) - starred = Store.of(self).get(StarredAlbum, (user.id, self.id)) - if starred: - info['starred'] = starred.date.isoformat() + starred = Store.of(self).get(StarredAlbum, (user.id, self.id)) + if starred: + info['starred'] = starred.date.isoformat() - return info + return info - def sort_key(self): - year = min(map(lambda t: t.year if t.year else 9999, self.tracks)) - return '%i%s' % (year, self.name.lower()) + def sort_key(self): + year = min(map(lambda t: t.year if t.year else 9999, self.tracks)) + return '%i%s' % (year, self.name.lower()) Artist.albums = ReferenceSet(Artist.id, Album.artist_id) class Track(object): - __storm_table__ = 'track' + __storm_table__ = 'track' - id = UUID(primary = True, default_factory = uuid.uuid4) - disc = Int() - number = Int() - title = Unicode() - year = Int() # nullable - genre = Unicode() # nullable - duration = Int() - album_id = UUID() - album = Reference(album_id, Album.id) - artist_id = UUID() - artist = Reference(artist_id, Artist.id) - bitrate = Int() + id = UUID(primary = True, default_factory = uuid.uuid4) + disc = Int() + number = Int() + title = Unicode() + year = Int() # nullable + genre = Unicode() # nullable + duration = Int() + album_id = UUID() + album = Reference(album_id, Album.id) + artist_id = UUID() + artist = Reference(artist_id, Artist.id) + bitrate = Int() - path = Unicode() # unique - content_type = Unicode() - created = DateTime(default_factory = now) - last_modification = Int() + path = Unicode() # unique + content_type = Unicode() + created = DateTime(default_factory = now) + last_modification = Int() - play_count = Int(default = 0) - last_play = DateTime() # nullable + play_count = Int(default = 0) + last_play = DateTime() # nullable - root_folder_id = UUID() - root_folder = Reference(root_folder_id, Folder.id) - folder_id = UUID() - folder = Reference(folder_id, Folder.id) + root_folder_id = UUID() + root_folder = Reference(root_folder_id, Folder.id) + folder_id = UUID() + folder = Reference(folder_id, Folder.id) - def as_subsonic_child(self, user, prefs): - info = { - 'id': str(self.id), - 'parent': str(self.folder_id), - 'isDir': False, - 'title': self.title, - 'album': self.album.name, - 'artist': self.artist.name, - 'track': self.number, - 'size': os.path.getsize(self.path), - 'contentType': self.content_type, - 'suffix': self.suffix(), - 'duration': self.duration, - 'bitRate': self.bitrate, - 'path': self.path[len(self.root_folder.path) + 1:], - 'isVideo': False, - 'discNumber': self.disc, - 'created': self.created.isoformat(), - 'albumId': str(self.album_id), - 'artistId': str(self.artist_id), - 'type': 'music' - } + def as_subsonic_child(self, user, prefs): + info = { + 'id': str(self.id), + 'parent': str(self.folder_id), + 'isDir': False, + 'title': self.title, + 'album': self.album.name, + 'artist': self.artist.name, + 'track': self.number, + 'size': os.path.getsize(self.path), + 'contentType': self.content_type, + 'suffix': self.suffix(), + 'duration': self.duration, + 'bitRate': self.bitrate, + 'path': self.path[len(self.root_folder.path) + 1:], + 'isVideo': False, + 'discNumber': self.disc, + 'created': self.created.isoformat(), + 'albumId': str(self.album_id), + 'artistId': str(self.artist_id), + 'type': 'music' + } - if self.year: - info['year'] = self.year - if self.genre: - info['genre'] = self.genre - if self.folder.has_cover_art: - info['coverArt'] = str(self.folder_id) + if self.year: + info['year'] = self.year + if self.genre: + info['genre'] = self.genre + if self.folder.has_cover_art: + info['coverArt'] = str(self.folder_id) - starred = Store.of(self).get(StarredTrack, (user.id, self.id)) - if starred: - info['starred'] = starred.date.isoformat() + starred = Store.of(self).get(StarredTrack, (user.id, self.id)) + if starred: + info['starred'] = starred.date.isoformat() - rating = Store.of(self).get(RatingTrack, (user.id, self.id)) - if rating: - info['userRating'] = rating.rating - avgRating = Store.of(self).find(RatingTrack, RatingTrack.rated_id == self.id).avg(RatingTrack.rating) - if avgRating: - info['averageRating'] = avgRating + rating = Store.of(self).get(RatingTrack, (user.id, self.id)) + if rating: + info['userRating'] = rating.rating + avgRating = Store.of(self).find(RatingTrack, RatingTrack.rated_id == self.id).avg(RatingTrack.rating) + if avgRating: + info['averageRating'] = avgRating - if prefs and prefs.format and prefs.format != self.suffix(): - info['transcodedSuffix'] = prefs.format - info['transcodedContentType'] = config.get_mime(prefs.format) + if prefs and prefs.format and prefs.format != self.suffix(): + info['transcodedSuffix'] = prefs.format + info['transcodedContentType'] = config.get_mime(prefs.format) - return info + return info - def duration_str(self): - ret = '%02i:%02i' % ((self.duration % 3600) / 60, self.duration % 60) - if self.duration >= 3600: - ret = '%02i:%s' % (self.duration / 3600, ret) - return ret + def duration_str(self): + ret = '%02i:%02i' % ((self.duration % 3600) / 60, self.duration % 60) + if self.duration >= 3600: + ret = '%02i:%s' % (self.duration / 3600, ret) + return ret - def suffix(self): - return os.path.splitext(self.path)[1][1:].lower() + def suffix(self): + return os.path.splitext(self.path)[1][1:].lower() - def sort_key(self): - return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() + def sort_key(self): + return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() Folder.tracks = ReferenceSet(Folder.id, Track.folder_id) Album.tracks = ReferenceSet(Album.id, Track.album_id) Artist.tracks = ReferenceSet(Artist.id, Track.artist_id) class User(object): - __storm_table__ = 'user' + __storm_table__ = 'user' - id = UUID(primary = True, default_factory = uuid.uuid4) - name = Unicode() # unique - mail = Unicode() - password = Unicode() - salt = Unicode() - admin = Bool(default = False) - lastfm_session = Unicode() # nullable - lastfm_status = Bool(default = True) # True: ok/unlinked, False: invalid session + id = UUID(primary = True, default_factory = uuid.uuid4) + name = Unicode() # unique + mail = Unicode() + password = Unicode() + salt = Unicode() + admin = Bool(default = False) + lastfm_session = Unicode() # nullable + lastfm_status = Bool(default = True) # True: ok/unlinked, False: invalid session - last_play_id = UUID() # nullable - last_play = Reference(last_play_id, Track.id) - last_play_date = DateTime() # nullable + last_play_id = UUID() # nullable + last_play = Reference(last_play_id, Track.id) + last_play_date = DateTime() # nullable - def as_subsonic_user(self): - return { - 'username': self.name, - 'email': self.mail, - 'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status, - 'adminRole': self.admin, - 'settingsRole': True, - 'downloadRole': True, - 'uploadRole': False, - 'playlistRole': True, - 'coverArtRole': False, - 'commentRole': False, - 'podcastRole': False, - 'streamRole': True, - 'jukeboxRole': False, - 'shareRole': False - } + def as_subsonic_user(self): + return { + 'username': self.name, + 'email': self.mail, + 'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status, + 'adminRole': self.admin, + 'settingsRole': True, + 'downloadRole': True, + 'uploadRole': False, + 'playlistRole': True, + 'coverArtRole': False, + 'commentRole': False, + 'podcastRole': False, + 'streamRole': True, + 'jukeboxRole': False, + 'shareRole': False + } class ClientPrefs(object): - __storm_table__ = 'client_prefs' - __storm_primary__ = 'user_id', 'client_name' + __storm_table__ = 'client_prefs' + __storm_primary__ = 'user_id', 'client_name' - user_id = UUID() - client_name = Unicode() - format = Unicode() # nullable - bitrate = Int() # nullable + user_id = UUID() + client_name = Unicode() + format = Unicode() # nullable + bitrate = Int() # nullable class BaseStarred(object): - __storm_primary__ = 'user_id', 'starred_id' + __storm_primary__ = 'user_id', 'starred_id' - user_id = UUID() - starred_id = UUID() - date = DateTime(default_factory = now) + user_id = UUID() + starred_id = UUID() + date = DateTime(default_factory = now) - user = Reference(user_id, User.id) + user = Reference(user_id, User.id) class StarredFolder(BaseStarred): - __storm_table__ = 'starred_folder' + __storm_table__ = 'starred_folder' - starred = Reference(BaseStarred.starred_id, Folder.id) + starred = Reference(BaseStarred.starred_id, Folder.id) class StarredArtist(BaseStarred): - __storm_table__ = 'starred_artist' + __storm_table__ = 'starred_artist' - starred = Reference(BaseStarred.starred_id, Artist.id) + starred = Reference(BaseStarred.starred_id, Artist.id) class StarredAlbum(BaseStarred): - __storm_table__ = 'starred_album' + __storm_table__ = 'starred_album' - starred = Reference(BaseStarred.starred_id, Album.id) + starred = Reference(BaseStarred.starred_id, Album.id) class StarredTrack(BaseStarred): - __storm_table__ = 'starred_track' + __storm_table__ = 'starred_track' - starred = Reference(BaseStarred.starred_id, Track.id) + starred = Reference(BaseStarred.starred_id, Track.id) class BaseRating(object): - __storm_primary__ = 'user_id', 'rated_id' + __storm_primary__ = 'user_id', 'rated_id' - user_id = UUID() - rated_id = UUID() - rating = Int() + user_id = UUID() + rated_id = UUID() + rating = Int() - user = Reference(user_id, User.id) + user = Reference(user_id, User.id) class RatingFolder(BaseRating): - __storm_table__ = 'rating_folder' + __storm_table__ = 'rating_folder' - rated = Reference(BaseRating.rated_id, Folder.id) + rated = Reference(BaseRating.rated_id, Folder.id) class RatingTrack(BaseRating): - __storm_table__ = 'rating_track' + __storm_table__ = 'rating_track' - rated = Reference(BaseRating.rated_id, Track.id) + rated = Reference(BaseRating.rated_id, Track.id) class ChatMessage(object): - __storm_table__ = 'chat_message' + __storm_table__ = 'chat_message' - id = UUID(primary = True, default_factory = uuid.uuid4) - user_id = UUID() - time = Int(default_factory = lambda: int(time.time())) - message = Unicode() + id = UUID(primary = True, default_factory = uuid.uuid4) + user_id = UUID() + time = Int(default_factory = lambda: int(time.time())) + message = Unicode() - user = Reference(user_id, User.id) + user = Reference(user_id, User.id) - def responsize(self): - return { - 'username': self.user.name, - 'time': self.time * 1000, - 'message': self.message - } + def responsize(self): + return { + 'username': self.user.name, + 'time': self.time * 1000, + 'message': self.message + } class Playlist(object): - __storm_table__ = 'playlist' + __storm_table__ = 'playlist' - id = UUID(primary = True, default_factory = uuid.uuid4) - user_id = UUID() - name = Unicode() - comment = Unicode() # nullable - public = Bool(default = False) - created = DateTime(default_factory = now) - tracks = Unicode() + id = UUID(primary = True, default_factory = uuid.uuid4) + user_id = UUID() + name = Unicode() + comment = Unicode() # nullable + public = Bool(default = False) + created = DateTime(default_factory = now) + tracks = Unicode() - user = Reference(user_id, User.id) + user = Reference(user_id, User.id) - def as_subsonic_playlist(self, user): - tracks = self.get_tracks() - info = { - 'id': str(self.id), - 'name': self.name if self.user_id == user.id else '[%s] %s' % (self.user.name, self.name), - 'owner': self.user.name, - 'public': self.public, - 'songCount': len(tracks), - 'duration': sum(map(lambda t: t.duration, tracks)), - 'created': self.created.isoformat() - } - if self.comment: - info['comment'] = self.comment - return info + def as_subsonic_playlist(self, user): + tracks = self.get_tracks() + info = { + 'id': str(self.id), + 'name': self.name if self.user_id == user.id else '[%s] %s' % (self.user.name, self.name), + 'owner': self.user.name, + 'public': self.public, + 'songCount': len(tracks), + 'duration': sum(map(lambda t: t.duration, tracks)), + 'created': self.created.isoformat() + } + if self.comment: + info['comment'] = self.comment + return info - def get_tracks(self): - if not self.tracks: - return [] + def get_tracks(self): + if not self.tracks: + return [] - tracks = [] - should_fix = False - store = Store.of(self) + tracks = [] + should_fix = False + store = Store.of(self) - for t in self.tracks.split(','): - try: - tid = uuid.UUID(t) - track = store.get(Track, tid) - if track: - tracks.append(track) - else: - should_fix = True - except: - should_fix = True + for t in self.tracks.split(','): + try: + tid = uuid.UUID(t) + track = store.get(Track, tid) + if track: + tracks.append(track) + else: + should_fix = True + except: + should_fix = True - if should_fix: - self.tracks = ','.join(map(lambda t: str(t.id), tracks)) - store.commit() + if should_fix: + self.tracks = ','.join(map(lambda t: str(t.id), tracks)) + store.commit() - return tracks + return tracks - def clear(self): - self.tracks = "" + def clear(self): + self.tracks = "" - def add(self, track): - if isinstance(track, uuid.UUID): - tid = track - elif isinstance(track, Track): - tid = track.id - elif isinstance(track, basestring): - tid = uuid.UUID(track) + def add(self, track): + if isinstance(track, uuid.UUID): + tid = track + elif isinstance(track, Track): + tid = track.id + elif isinstance(track, basestring): + tid = uuid.UUID(track) - if self.tracks and len(self.tracks) > 0: - self.tracks = "{},{}".format(self.tracks, tid) - else: - self.tracks = str(tid) + if self.tracks and len(self.tracks) > 0: + self.tracks = "{},{}".format(self.tracks, tid) + else: + self.tracks = str(tid) - def remove_at_indexes(self, indexes): - tracks = self.tracks.split(',') - for i in indexes: - if i < 0 or i >= len(tracks): - continue - tracks[i] = None + def remove_at_indexes(self, indexes): + tracks = self.tracks.split(',') + for i in indexes: + if i < 0 or i >= len(tracks): + continue + tracks[i] = None - self.tracks = ','.join(t for t in tracks if t) + self.tracks = ','.join(t for t in tracks if t) def get_store(database_uri): - database = create_database(database_uri) - store = Store(database) - return store + database = create_database(database_uri) + store = Store(database) + return store diff --git a/supysonic/frontend/folder.py b/supysonic/frontend/folder.py index 83983b5..02bf557 100644 --- a/supysonic/frontend/folder.py +++ b/supysonic/frontend/folder.py @@ -30,76 +30,76 @@ from supysonic.managers.folder import FolderManager @app.before_request def check_admin(): - if not request.path.startswith('/folder'): - return + if not request.path.startswith('/folder'): + return - if not UserManager.get(store, session.get('userid'))[1].admin: - return redirect(url_for('index')) + if not UserManager.get(store, session.get('userid'))[1].admin: + return redirect(url_for('index')) @app.route('/folder') def folder_index(): - return render_template('folders.html', folders = store.find(Folder, Folder.root == True), admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('folders.html', folders = store.find(Folder, Folder.root == True), admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/folder/add', methods = [ 'GET', 'POST' ]) def add_folder(): - if request.method == 'GET': - return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + if request.method == 'GET': + return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) - error = False - (name, path) = map(request.form.get, [ 'name', 'path' ]) - if name in (None, ''): - flash('The name is required.') - error = True - if path in (None, ''): - flash('The path is required.') - error = True - if error: - return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + error = False + (name, path) = map(request.form.get, [ 'name', 'path' ]) + if name in (None, ''): + flash('The name is required.') + error = True + if path in (None, ''): + flash('The path is required.') + error = True + if error: + return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) - ret = FolderManager.add(store, name, path) - if ret != FolderManager.SUCCESS: - flash(FolderManager.error_str(ret)) - return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + ret = FolderManager.add(store, name, path) + if ret != FolderManager.SUCCESS: + flash(FolderManager.error_str(ret)) + return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin) - flash("Folder '%s' created. You should now run a scan" % name) + flash("Folder '%s' created. You should now run a scan" % name) - return redirect(url_for('folder_index')) + return redirect(url_for('folder_index')) @app.route('/folder/del/') def del_folder(id): - try: - idid = uuid.UUID(id) - except ValueError: - flash('Invalid folder id') - return redirect(url_for('folder_index')) + try: + idid = uuid.UUID(id) + except ValueError: + flash('Invalid folder id') + return redirect(url_for('folder_index')) - ret = FolderManager.delete(store, idid) - if ret != FolderManager.SUCCESS: - flash(FolderManager.error_str(ret)) - else: - flash('Deleted folder') + ret = FolderManager.delete(store, idid) + if ret != FolderManager.SUCCESS: + flash(FolderManager.error_str(ret)) + else: + flash('Deleted folder') - return redirect(url_for('folder_index')) + return redirect(url_for('folder_index')) @app.route('/folder/scan') @app.route('/folder/scan/') def scan_folder(id = None): - scanner = Scanner(store) - if id is None: - for folder in store.find(Folder, Folder.root == True): - scanner.scan(folder) - else: - status, folder = FolderManager.get(store, id) - if status != FolderManager.SUCCESS: - flash(FolderManager.error_str(status)) - return redirect(url_for('folder_index')) - scanner.scan(folder) + scanner = Scanner(store) + if id is None: + for folder in store.find(Folder, Folder.root == True): + scanner.scan(folder) + else: + status, folder = FolderManager.get(store, id) + if status != FolderManager.SUCCESS: + flash(FolderManager.error_str(status)) + return redirect(url_for('folder_index')) + scanner.scan(folder) - scanner.finish() - added, deleted = scanner.stats() - store.commit() + scanner.finish() + added, deleted = scanner.stats() + store.commit() - flash('Added: %i artists, %i albums, %i tracks' % (added[0], added[1], added[2])) - flash('Deleted: %i artists, %i albums, %i tracks' % (deleted[0], deleted[1], deleted[2])) - return redirect(url_for('folder_index')) + flash('Added: %i artists, %i albums, %i tracks' % (added[0], added[1], added[2])) + flash('Deleted: %i artists, %i albums, %i tracks' % (deleted[0], deleted[1], deleted[2])) + return redirect(url_for('folder_index')) diff --git a/supysonic/frontend/playlist.py b/supysonic/frontend/playlist.py index 8df057d..29f7ba7 100644 --- a/supysonic/frontend/playlist.py +++ b/supysonic/frontend/playlist.py @@ -26,67 +26,67 @@ from supysonic.managers.user import UserManager @app.route('/playlist') def playlist_index(): - return render_template('playlists.html', mine = store.find(Playlist, Playlist.user_id == uuid.UUID(session.get('userid'))), - others = store.find(Playlist, Playlist.user_id != uuid.UUID(session.get('userid')), Playlist.public == True), - admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('playlists.html', mine = store.find(Playlist, Playlist.user_id == uuid.UUID(session.get('userid'))), + others = store.find(Playlist, Playlist.user_id != uuid.UUID(session.get('userid')), Playlist.public == True), + admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/playlist/') def playlist_details(uid): - try: - uid = uuid.UUID(uid) if type(uid) in (str, unicode) else uid - except: - flash('Invalid playlist id') - return redirect(url_for('playlist_index')) + try: + uid = uuid.UUID(uid) if type(uid) in (str, unicode) else uid + except: + flash('Invalid playlist id') + return redirect(url_for('playlist_index')) - playlist = store.get(Playlist, uid) - if not playlist: - flash('Unknown playlist') - return redirect(url_for('playlist_index')) + playlist = store.get(Playlist, uid) + if not playlist: + flash('Unknown playlist') + return redirect(url_for('playlist_index')) - return render_template('playlist.html', playlist = playlist, admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('playlist.html', playlist = playlist, admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/playlist/', methods = [ 'POST' ]) def playlist_update(uid): - try: - uid = uuid.UUID(uid) - except: - flash('Invalid playlist id') - return redirect(url_for('playlist_index')) + try: + uid = uuid.UUID(uid) + except: + flash('Invalid playlist id') + return redirect(url_for('playlist_index')) - playlist = store.get(Playlist, uid) - if not playlist: - flash('Unknown playlist') - return redirect(url_for('playlist_index')) + playlist = store.get(Playlist, uid) + if not playlist: + flash('Unknown playlist') + return redirect(url_for('playlist_index')) - if str(playlist.user_id) != session.get('userid'): - flash("You're not allowed to edit this playlist") - elif not request.form.get('name'): - flash('Missing playlist name') - else: - playlist.name = request.form.get('name') - playlist.public = request.form.get('public') in (True, 'True', 1, '1', 'on', 'checked') - store.commit() - flash('Playlist updated.') + if str(playlist.user_id) != session.get('userid'): + flash("You're not allowed to edit this playlist") + elif not request.form.get('name'): + flash('Missing playlist name') + else: + playlist.name = request.form.get('name') + playlist.public = request.form.get('public') in (True, 'True', 1, '1', 'on', 'checked') + store.commit() + flash('Playlist updated.') - return playlist_details(uid) + return playlist_details(uid) @app.route('/playlist/del/') def playlist_delete(uid): - try: - uid = uuid.UUID(uid) - except: - flash('Invalid playlist id') - return redirect(url_for('playlist_index')) + try: + uid = uuid.UUID(uid) + except: + flash('Invalid playlist id') + return redirect(url_for('playlist_index')) - playlist = store.get(Playlist, uid) - if not playlist: - flash('Unknown playlist') - elif str(playlist.user_id) != session.get('userid'): - flash("You're not allowed to delete this playlist") - else: - store.remove(playlist) - store.commit() - flash('Playlist deleted') + playlist = store.get(Playlist, uid) + if not playlist: + flash('Unknown playlist') + elif str(playlist.user_id) != session.get('userid'): + flash("You're not allowed to delete this playlist") + else: + store.remove(playlist) + store.commit() + flash('Playlist deleted') - return redirect(url_for('playlist_index')) + return redirect(url_for('playlist_index')) diff --git a/supysonic/frontend/user.py b/supysonic/frontend/user.py index bd349c8..0b91329 100644 --- a/supysonic/frontend/user.py +++ b/supysonic/frontend/user.py @@ -29,53 +29,53 @@ from supysonic.lastfm import LastFm @app.before_request def check_admin(): - if not request.path.startswith('/user'): - return + if not request.path.startswith('/user'): + return - if request.endpoint in ('user_index', 'add_user', 'del_user', 'export_users', 'import_users', 'do_user_import') and not UserManager.get(store, session.get('userid'))[1].admin: - return redirect(url_for('index')) + if request.endpoint in ('user_index', 'add_user', 'del_user', 'export_users', 'import_users', 'do_user_import') and not UserManager.get(store, session.get('userid'))[1].admin: + return redirect(url_for('index')) @app.route('/user') def user_index(): - return render_template('users.html', users = store.find(User), admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('users.html', users = store.find(User), admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/') def user_profile(uid): - if uid == 'me': - prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid'))) - return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(uid)) - return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) + if uid == 'me': + prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid'))) + return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(uid)) + return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/', methods = [ 'POST' ]) def update_clients(uid): - clients_opts = {} - for client in set(map(lambda k: k.rsplit('_', 1)[0], request.form.keys())): - clients_opts[client] = { k.rsplit('_', 1)[1]: v for k, v in filter(lambda (k, v): k.startswith(client), request.form.iteritems()) } - app.logger.debug(clients_opts) + clients_opts = {} + for client in set(map(lambda k: k.rsplit('_', 1)[0], request.form.keys())): + clients_opts[client] = { k.rsplit('_', 1)[1]: v for k, v in filter(lambda (k, v): k.startswith(client), request.form.iteritems()) } + app.logger.debug(clients_opts) - if uid == 'me': - userid = uuid.UUID(session.get('userid')) - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - userid = uuid.UUID(uid) + if uid == 'me': + userid = uuid.UUID(session.get('userid')) + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + userid = uuid.UUID(uid) - for client, opts in clients_opts.iteritems(): - prefs = store.get(ClientPrefs, (userid, client)) - if 'delete' in opts and opts['delete'] in [ 'on', 'true', 'checked', 'selected', '1' ]: - store.remove(prefs) - continue + for client, opts in clients_opts.iteritems(): + prefs = store.get(ClientPrefs, (userid, client)) + if 'delete' in opts and opts['delete'] in [ 'on', 'true', 'checked', 'selected', '1' ]: + store.remove(prefs) + continue - prefs.format = opts['format'] if 'format' in opts and opts['format'] else None - prefs.bitrate = int(opts['bitrate']) if 'bitrate' in opts and opts['bitrate'] else None + prefs.format = opts['format'] if 'format' in opts and opts['format'] else None + prefs.bitrate = int(opts['bitrate']) if 'bitrate' in opts and opts['bitrate'] else None - store.commit() - flash('Clients preferences updated.') - return user_profile(uid) + store.commit() + flash('Clients preferences updated.') + return user_profile(uid) @app.route('/user//changeusername', methods = [ 'GET', 'POST' ]) def change_username(uid): @@ -106,209 +106,209 @@ def change_username(uid): @app.route('/user//changemail', methods = [ 'GET', 'POST' ]) def change_mail(uid): - if uid == 'me': - user = UserManager.get(store, session.get('userid'))[1] - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - user = UserManager.get(store, uid)[1] - if request.method == 'POST': - mail = request.form.get('mail') - # No validation, lol. - user.mail = mail - store.commit() - return redirect(url_for('user_profile', uid = uid)) + if uid == 'me': + user = UserManager.get(store, session.get('userid'))[1] + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + user = UserManager.get(store, uid)[1] + if request.method == 'POST': + mail = request.form.get('mail') + # No validation, lol. + user.mail = mail + store.commit() + return redirect(url_for('user_profile', uid = uid)) - return render_template('change_mail.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('change_mail.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user//changepass', methods = [ 'GET', 'POST' ]) def change_password(uid): - if uid == 'me': - user = UserManager.get(store, session.get('userid'))[1].name - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - user = UserManager.get(store, uid)[1].name - if request.method == 'POST': - error = False - if uid == 'me' or uid == session.get('userid'): - current, new, confirm = map(request.form.get, [ 'current', 'new', 'confirm' ]) - if current in ('', None): - flash('The current password is required') - error = True - else: - new, confirm = map(request.form.get, [ 'new', 'confirm' ]) - if new in ('', None): - flash('The new password is required') - error = True - if new != confirm: - flash("The new password and its confirmation don't match") - error = True + if uid == 'me': + user = UserManager.get(store, session.get('userid'))[1].name + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + user = UserManager.get(store, uid)[1].name + if request.method == 'POST': + error = False + if uid == 'me' or uid == session.get('userid'): + current, new, confirm = map(request.form.get, [ 'current', 'new', 'confirm' ]) + if current in ('', None): + flash('The current password is required') + error = True + else: + new, confirm = map(request.form.get, [ 'new', 'confirm' ]) + if new in ('', None): + flash('The new password is required') + error = True + if new != confirm: + flash("The new password and its confirmation don't match") + error = True - if not error: - if uid == 'me' or uid == session.get('userid'): - status = UserManager.change_password(store, session.get('userid'), current, new) - else: - status = UserManager.change_password2(store, UserManager.get(store, uid)[1].name, new) - if status != UserManager.SUCCESS: - flash(UserManager.error_str(status)) - else: - flash('Password changed') - return redirect(url_for('user_profile', uid = uid)) + if not error: + if uid == 'me' or uid == session.get('userid'): + status = UserManager.change_password(store, session.get('userid'), current, new) + else: + status = UserManager.change_password2(store, UserManager.get(store, uid)[1].name, new) + if status != UserManager.SUCCESS: + flash(UserManager.error_str(status)) + else: + flash('Password changed') + return redirect(url_for('user_profile', uid = uid)) - return render_template('change_pass.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('change_pass.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/add', methods = [ 'GET', 'POST' ]) def add_user(): - if request.method == 'GET': - return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + if request.method == 'GET': + return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin) - error = False - (name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'user', 'passwd', 'passwd_confirm', 'mail', 'admin' ]) - if name in (None, ''): - flash('The name is required.') - error = True - if passwd in (None, ''): - flash('Please provide a password.') - error = True - elif passwd != passwd_confirm: - flash("The passwords don't match.") - error = True + error = False + (name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'user', 'passwd', 'passwd_confirm', 'mail', 'admin' ]) + if name in (None, ''): + flash('The name is required.') + error = True + if passwd in (None, ''): + flash('Please provide a password.') + error = True + elif passwd != passwd_confirm: + flash("The passwords don't match.") + error = True - if admin is None: - admin = True if store.find(User, User.admin == True).count() == 0 else False - else: - admin = True + if admin is None: + admin = True if store.find(User, User.admin == True).count() == 0 else False + else: + admin = True - if not error: - status = UserManager.add(store, name, passwd, mail, admin) - if status == UserManager.SUCCESS: - flash("User '%s' successfully added" % name) - return redirect(url_for('user_index')) - else: - flash(UserManager.error_str(status)) + if not error: + status = UserManager.add(store, name, passwd, mail, admin) + if status == UserManager.SUCCESS: + flash("User '%s' successfully added" % name) + return redirect(url_for('user_index')) + else: + flash(UserManager.error_str(status)) - return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/del/') def del_user(uid): - status = UserManager.delete(store, uid) - if status == UserManager.SUCCESS: - flash('Deleted user') - else: - flash(UserManager.error_str(status)) + status = UserManager.delete(store, uid) + if status == UserManager.SUCCESS: + flash('Deleted user') + else: + flash(UserManager.error_str(status)) - return redirect(url_for('user_index')) + return redirect(url_for('user_index')) @app.route('/user/export') def export_users(): - resp = make_response('\n'.join([ '%s,%s,%s,%s,"%s",%s,%s,%s' % (u.id, u.name, u.mail, u.password, u.salt, u.admin, u.lastfm_session, u.lastfm_status) - for u in store.find(User) ])) - resp.headers['Content-disposition'] = 'attachment;filename=users.csv' - resp.headers['Content-type'] = 'text/csv' - return resp + resp = make_response('\n'.join([ '%s,%s,%s,%s,"%s",%s,%s,%s' % (u.id, u.name, u.mail, u.password, u.salt, u.admin, u.lastfm_session, u.lastfm_status) + for u in store.find(User) ])) + resp.headers['Content-disposition'] = 'attachment;filename=users.csv' + resp.headers['Content-type'] = 'text/csv' + return resp @app.route('/user/import') def import_users(): - return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin) @app.route('/user/import', methods = [ 'POST' ]) def do_user_import(): - if not request.files['file']: - return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin) + if not request.files['file']: + return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin) - users = [] - reader = csv.reader(request.files['file']) - for id, name, mail, password, salt, admin, lfmsess, lfmstatus in reader: - mail = None if mail == 'None' else mail - admin = admin == 'True' - lfmsess = None if lfmsess == 'None' else lfmsess - lfmstatus = lfmstatus == 'True' + users = [] + reader = csv.reader(request.files['file']) + for id, name, mail, password, salt, admin, lfmsess, lfmstatus in reader: + mail = None if mail == 'None' else mail + admin = admin == 'True' + lfmsess = None if lfmsess == 'None' else lfmsess + lfmstatus = lfmstatus == 'True' - user = User() - user.id = uuid.UUID(id) - user.name = name - user.password = password - user.salt = salt - user.admin = admin - user.lastfm_session = lfmsess - user.lastfm_status = lfmstatus + user = User() + user.id = uuid.UUID(id) + user.name = name + user.password = password + user.salt = salt + user.admin = admin + user.lastfm_session = lfmsess + user.lastfm_status = lfmstatus - users.append(user) + users.append(user) - store.find(User).remove() - for u in users: - store.add(u) - store.commit() + store.find(User).remove() + for u in users: + store.add(u) + store.commit() - return redirect(url_for('user_index')) + return redirect(url_for('user_index')) @app.route('/user//lastfm/link') def lastfm_reg(uid): - token = request.args.get('token') - if token in ('', None): - flash('Missing LastFM auth token') - return redirect(url_for('user_profile', uid = uid)) + token = request.args.get('token') + if token in ('', None): + flash('Missing LastFM auth token') + return redirect(url_for('user_profile', uid = uid)) - if uid == 'me': - lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger) - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - lfm = LastFm(UserManager.get(store, uid)[1], app.logger) - status, error = lfm.link_account(token) - store.commit() - flash(error if not status else 'Successfully linked LastFM account') + if uid == 'me': + lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger) + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + lfm = LastFm(UserManager.get(store, uid)[1], app.logger) + status, error = lfm.link_account(token) + store.commit() + flash(error if not status else 'Successfully linked LastFM account') - return redirect(url_for('user_profile', uid = uid)) + return redirect(url_for('user_profile', uid = uid)) @app.route('/user//lastfm/unlink') def lastfm_unreg(uid): - if uid == 'me': - lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger) - else: - if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: - return redirect(url_for('index')) - lfm = LastFm(UserManager.get(store, uid)[1], app.logger) - lfm.unlink_account() - store.commit() - flash('Unliked LastFM account') - return redirect(url_for('user_profile', uid = uid)) + if uid == 'me': + lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger) + else: + if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS: + return redirect(url_for('index')) + lfm = LastFm(UserManager.get(store, uid)[1], app.logger) + lfm.unlink_account() + store.commit() + flash('Unliked LastFM account') + return redirect(url_for('user_profile', uid = uid)) @app.route('/user/login', methods = [ 'GET', 'POST']) def login(): - return_url = request.args.get('returnUrl') or url_for('index') - if session.get('userid'): - flash('Already logged in') - return redirect(return_url) + return_url = request.args.get('returnUrl') or url_for('index') + if session.get('userid'): + flash('Already logged in') + return redirect(return_url) - if request.method == 'GET': - return render_template('login.html') + if request.method == 'GET': + return render_template('login.html') - name, password = map(request.form.get, [ 'user', 'password' ]) - error = False - if name in ('', None): - flash('Missing user name') - error = True - if password in ('', None): - flash('Missing password') - error = True + name, password = map(request.form.get, [ 'user', 'password' ]) + error = False + if name in ('', None): + flash('Missing user name') + error = True + if password in ('', None): + flash('Missing password') + error = True - if not error: - status, user = UserManager.try_auth(store, name, password) - if status == UserManager.SUCCESS: - session['userid'] = str(user.id) - session['username'] = user.name - flash('Logged in!') - return redirect(return_url) - else: - flash(UserManager.error_str(status)) + if not error: + status, user = UserManager.try_auth(store, name, password) + if status == UserManager.SUCCESS: + session['userid'] = str(user.id) + session['username'] = user.name + flash('Logged in!') + return redirect(return_url) + else: + flash(UserManager.error_str(status)) - return render_template('login.html') + return render_template('login.html') @app.route('/user/logout') def logout(): - session.clear() - flash('Logged out!') - return redirect(url_for('login')) + session.clear() + flash('Logged out!') + return redirect(url_for('login')) diff --git a/supysonic/lastfm.py b/supysonic/lastfm.py index 5a799f3..801b07c 100644 --- a/supysonic/lastfm.py +++ b/supysonic/lastfm.py @@ -22,81 +22,81 @@ import requests, hashlib from supysonic import config class LastFm: - def __init__(self, user, logger): - self.__user = user - self.__api_key = config.get('lastfm', 'api_key') - self.__api_secret = config.get('lastfm', 'secret') - self.__enabled = self.__api_key is not None and self.__api_secret is not None - self.__logger = logger + def __init__(self, user, logger): + self.__user = user + self.__api_key = config.get('lastfm', 'api_key') + self.__api_secret = config.get('lastfm', 'secret') + self.__enabled = self.__api_key is not None and self.__api_secret is not None + self.__logger = logger - def link_account(self, token): - if not self.__enabled: - return False, 'No API key set' + def link_account(self, token): + if not self.__enabled: + return False, 'No API key set' - res = self.__api_request(False, method = 'auth.getSession', token = token) - if not res: - return False, 'Error connecting to LastFM' - elif 'error' in res: - return False, 'Error %i: %s' % (res['error'], res['message']) - else: - self.__user.lastfm_session = res['session']['key'] - self.__user.lastfm_status = True - return True, 'OK' + res = self.__api_request(False, method = 'auth.getSession', token = token) + if not res: + return False, 'Error connecting to LastFM' + elif 'error' in res: + return False, 'Error %i: %s' % (res['error'], res['message']) + else: + self.__user.lastfm_session = res['session']['key'] + self.__user.lastfm_status = True + return True, 'OK' - def unlink_account(self): - self.__user.lastfm_session = None - self.__user.lastfm_status = True + def unlink_account(self): + self.__user.lastfm_session = None + self.__user.lastfm_status = True - def now_playing(self, track): - if not self.__enabled: - return + def now_playing(self, track): + if not self.__enabled: + return - self.__api_request(True, method = 'track.updateNowPlaying', artist = track.album.artist.name, track = track.title, album = track.album.name, - trackNumber = track.number, duration = track.duration) + self.__api_request(True, method = 'track.updateNowPlaying', artist = track.album.artist.name, track = track.title, album = track.album.name, + trackNumber = track.number, duration = track.duration) - def scrobble(self, track, ts): - if not self.__enabled: - return + def scrobble(self, track, ts): + if not self.__enabled: + return - self.__api_request(True, method = 'track.scrobble', artist = track.album.artist.name, track = track.title, album = track.album.name, - timestamp = ts, trackNumber = track.number, duration = track.duration) + self.__api_request(True, method = 'track.scrobble', artist = track.album.artist.name, track = track.title, album = track.album.name, + timestamp = ts, trackNumber = track.number, duration = track.duration) - def __api_request(self, write, **kwargs): - if not self.__enabled: - return + def __api_request(self, write, **kwargs): + if not self.__enabled: + return - if write: - if not self.__user.lastfm_session or not self.__user.lastfm_status: - return - kwargs['sk'] = self.__user.lastfm_session + if write: + if not self.__user.lastfm_session or not self.__user.lastfm_status: + return + kwargs['sk'] = self.__user.lastfm_session - kwargs['api_key'] = self.__api_key + kwargs['api_key'] = self.__api_key - sig_str = '' - for k, v in sorted(kwargs.iteritems()): - if type(v) is unicode: - sig_str += k + v.encode('utf-8') - else: - sig_str += k + str(v) - sig = hashlib.md5(sig_str + self.__api_secret).hexdigest() + sig_str = '' + for k, v in sorted(kwargs.iteritems()): + if type(v) is unicode: + sig_str += k + v.encode('utf-8') + else: + sig_str += k + str(v) + sig = hashlib.md5(sig_str + self.__api_secret).hexdigest() - kwargs['api_sig'] = sig - kwargs['format'] = 'json' + kwargs['api_sig'] = sig + kwargs['format'] = 'json' - try: - if write: - r = requests.post('http://ws.audioscrobbler.com/2.0/', data = kwargs) - else: - r = requests.get('http://ws.audioscrobbler.com/2.0/', params = kwargs) - except requests.exceptions.RequestException, e: - self.__logger.warn('Error while connecting to LastFM: ' + str(e)) - return None + try: + if write: + r = requests.post('http://ws.audioscrobbler.com/2.0/', data = kwargs) + else: + r = requests.get('http://ws.audioscrobbler.com/2.0/', params = kwargs) + except requests.exceptions.RequestException, e: + self.__logger.warn('Error while connecting to LastFM: ' + str(e)) + return None - json = r.json() - if 'error' in json: - if json['error'] in (9, '9'): - self.__user.lastfm_status = False - self.__logger.warn('LastFM error %i: %s' % (json['error'], json['message'])) + json = r.json() + if 'error' in json: + if json['error'] in (9, '9'): + self.__user.lastfm_status = False + self.__logger.warn('LastFM error %i: %s' % (json['error'], json['message'])) - return json + return json diff --git a/supysonic/managers/folder.py b/supysonic/managers/folder.py index c138403..d5ca32f 100644 --- a/supysonic/managers/folder.py +++ b/supysonic/managers/folder.py @@ -23,101 +23,101 @@ from supysonic.db import Folder, Artist, Album, Track, StarredFolder, RatingFold from supysonic.scanner import Scanner class FolderManager: - SUCCESS = 0 - INVALID_ID = 1 - NAME_EXISTS = 2 - INVALID_PATH = 3 - PATH_EXISTS = 4 - NO_SUCH_FOLDER = 5 - SUBPATH_EXISTS = 6 + SUCCESS = 0 + INVALID_ID = 1 + NAME_EXISTS = 2 + INVALID_PATH = 3 + PATH_EXISTS = 4 + NO_SUCH_FOLDER = 5 + SUBPATH_EXISTS = 6 - @staticmethod - def get(store, uid): - if isinstance(uid, basestring): - try: - uid = uuid.UUID(uid) - except: - return FolderManager.INVALID_ID, None - elif type(uid) is uuid.UUID: - pass - else: - return FolderManager.INVALID_ID, None + @staticmethod + def get(store, uid): + if isinstance(uid, basestring): + try: + uid = uuid.UUID(uid) + except: + return FolderManager.INVALID_ID, None + elif type(uid) is uuid.UUID: + pass + else: + return FolderManager.INVALID_ID, None - folder = store.get(Folder, uid) - if not folder: - return FolderManager.NO_SUCH_FOLDER, None + folder = store.get(Folder, uid) + if not folder: + return FolderManager.NO_SUCH_FOLDER, None - return FolderManager.SUCCESS, folder + return FolderManager.SUCCESS, folder - @staticmethod - def add(store, name, path): - if not store.find(Folder, Folder.name == name, Folder.root == True).is_empty(): - return FolderManager.NAME_EXISTS + @staticmethod + def add(store, name, path): + if not store.find(Folder, Folder.name == name, Folder.root == True).is_empty(): + return FolderManager.NAME_EXISTS - path = unicode(os.path.abspath(path)) - if not os.path.isdir(path): - return FolderManager.INVALID_PATH - if not store.find(Folder, Folder.path == path).is_empty(): - return FolderManager.PATH_EXISTS - if any(path.startswith(p) for p in store.find(Folder).values(Folder.path)): - return FolderManager.PATH_EXISTS - if not store.find(Folder, Folder.path.startswith(path)).is_empty(): - return FolderManager.SUBPATH_EXISTS + path = unicode(os.path.abspath(path)) + if not os.path.isdir(path): + return FolderManager.INVALID_PATH + if not store.find(Folder, Folder.path == path).is_empty(): + return FolderManager.PATH_EXISTS + if any(path.startswith(p) for p in store.find(Folder).values(Folder.path)): + return FolderManager.PATH_EXISTS + if not store.find(Folder, Folder.path.startswith(path)).is_empty(): + return FolderManager.SUBPATH_EXISTS - folder = Folder() - folder.root = True - folder.name = name - folder.path = path + folder = Folder() + folder.root = True + folder.name = name + folder.path = path - store.add(folder) - store.commit() + store.add(folder) + store.commit() - return FolderManager.SUCCESS + return FolderManager.SUCCESS - @staticmethod - def delete(store, uid): - status, folder = FolderManager.get(store, uid) - if status != FolderManager.SUCCESS: - return status + @staticmethod + def delete(store, uid): + status, folder = FolderManager.get(store, uid) + if status != FolderManager.SUCCESS: + return status - if not folder.root: - return FolderManager.NO_SUCH_FOLDER + if not folder.root: + return FolderManager.NO_SUCH_FOLDER - scanner = Scanner(store) - for track in store.find(Track, Track.root_folder_id == folder.id): - scanner.remove_file(track.path) - scanner.finish() + scanner = Scanner(store) + for track in store.find(Track, Track.root_folder_id == folder.id): + scanner.remove_file(track.path) + scanner.finish() - store.find(StarredFolder, StarredFolder.starred_id == uid).remove() - store.find(RatingFolder, RatingFolder.rated_id == uid).remove() + store.find(StarredFolder, StarredFolder.starred_id == uid).remove() + store.find(RatingFolder, RatingFolder.rated_id == uid).remove() - store.remove(folder) - store.commit() + store.remove(folder) + store.commit() - return FolderManager.SUCCESS + return FolderManager.SUCCESS - @staticmethod - def delete_by_name(store, name): - folder = store.find(Folder, Folder.name == name, Folder.root == True).one() - if not folder: - return FolderManager.NO_SUCH_FOLDER - return FolderManager.delete(store, folder.id) + @staticmethod + def delete_by_name(store, name): + folder = store.find(Folder, Folder.name == name, Folder.root == True).one() + if not folder: + return FolderManager.NO_SUCH_FOLDER + return FolderManager.delete(store, folder.id) - @staticmethod - def error_str(err): - if err == FolderManager.SUCCESS: - return 'No error' - elif err == FolderManager.INVALID_ID: - return 'Invalid folder id' - elif err == FolderManager.NAME_EXISTS: - return 'There is already a folder with that name. Please pick another one.' - elif err == FolderManager.INVALID_PATH: - return "The path doesn't exists or isn't a directory" - elif err == FolderManager.PATH_EXISTS: - return 'This path is already registered' - elif err == FolderManager.NO_SUCH_FOLDER: - return 'No such folder' - elif err == FolderManager.SUBPATH_EXISTS: - return 'This path contains a folder that is already registered' - return 'Unknown error' + @staticmethod + def error_str(err): + if err == FolderManager.SUCCESS: + return 'No error' + elif err == FolderManager.INVALID_ID: + return 'Invalid folder id' + elif err == FolderManager.NAME_EXISTS: + return 'There is already a folder with that name. Please pick another one.' + elif err == FolderManager.INVALID_PATH: + return "The path doesn't exists or isn't a directory" + elif err == FolderManager.PATH_EXISTS: + return 'This path is already registered' + elif err == FolderManager.NO_SUCH_FOLDER: + return 'No such folder' + elif err == FolderManager.SUBPATH_EXISTS: + return 'This path contains a folder that is already registered' + return 'Unknown error' diff --git a/supysonic/scanner.py b/supysonic/scanner.py index 7af7a33..8b2e867 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -32,288 +32,288 @@ from supysonic.db import RatingFolder, RatingTrack # Hacking in support for a concatenation expression class Concat(ComparableExpr): - __slots__ = ("left", "right", "db") + __slots__ = ("left", "right", "db") - def __init__(self, left, right, db): - self.left = left - self.right = right - self.db = db + def __init__(self, left, right, db): + self.left = left + self.right = right + self.db = db @compile.when(Concat) def compile_concat(compile, concat, state): - left = compile(concat.left, state) - right = compile(concat.right, state) - if concat.db in ('sqlite', 'postgres'): - statement = "%s||%s" - elif concat.db == 'mysql': - statement = "CONCAT(%s, %s)" - else: - raise NotSupportedError("Unspported database (%s)" % concat.db) - return statement % (left, right) + left = compile(concat.left, state) + right = compile(concat.right, state) + if concat.db in ('sqlite', 'postgres'): + statement = "%s||%s" + elif concat.db == 'mysql': + statement = "CONCAT(%s, %s)" + else: + raise NotSupportedError("Unspported database (%s)" % concat.db) + return statement % (left, right) class Scanner: - def __init__(self, store, force = False): - self.__store = store - self.__force = force + def __init__(self, store, force = False): + self.__store = store + self.__force = force - self.__added_artists = 0 - self.__added_albums = 0 - self.__added_tracks = 0 - self.__deleted_artists = 0 - self.__deleted_albums = 0 - self.__deleted_tracks = 0 + self.__added_artists = 0 + self.__added_albums = 0 + self.__added_tracks = 0 + self.__deleted_artists = 0 + self.__deleted_albums = 0 + self.__deleted_tracks = 0 - extensions = config.get('base', 'scanner_extensions') - self.__extensions = map(str.lower, extensions.split()) if extensions else None + extensions = config.get('base', 'scanner_extensions') + self.__extensions = map(str.lower, extensions.split()) if extensions else None - self.__folders_to_check = set() - self.__artists_to_check = set() - self.__albums_to_check = set() + self.__folders_to_check = set() + self.__artists_to_check = set() + self.__albums_to_check = set() - def __del__(self): - if self.__folders_to_check or self.__artists_to_check or self.__albums_to_check: - raise Exception("There's still something to check. Did you run Scanner.finish()?") + def __del__(self): + if self.__folders_to_check or self.__artists_to_check or self.__albums_to_check: + raise Exception("There's still something to check. Did you run Scanner.finish()?") - def scan(self, folder, progress_callback = None): - # Scan new/updated files - files = [ os.path.join(root, f) for root, _, fs in os.walk(folder.path) for f in fs if self.__is_valid_path(os.path.join(root, f)) ] - total = len(files) - current = 0 + def scan(self, folder, progress_callback = None): + # Scan new/updated files + files = [ os.path.join(root, f) for root, _, fs in os.walk(folder.path) for f in fs if self.__is_valid_path(os.path.join(root, f)) ] + total = len(files) + current = 0 - for path in files: - self.scan_file(path) - current += 1 - if progress_callback: - progress_callback(current, total) + for path in files: + self.scan_file(path) + current += 1 + if progress_callback: + progress_callback(current, total) - # Remove files that have been deleted - for track in [ t for t in self.__store.find(Track, Track.root_folder_id == folder.id) if not self.__is_valid_path(t.path) ]: - self.remove_file(track.path) + # Remove files that have been deleted + for track in [ t for t in self.__store.find(Track, Track.root_folder_id == folder.id) if not self.__is_valid_path(t.path) ]: + self.remove_file(track.path) - # Update cover art info - folders = [ folder ] - while folders: - f = folders.pop() - f.has_cover_art = os.path.isfile(os.path.join(f.path, 'cover.jpg')) - folders += f.children + # Update cover art info + folders = [ folder ] + while folders: + f = folders.pop() + f.has_cover_art = os.path.isfile(os.path.join(f.path, 'cover.jpg')) + folders += f.children - folder.last_scan = int(time.time()) + folder.last_scan = int(time.time()) - def finish(self): - for album in [ a for a in self.__albums_to_check if not a.tracks.count() ]: - self.__store.find(StarredAlbum, StarredAlbum.starred_id == album.id).remove() + def finish(self): + for album in [ a for a in self.__albums_to_check if not a.tracks.count() ]: + self.__store.find(StarredAlbum, StarredAlbum.starred_id == album.id).remove() - self.__artists_to_check.add(album.artist) - self.__store.remove(album) - self.__deleted_albums += 1 - self.__albums_to_check.clear() + self.__artists_to_check.add(album.artist) + self.__store.remove(album) + self.__deleted_albums += 1 + self.__albums_to_check.clear() - for artist in [ a for a in self.__artists_to_check if not a.albums.count() and not a.tracks.count() ]: - self.__store.find(StarredArtist, StarredArtist.starred_id == artist.id).remove() + for artist in [ a for a in self.__artists_to_check if not a.albums.count() and not a.tracks.count() ]: + self.__store.find(StarredArtist, StarredArtist.starred_id == artist.id).remove() - self.__store.remove(artist) - self.__deleted_artists += 1 - self.__artists_to_check.clear() + self.__store.remove(artist) + self.__deleted_artists += 1 + self.__artists_to_check.clear() - while self.__folders_to_check: - folder = self.__folders_to_check.pop() - if folder.root: - continue + while self.__folders_to_check: + folder = self.__folders_to_check.pop() + if folder.root: + continue - if not folder.tracks.count() and not folder.children.count(): - self.__store.find(StarredFolder, StarredFolder.starred_id == folder.id).remove() - self.__store.find(RatingFolder, RatingFolder.rated_id == folder.id).remove() + if not folder.tracks.count() and not folder.children.count(): + self.__store.find(StarredFolder, StarredFolder.starred_id == folder.id).remove() + self.__store.find(RatingFolder, RatingFolder.rated_id == folder.id).remove() - self.__folders_to_check.add(folder.parent) - self.__store.remove(folder) + self.__folders_to_check.add(folder.parent) + self.__store.remove(folder) - def __is_valid_path(self, path): - if not os.path.exists(path): - return False - if not self.__extensions: - return True - return os.path.splitext(path)[1][1:].lower() in self.__extensions + def __is_valid_path(self, path): + if not os.path.exists(path): + return False + if not self.__extensions: + return True + return os.path.splitext(path)[1][1:].lower() in self.__extensions - def scan_file(self, path): - tr = self.__store.find(Track, Track.path == path).one() - add = False - if tr: - if not self.__force and not int(os.path.getmtime(path)) > tr.last_modification: - return + def scan_file(self, path): + tr = self.__store.find(Track, Track.path == path).one() + add = False + if tr: + if not self.__force and not int(os.path.getmtime(path)) > tr.last_modification: + return - tag = self.__try_load_tag(path) - if not tag: - self.remove_file(path) - return - else: - tag = self.__try_load_tag(path) - if not tag: - return + tag = self.__try_load_tag(path) + if not tag: + self.remove_file(path) + return + else: + tag = self.__try_load_tag(path) + if not tag: + return - tr = Track() - tr.path = path - add = True + tr = Track() + tr.path = path + add = True - artist = self.__try_read_tag(tag, 'artist', '') - album = self.__try_read_tag(tag, 'album', '') - albumartist = self.__try_read_tag(tag, 'albumartist', artist) + artist = self.__try_read_tag(tag, 'artist', '') + album = self.__try_read_tag(tag, 'album', '') + albumartist = self.__try_read_tag(tag, 'albumartist', artist) - tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0])) - tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0])) - tr.title = self.__try_read_tag(tag, 'title', '') - tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0])) - tr.genre = self.__try_read_tag(tag, 'genre') - tr.duration = int(tag.info.length) + tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0])) + tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0])) + tr.title = self.__try_read_tag(tag, 'title', '') + tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0])) + tr.genre = self.__try_read_tag(tag, 'genre') + tr.duration = int(tag.info.length) - tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000 - tr.content_type = config.get_mime(os.path.splitext(path)[1][1:]) - tr.last_modification = os.path.getmtime(path) + tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000 + tr.content_type = config.get_mime(os.path.splitext(path)[1][1:]) + tr.last_modification = os.path.getmtime(path) - tralbum = self.__find_album(albumartist, album) - trartist = self.__find_artist(artist) + tralbum = self.__find_album(albumartist, album) + trartist = self.__find_artist(artist) - if add: - trroot = self.__find_root_folder(path) - trfolder = self.__find_folder(path) + if add: + trroot = self.__find_root_folder(path) + trfolder = self.__find_folder(path) - # Set the references at the very last as searching for them will cause the added track to be flushed, even if - # it is incomplete, causing not null constraints errors. - tr.album = tralbum - tr.artist = trartist - tr.folder = trfolder - tr.root_folder = trroot + # Set the references at the very last as searching for them will cause the added track to be flushed, even if + # it is incomplete, causing not null constraints errors. + tr.album = tralbum + tr.artist = trartist + tr.folder = trfolder + tr.root_folder = trroot - self.__store.add(tr) - self.__added_tracks += 1 - else: - if tr.album.id != tralbum.id: - self.__albums_to_check.add(tr.album) - tr.album = tralbum + self.__store.add(tr) + self.__added_tracks += 1 + else: + if tr.album.id != tralbum.id: + self.__albums_to_check.add(tr.album) + tr.album = tralbum - if tr.artist.id != trartist.id: - self.__artists_to_check.add(tr.artist) - tr.artist = trartist + if tr.artist.id != trartist.id: + self.__artists_to_check.add(tr.artist) + tr.artist = trartist - def remove_file(self, path): - tr = self.__store.find(Track, Track.path == path).one() - if not tr: - return + def remove_file(self, path): + tr = self.__store.find(Track, Track.path == path).one() + if not tr: + return - self.__store.find(StarredTrack, StarredTrack.starred_id == tr.id).remove() - self.__store.find(RatingTrack, RatingTrack.rated_id == tr.id).remove() - # Playlist autofix themselves - self.__store.find(User, User.last_play_id == tr.id).set(last_play_id = None) + self.__store.find(StarredTrack, StarredTrack.starred_id == tr.id).remove() + self.__store.find(RatingTrack, RatingTrack.rated_id == tr.id).remove() + # Playlist autofix themselves + self.__store.find(User, User.last_play_id == tr.id).set(last_play_id = None) - self.__folders_to_check.add(tr.folder) - self.__albums_to_check.add(tr.album) - self.__artists_to_check.add(tr.artist) - self.__store.remove(tr) - self.__deleted_tracks += 1 + self.__folders_to_check.add(tr.folder) + self.__albums_to_check.add(tr.album) + self.__artists_to_check.add(tr.artist) + self.__store.remove(tr) + self.__deleted_tracks += 1 - def move_file(self, src_path, dst_path): - tr = self.__store.find(Track, Track.path == src_path).one() - if not tr: - return + def move_file(self, src_path, dst_path): + tr = self.__store.find(Track, Track.path == src_path).one() + if not tr: + return - self.__folders_to_check.add(tr.folder) - tr_dst = self.__store.find(Track, Track.path == dst_path).one() - if tr_dst: - tr.root_folder = tr_dst.root_folder - tr.folder = tr_dst.folder - self.remove_file(dst_path) - else: - root = self.__find_root_folder(dst_path) - folder = self.__find_folder(dst_path) - tr.root_folder = root - tr.folder = folder - tr.path = dst_path + self.__folders_to_check.add(tr.folder) + tr_dst = self.__store.find(Track, Track.path == dst_path).one() + if tr_dst: + tr.root_folder = tr_dst.root_folder + tr.folder = tr_dst.folder + self.remove_file(dst_path) + else: + root = self.__find_root_folder(dst_path) + folder = self.__find_folder(dst_path) + tr.root_folder = root + tr.folder = folder + tr.path = dst_path - def __find_album(self, artist, album): - ar = self.__find_artist(artist) - al = ar.albums.find(name = album).one() - if al: - return al + def __find_album(self, artist, album): + ar = self.__find_artist(artist) + al = ar.albums.find(name = album).one() + if al: + return al - al = Album() - al.name = album - al.artist = ar + al = Album() + al.name = album + al.artist = ar - self.__store.add(al) - self.__added_albums += 1 + self.__store.add(al) + self.__added_albums += 1 - return al + return al - def __find_artist(self, artist): - ar = self.__store.find(Artist, Artist.name == artist).one() - if ar: - return ar + def __find_artist(self, artist): + ar = self.__store.find(Artist, Artist.name == artist).one() + if ar: + return ar - ar = Artist() - ar.name = artist + ar = Artist() + ar.name = artist - self.__store.add(ar) - self.__added_artists += 1 + self.__store.add(ar) + self.__added_artists += 1 - return ar + return ar - def __find_root_folder(self, path): - path = os.path.dirname(path) - db = self.__store.get_database().__module__[len('storm.databases.'):] - folders = self.__store.find(Folder, Like(path, Concat(Folder.path, u'%', db)), Folder.root == True) - count = folders.count() - if count > 1: - raise Exception("Found multiple root folders for '{}'.".format(path)) - elif count == 0: - raise Exception("Couldn't find the root folder for '{}'.\nDon't scan files that aren't located in a defined music folder".format(path)) - return folders.one() + def __find_root_folder(self, path): + path = os.path.dirname(path) + db = self.__store.get_database().__module__[len('storm.databases.'):] + folders = self.__store.find(Folder, Like(path, Concat(Folder.path, u'%', db)), Folder.root == True) + count = folders.count() + if count > 1: + raise Exception("Found multiple root folders for '{}'.".format(path)) + elif count == 0: + raise Exception("Couldn't find the root folder for '{}'.\nDon't scan files that aren't located in a defined music folder".format(path)) + return folders.one() - def __find_folder(self, path): - path = os.path.dirname(path) - folders = self.__store.find(Folder, Folder.path == path) - count = folders.count() - if count > 1: - raise Exception("Found multiple folders for '{}'.".format(path)) - elif count == 1: - return folders.one() + def __find_folder(self, path): + path = os.path.dirname(path) + folders = self.__store.find(Folder, Folder.path == path) + count = folders.count() + if count > 1: + raise Exception("Found multiple folders for '{}'.".format(path)) + elif count == 1: + return folders.one() - db = self.__store.get_database().__module__[len('storm.databases.'):] - folder = self.__store.find(Folder, Like(path, Concat(Folder.path, os.sep + u'%', db))).order_by(Folder.path).last() + db = self.__store.get_database().__module__[len('storm.databases.'):] + folder = self.__store.find(Folder, Like(path, Concat(Folder.path, os.sep + u'%', db))).order_by(Folder.path).last() - full_path = folder.path - path = path[len(folder.path) + 1:] + full_path = folder.path + path = path[len(folder.path) + 1:] - for name in path.split(os.sep): - full_path = os.path.join(full_path, name) + for name in path.split(os.sep): + full_path = os.path.join(full_path, name) - fold = Folder() - fold.root = False - fold.name = name - fold.path = full_path - fold.parent = folder + fold = Folder() + fold.root = False + fold.name = name + fold.path = full_path + fold.parent = folder - self.__store.add(fold) + self.__store.add(fold) - folder = fold + folder = fold - return folder + return folder - def __try_load_tag(self, path): - try: - return mutagen.File(path, easy = True) - except: - return None + def __try_load_tag(self, path): + try: + return mutagen.File(path, easy = True) + except: + return None - def __try_read_tag(self, metadata, field, default = None, transform = lambda x: x[0]): - try: - value = metadata[field] - if not value: - return default - if transform: - value = transform(value) - return value if value else default - except: - return default + def __try_read_tag(self, metadata, field, default = None, transform = lambda x: x[0]): + try: + value = metadata[field] + if not value: + return default + if transform: + value = transform(value) + return value if value else default + except: + return default - def stats(self): - return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks) + def stats(self): + return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)