mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +00:00
Untabbify
This commit is contained in:
parent
fa4b1eca84
commit
7effd3aee5
@ -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 *
|
||||
|
@ -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) ]
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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({})
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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({})
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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({})
|
||||
|
||||
|
@ -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 ]
|
||||
}})
|
||||
|
||||
|
@ -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 } })
|
||||
|
||||
|
@ -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({})
|
||||
|
||||
|
578
supysonic/db.py
578
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
|
||||
|
||||
|
@ -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/<id>')
|
||||
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/<id>')
|
||||
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'))
|
||||
|
||||
|
@ -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/<uid>')
|
||||
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/<uid>', 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/<uid>')
|
||||
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'))
|
||||
|
||||
|
@ -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/<uid>')
|
||||
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/<uid>', 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/<uid>/changeusername', methods = [ 'GET', 'POST' ])
|
||||
def change_username(uid):
|
||||
@ -106,209 +106,209 @@ def change_username(uid):
|
||||
|
||||
@app.route('/user/<uid>/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/<uid>/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/<uid>')
|
||||
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/<uid>/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/<uid>/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'))
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user