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