1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-09 19:52:16 +00:00

Untabbify

This commit is contained in:
spl0k 2017-10-28 12:23:31 +02:00
parent fa4b1eca84
commit 7effd3aee5
17 changed files with 1804 additions and 1804 deletions

View File

@ -29,187 +29,187 @@ from supysonic.managers.user import UserManager
@app.before_request
def set_formatter():
if not request.path.startswith('/rest/'):
return
if not request.path.startswith('/rest/'):
return
"""Return a function to create the response."""
(f, callback) = map(request.values.get, ['f', 'callback'])
if f == 'jsonp':
# Some clients (MiniSub, Perisonic) set f to jsonp without callback for streamed data
if not callback and request.endpoint not in [ 'stream_media', 'cover_art' ]:
return ResponseHelper.responsize_json({
'error': {
'code': 0,
'message': 'Missing callback'
}
}, error = True), 400
request.formatter = lambda x, **kwargs: ResponseHelper.responsize_jsonp(x, callback, kwargs)
elif f == "json":
request.formatter = ResponseHelper.responsize_json
else:
request.formatter = ResponseHelper.responsize_xml
"""Return a function to create the response."""
(f, callback) = map(request.values.get, ['f', 'callback'])
if f == 'jsonp':
# Some clients (MiniSub, Perisonic) set f to jsonp without callback for streamed data
if not callback and request.endpoint not in [ 'stream_media', 'cover_art' ]:
return ResponseHelper.responsize_json({
'error': {
'code': 0,
'message': 'Missing callback'
}
}, error = True), 400
request.formatter = lambda x, **kwargs: ResponseHelper.responsize_jsonp(x, callback, kwargs)
elif f == "json":
request.formatter = ResponseHelper.responsize_json
else:
request.formatter = ResponseHelper.responsize_xml
request.error_formatter = lambda code, msg: request.formatter({ 'error': { 'code': code, 'message': msg } }, error = True)
request.error_formatter = lambda code, msg: request.formatter({ 'error': { 'code': code, 'message': msg } }, error = True)
@app.before_request
def authorize():
if not request.path.startswith('/rest/'):
return
if not request.path.startswith('/rest/'):
return
error = request.error_formatter(40, 'Unauthorized'), 401
error = request.error_formatter(40, 'Unauthorized'), 401
if request.authorization:
status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password)
if status == UserManager.SUCCESS:
request.username = request.authorization.username
request.user = user
return
if request.authorization:
status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password)
if status == UserManager.SUCCESS:
request.username = request.authorization.username
request.user = user
return
(username, password) = map(request.values.get, [ 'u', 'p' ])
if not username or not password:
return error
(username, password) = map(request.values.get, [ 'u', 'p' ])
if not username or not password:
return error
status, user = UserManager.try_auth(store, username, password)
if status != UserManager.SUCCESS:
return error
status, user = UserManager.try_auth(store, username, password)
if status != UserManager.SUCCESS:
return error
request.username = username
request.user = user
request.username = username
request.user = user
@app.before_request
def get_client_prefs():
if not request.path.startswith('/rest/'):
return
if not request.path.startswith('/rest/'):
return
if 'c' not in request.values:
return request.error_formatter(10, 'Missing required parameter')
if 'c' not in request.values:
return request.error_formatter(10, 'Missing required parameter')
client = request.values.get('c')
prefs = store.get(ClientPrefs, (request.user.id, client))
if not prefs:
prefs = ClientPrefs()
prefs.user_id = request.user.id
prefs.client_name = client
store.add(prefs)
store.commit()
client = request.values.get('c')
prefs = store.get(ClientPrefs, (request.user.id, client))
if not prefs:
prefs = ClientPrefs()
prefs.user_id = request.user.id
prefs.client_name = client
store.add(prefs)
store.commit()
request.prefs = prefs
request.prefs = prefs
@app.after_request
def set_headers(response):
if not request.path.startswith('/rest/'):
return response
if not request.path.startswith('/rest/'):
return response
if response.mimetype.startswith('text'):
f = request.values.get('f')
response.headers['Content-Type'] = 'application/json' if f in [ 'jsonp', 'json' ] else 'text/xml'
if response.mimetype.startswith('text'):
f = request.values.get('f')
response.headers['Content-Type'] = 'application/json' if f in [ 'jsonp', 'json' ] else 'text/xml'
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Origin'] = '*'
return response
return response
@app.errorhandler(404)
def not_found(error):
if not request.path.startswith('/rest/'):
return error
if not request.path.startswith('/rest/'):
return error
return request.error_formatter(0, 'Not implemented'), 501
return request.error_formatter(0, 'Not implemented'), 501
class ResponseHelper:
@staticmethod
def responsize_json(ret, error = False, version = "1.8.0"):
def check_lists(d):
for key, value in d.items():
if isinstance(value, dict):
d[key] = check_lists(value)
elif isinstance(value, list):
if len(value) == 0:
del d[key]
else:
d[key] = [ check_lists(item) if isinstance(item, dict) else item for item in value ]
return d
@staticmethod
def responsize_json(ret, error = False, version = "1.8.0"):
def check_lists(d):
for key, value in d.items():
if isinstance(value, dict):
d[key] = check_lists(value)
elif isinstance(value, list):
if len(value) == 0:
del d[key]
else:
d[key] = [ check_lists(item) if isinstance(item, dict) else item for item in value ]
return d
ret = check_lists(ret)
# add headers to response
ret.update({
'status': 'failed' if error else 'ok',
'version': version
})
return simplejson.dumps({ 'subsonic-response': ret }, indent = True, encoding = 'utf-8')
ret = check_lists(ret)
# add headers to response
ret.update({
'status': 'failed' if error else 'ok',
'version': version
})
return simplejson.dumps({ 'subsonic-response': ret }, indent = True, encoding = 'utf-8')
@staticmethod
def responsize_jsonp(ret, callback, error = False, version = "1.8.0"):
return "%s(%s)" % (callback, ResponseHelper.responsize_json(ret, error, version))
@staticmethod
def responsize_jsonp(ret, callback, error = False, version = "1.8.0"):
return "%s(%s)" % (callback, ResponseHelper.responsize_json(ret, error, version))
@staticmethod
def responsize_xml(ret, error = False, version = "1.8.0"):
"""Return an xml response from json and replace unsupported characters."""
ret.update({
'status': 'failed' if error else 'ok',
'version': version,
'xmlns': "http://subsonic.org/restapi"
})
@staticmethod
def responsize_xml(ret, error = False, version = "1.8.0"):
"""Return an xml response from json and replace unsupported characters."""
ret.update({
'status': 'failed' if error else 'ok',
'version': version,
'xmlns': "http://subsonic.org/restapi"
})
elem = ElementTree.Element('subsonic-response')
ResponseHelper.dict2xml(elem, ret)
elem = ElementTree.Element('subsonic-response')
ResponseHelper.dict2xml(elem, ret)
return minidom.parseString(ElementTree.tostring(elem)).toprettyxml(indent = ' ', encoding = 'UTF-8')
return minidom.parseString(ElementTree.tostring(elem)).toprettyxml(indent = ' ', encoding = 'UTF-8')
@staticmethod
def dict2xml(elem, dictionary):
"""Convert a json structure to xml. The game is trivial. Nesting uses the [] parenthesis.
ex. { 'musicFolder': {'id': 1234, 'name': "sss" } }
ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]}
ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } }
ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] }
ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]},
"status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}}
"""
if not isinstance(dictionary, dict):
raise TypeError('Expecting a dict')
if not all(map(lambda x: isinstance(x, basestring), dictionary.keys())):
raise TypeError('Dictionary keys must be strings')
@staticmethod
def dict2xml(elem, dictionary):
"""Convert a json structure to xml. The game is trivial. Nesting uses the [] parenthesis.
ex. { 'musicFolder': {'id': 1234, 'name': "sss" } }
ex. { 'musicFolder': [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }]}
ex. { 'musicFolders': {'musicFolder' : [{'id': 1234, 'name': "sss" }, {'id': 456, 'name': "aaa" }] } }
ex. { 'index': [{'name': 'A', 'artist': [{'id': '517674445', 'name': 'Antonello Venditti'}] }] }
ex. {"subsonic-response": { "musicFolders": {"musicFolder": [{ "id": 0,"name": "Music"}]},
"status": "ok","version": "1.7.0","xmlns": "http://subsonic.org/restapi"}}
"""
if not isinstance(dictionary, dict):
raise TypeError('Expecting a dict')
if not all(map(lambda x: isinstance(x, basestring), dictionary.keys())):
raise TypeError('Dictionary keys must be strings')
subelems = { k: v for k, v in dictionary.iteritems() if isinstance(v, dict) }
sequences = { k: v for k, v in dictionary.iteritems() if isinstance(v, list) }
attributes = { k: v for k, v in dictionary.iteritems() if k != '_value_' and k not in subelems and k not in sequences }
subelems = { k: v for k, v in dictionary.iteritems() if isinstance(v, dict) }
sequences = { k: v for k, v in dictionary.iteritems() if isinstance(v, list) }
attributes = { k: v for k, v in dictionary.iteritems() if k != '_value_' and k not in subelems and k not in sequences }
if '_value_' in dictionary:
elem.text = ResponseHelper.value_tostring(dictionary['_value_'])
for attr, value in attributes.iteritems():
elem.set(attr, ResponseHelper.value_tostring(value))
for sub, subdict in subelems.iteritems():
subelem = ElementTree.SubElement(elem, sub)
ResponseHelper.dict2xml(subelem, subdict)
for seq, dicts in sequences.iteritems():
for subdict in dicts:
subelem = ElementTree.SubElement(elem, seq)
ResponseHelper.dict2xml(subelem, subdict)
if '_value_' in dictionary:
elem.text = ResponseHelper.value_tostring(dictionary['_value_'])
for attr, value in attributes.iteritems():
elem.set(attr, ResponseHelper.value_tostring(value))
for sub, subdict in subelems.iteritems():
subelem = ElementTree.SubElement(elem, sub)
ResponseHelper.dict2xml(subelem, subdict)
for seq, dicts in sequences.iteritems():
for subdict in dicts:
subelem = ElementTree.SubElement(elem, seq)
ResponseHelper.dict2xml(subelem, subdict)
@staticmethod
def value_tostring(value):
if isinstance(value, basestring):
return value
if isinstance(value, bool):
return str(value).lower()
return str(value)
@staticmethod
def value_tostring(value):
if isinstance(value, basestring):
return value
if isinstance(value, bool):
return str(value).lower()
return str(value)
def get_entity(req, ent, param = 'id'):
eid = req.values.get(param)
if not eid:
return False, req.error_formatter(10, 'Missing %s id' % ent.__name__)
eid = req.values.get(param)
if not eid:
return False, req.error_formatter(10, 'Missing %s id' % ent.__name__)
try:
eid = uuid.UUID(eid)
except:
return False, req.error_formatter(0, 'Invalid %s id' % ent.__name__)
try:
eid = uuid.UUID(eid)
except:
return False, req.error_formatter(0, 'Invalid %s id' % ent.__name__)
entity = store.get(ent, eid)
if not entity:
return False, (req.error_formatter(70, '%s not found' % ent.__name__), 404)
entity = store.get(ent, eid)
if not entity:
return False, (req.error_formatter(70, '%s not found' % ent.__name__), 404)
return True, entity
return True, entity
from .system import *
from .browse import *

View File

@ -31,171 +31,171 @@ from supysonic.db import now
@app.route('/rest/getRandomSongs.view', methods = [ 'GET', 'POST' ])
def rand_songs():
size = request.values.get('size', '10')
genre, fromYear, toYear, musicFolderId = map(request.values.get, [ 'genre', 'fromYear', 'toYear', 'musicFolderId' ])
size = request.values.get('size', '10')
genre, fromYear, toYear, musicFolderId = map(request.values.get, [ 'genre', 'fromYear', 'toYear', 'musicFolderId' ])
try:
size = int(size) if size else 10
fromYear = int(fromYear) if fromYear else None
toYear = int(toYear) if toYear else None
fid = uuid.UUID(musicFolderId) if musicFolderId else None
except:
return request.error_formatter(0, 'Invalid parameter format')
try:
size = int(size) if size else 10
fromYear = int(fromYear) if fromYear else None
toYear = int(toYear) if toYear else None
fid = uuid.UUID(musicFolderId) if musicFolderId else None
except:
return request.error_formatter(0, 'Invalid parameter format')
query = store.find(Track)
if fromYear:
query = query.find(Track.year >= fromYear)
if toYear:
query = query.find(Track.year <= toYear)
if genre:
query = query.find(Track.genre == genre)
if fid:
query = query.find(Track.root_folder_id == fid)
count = query.count()
query = store.find(Track)
if fromYear:
query = query.find(Track.year >= fromYear)
if toYear:
query = query.find(Track.year <= toYear)
if genre:
query = query.find(Track.genre == genre)
if fid:
query = query.find(Track.root_folder_id == fid)
count = query.count()
if not count:
return request.formatter({ 'randomSongs': {} })
if not count:
return request.formatter({ 'randomSongs': {} })
tracks = []
for _ in xrange(size):
x = random.choice(xrange(count))
tracks.append(query[x])
tracks = []
for _ in xrange(size):
x = random.choice(xrange(count))
tracks.append(query[x])
return request.formatter({
'randomSongs': {
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in tracks ]
}
})
return request.formatter({
'randomSongs': {
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in tracks ]
}
})
@app.route('/rest/getAlbumList.view', methods = [ 'GET', 'POST' ])
def album_list():
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
try:
size = int(size) if size else 10
offset = int(offset) if offset else 0
except:
return request.error_formatter(0, 'Invalid parameter format')
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
try:
size = int(size) if size else 10
offset = int(offset) if offset else 0
except:
return request.error_formatter(0, 'Invalid parameter format')
query = store.find(Folder, Track.folder_id == Folder.id)
if ltype == 'random':
albums = []
count = query.count()
query = store.find(Folder, Track.folder_id == Folder.id)
if ltype == 'random':
albums = []
count = query.count()
if not count:
return request.formatter({ 'albumList': {} })
if not count:
return request.formatter({ 'albumList': {} })
for _ in xrange(size):
x = random.choice(xrange(count))
albums.append(query[x])
for _ in xrange(size):
x = random.choice(xrange(count))
albums.append(query[x])
return request.formatter({
'albumList': {
'album': [ a.as_subsonic_child(request.user) for a in albums ]
}
})
elif ltype == 'newest':
query = query.order_by(Desc(Folder.created)).config(distinct = True)
elif ltype == 'highest':
query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating)))
elif ltype == 'frequent':
query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count)))
elif ltype == 'recent':
query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play)))
elif ltype == 'starred':
query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username)
elif ltype == 'alphabeticalByName':
query = query.order_by(Folder.name).config(distinct = True)
elif ltype == 'alphabeticalByArtist':
parent = ClassAlias(Folder)
query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True)
else:
return request.error_formatter(0, 'Unknown search type')
return request.formatter({
'albumList': {
'album': [ a.as_subsonic_child(request.user) for a in albums ]
}
})
elif ltype == 'newest':
query = query.order_by(Desc(Folder.created)).config(distinct = True)
elif ltype == 'highest':
query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating)))
elif ltype == 'frequent':
query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count)))
elif ltype == 'recent':
query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play)))
elif ltype == 'starred':
query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username)
elif ltype == 'alphabeticalByName':
query = query.order_by(Folder.name).config(distinct = True)
elif ltype == 'alphabeticalByArtist':
parent = ClassAlias(Folder)
query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True)
else:
return request.error_formatter(0, 'Unknown search type')
return request.formatter({
'albumList': {
'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset+size] ]
}
})
return request.formatter({
'albumList': {
'album': [ f.as_subsonic_child(request.user) for f in query[offset:offset+size] ]
}
})
@app.route('/rest/getAlbumList2.view', methods = [ 'GET', 'POST' ])
def album_list_id3():
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
try:
size = int(size) if size else 10
offset = int(offset) if offset else 0
except:
return request.error_formatter(0, 'Invalid parameter format')
ltype, size, offset = map(request.values.get, [ 'type', 'size', 'offset' ])
try:
size = int(size) if size else 10
offset = int(offset) if offset else 0
except:
return request.error_formatter(0, 'Invalid parameter format')
query = store.find(Album)
if ltype == 'random':
albums = []
count = query.count()
query = store.find(Album)
if ltype == 'random':
albums = []
count = query.count()
if not count:
return request.formatter({ 'albumList2': {} })
if not count:
return request.formatter({ 'albumList2': {} })
for _ in xrange(size):
x = random.choice(xrange(count))
albums.append(query[x])
for _ in xrange(size):
x = random.choice(xrange(count))
albums.append(query[x])
return request.formatter({
'albumList2': {
'album': [ a.as_subsonic_album(request.user) for a in albums ]
}
})
elif ltype == 'newest':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created)))
elif ltype == 'frequent':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count)))
elif ltype == 'recent':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play)))
elif ltype == 'starred':
query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username)
elif ltype == 'alphabeticalByName':
query = query.order_by(Album.name)
elif ltype == 'alphabeticalByArtist':
query = query.find(Artist.id == Album.artist_id).order_by(Artist.name, Album.name)
else:
return request.error_formatter(0, 'Unknown search type')
return request.formatter({
'albumList2': {
'album': [ a.as_subsonic_album(request.user) for a in albums ]
}
})
elif ltype == 'newest':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created)))
elif ltype == 'frequent':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count)))
elif ltype == 'recent':
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play)))
elif ltype == 'starred':
query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username)
elif ltype == 'alphabeticalByName':
query = query.order_by(Album.name)
elif ltype == 'alphabeticalByArtist':
query = query.find(Artist.id == Album.artist_id).order_by(Artist.name, Album.name)
else:
return request.error_formatter(0, 'Unknown search type')
return request.formatter({
'albumList2': {
'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset+size] ]
}
})
return request.formatter({
'albumList2': {
'album': [ f.as_subsonic_album(request.user) for f in query[offset:offset+size] ]
}
})
@app.route('/rest/getNowPlaying.view', methods = [ 'GET', 'POST' ])
def now_playing():
query = store.find(User, Track.id == User.last_play_id)
query = store.find(User, Track.id == User.last_play_id)
return request.formatter({
'nowPlaying': {
'entry': [ dict(
u.last_play.as_subsonic_child(request.user, request.prefs).items() +
{ 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items()
) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ]
}
})
return request.formatter({
'nowPlaying': {
'entry': [ dict(
u.last_play.as_subsonic_child(request.user, request.prefs).items() +
{ 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items()
) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ]
}
})
@app.route('/rest/getStarred.view', methods = [ 'GET', 'POST' ])
def get_starred():
folders = store.find(StarredFolder, StarredFolder.user_id == User.id, User.name == request.username)
folders = store.find(StarredFolder, StarredFolder.user_id == User.id, User.name == request.username)
return request.formatter({
'starred': {
'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ],
'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ],
'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
}
})
return request.formatter({
'starred': {
'artist': [ { 'id': str(sf.starred_id), 'name': sf.starred.name } for sf in folders.find(Folder.parent_id == StarredFolder.starred_id, Track.folder_id == Folder.id).config(distinct = True) ],
'album': [ sf.starred.as_subsonic_child(request.user) for sf in folders.find(Track.folder_id == StarredFolder.starred_id).config(distinct = True) ],
'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
}
})
@app.route('/rest/getStarred2.view', methods = [ 'GET', 'POST' ])
def get_starred_id3():
return request.formatter({
'starred2': {
'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ],
'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ],
'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
}
})
return request.formatter({
'starred2': {
'artist': [ sa.starred.as_subsonic_artist(request.user) for sa in store.find(StarredArtist, StarredArtist.user_id == User.id, User.name == request.username) ],
'album': [ sa.starred.as_subsonic_album(request.user) for sa in store.find(StarredAlbum, StarredAlbum.user_id == User.id, User.name == request.username) ],
'song': [ st.starred.as_subsonic_child(request.user, request.prefs) for st in store.find(StarredTrack, StarredTrack.user_id == User.id, User.name == request.username) ]
}
})

View File

@ -30,145 +30,145 @@ from supysonic.db import RatingTrack, RatingFolder
@app.route('/rest/star.view', methods = [ 'GET', 'POST' ])
def star():
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
def try_star(ent, starred_ent, eid):
try:
uid = uuid.UUID(eid)
except:
return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__)
def try_star(ent, starred_ent, eid):
try:
uid = uuid.UUID(eid)
except:
return 2, request.error_formatter(0, 'Invalid %s id' % ent.__name__)
if store.get(starred_ent, (request.user.id, uid)):
return 2, request.error_formatter(0, '%s already starred' % ent.__name__)
e = store.get(ent, uid)
if e:
starred = starred_ent()
starred.user_id = request.user.id
starred.starred_id = uid
store.add(starred)
else:
return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__)
if store.get(starred_ent, (request.user.id, uid)):
return 2, request.error_formatter(0, '%s already starred' % ent.__name__)
e = store.get(ent, uid)
if e:
starred = starred_ent()
starred.user_id = request.user.id
starred.starred_id = uid
store.add(starred)
else:
return 1, request.error_formatter(70, 'Unknown %s id' % ent.__name__)
return 0, None
return 0, None
for eid in id:
err, ferror = try_star(Track, StarredTrack, eid)
if err == 1:
err, ferror = try_star(Folder, StarredFolder, eid)
if err:
return ferror
elif err == 2:
return ferror
for eid in id:
err, ferror = try_star(Track, StarredTrack, eid)
if err == 1:
err, ferror = try_star(Folder, StarredFolder, eid)
if err:
return ferror
elif err == 2:
return ferror
for alId in albumId:
err, ferror = try_star(Album, StarredAlbum, alId)
if err:
return ferror
for alId in albumId:
err, ferror = try_star(Album, StarredAlbum, alId)
if err:
return ferror
for arId in artistId:
err, ferror = try_star(Artist, StarredArtist, arId)
if err:
return ferror
for arId in artistId:
err, ferror = try_star(Artist, StarredArtist, arId)
if err:
return ferror
store.commit()
return request.formatter({})
store.commit()
return request.formatter({})
@app.route('/rest/unstar.view', methods = [ 'GET', 'POST' ])
def unstar():
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
id, albumId, artistId = map(request.values.getlist, [ 'id', 'albumId', 'artistId' ])
def try_unstar(ent, eid):
try:
uid = uuid.UUID(eid)
except:
return request.error_formatter(0, 'Invalid id')
def try_unstar(ent, eid):
try:
uid = uuid.UUID(eid)
except:
return request.error_formatter(0, 'Invalid id')
store.find(ent, ent.user_id == request.user.id, ent.starred_id == uid).remove()
return None
store.find(ent, ent.user_id == request.user.id, ent.starred_id == uid).remove()
return None
for eid in id:
err = try_unstar(StarredTrack, eid)
if err:
return err
err = try_unstar(StarredFolder, eid)
if err:
return err
for eid in id:
err = try_unstar(StarredTrack, eid)
if err:
return err
err = try_unstar(StarredFolder, eid)
if err:
return err
for alId in albumId:
err = try_unstar(StarredAlbum, alId)
if err:
return err
for alId in albumId:
err = try_unstar(StarredAlbum, alId)
if err:
return err
for arId in artistId:
err = try_unstar(StarredArtist, arId)
if err:
return err
for arId in artistId:
err = try_unstar(StarredArtist, arId)
if err:
return err
store.commit()
return request.formatter({})
store.commit()
return request.formatter({})
@app.route('/rest/setRating.view', methods = [ 'GET', 'POST' ])
def rate():
id, rating = map(request.values.get, [ 'id', 'rating' ])
if not id or not rating:
return request.error_formatter(10, 'Missing parameter')
id, rating = map(request.values.get, [ 'id', 'rating' ])
if not id or not rating:
return request.error_formatter(10, 'Missing parameter')
try:
uid = uuid.UUID(id)
rating = int(rating)
except:
return request.error_formatter(0, 'Invalid parameter')
try:
uid = uuid.UUID(id)
rating = int(rating)
except:
return request.error_formatter(0, 'Invalid parameter')
if not rating in xrange(6):
return request.error_formatter(0, 'rating must be between 0 and 5 (inclusive)')
if not rating in xrange(6):
return request.error_formatter(0, 'rating must be between 0 and 5 (inclusive)')
if rating == 0:
store.find(RatingTrack, RatingTrack.user_id == request.user.id, RatingTrack.rated_id == uid).remove()
store.find(RatingFolder, RatingFolder.user_id == request.user.id, RatingFolder.rated_id == uid).remove()
else:
rated = store.get(Track, uid)
rating_ent = RatingTrack
if not rated:
rated = store.get(Folder, uid)
rating_ent = RatingFolder
if not rated:
return request.error_formatter(70, 'Unknown id')
if rating == 0:
store.find(RatingTrack, RatingTrack.user_id == request.user.id, RatingTrack.rated_id == uid).remove()
store.find(RatingFolder, RatingFolder.user_id == request.user.id, RatingFolder.rated_id == uid).remove()
else:
rated = store.get(Track, uid)
rating_ent = RatingTrack
if not rated:
rated = store.get(Folder, uid)
rating_ent = RatingFolder
if not rated:
return request.error_formatter(70, 'Unknown id')
rating_info = store.get(rating_ent, (request.user.id, uid))
if rating_info:
rating_info.rating = rating
else:
rating_info = rating_ent()
rating_info.user_id = request.user.id
rating_info.rated_id = uid
rating_info.rating = rating
store.add(rating_info)
rating_info = store.get(rating_ent, (request.user.id, uid))
if rating_info:
rating_info.rating = rating
else:
rating_info = rating_ent()
rating_info.user_id = request.user.id
rating_info.rated_id = uid
rating_info.rating = rating
store.add(rating_info)
store.commit()
return request.formatter({})
store.commit()
return request.formatter({})
@app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ])
def scrobble():
status, res = get_entity(request, Track)
if not status:
return res
status, res = get_entity(request, Track)
if not status:
return res
t, submission = map(request.values.get, [ 'time', 'submission' ])
t, submission = map(request.values.get, [ 'time', 'submission' ])
if t:
try:
t = int(t) / 1000
except:
return request.error_formatter(0, 'Invalid time value')
else:
t = int(time.time())
if t:
try:
t = int(t) / 1000
except:
return request.error_formatter(0, 'Invalid time value')
else:
t = int(time.time())
lfm = LastFm(request.user, app.logger)
lfm = LastFm(request.user, app.logger)
if submission in (None, '', True, 'true', 'True', 1, '1'):
lfm.scrobble(res, t)
else:
lfm.now_playing(res)
if submission in (None, '', True, 'true', 'True', 1, '1'):
lfm.scrobble(res, t)
else:
lfm.now_playing(res)
return request.formatter({})
return request.formatter({})

View File

@ -26,155 +26,155 @@ import uuid, string
@app.route('/rest/getMusicFolders.view', methods = [ 'GET', 'POST' ])
def list_folders():
return request.formatter({
'musicFolders': {
'musicFolder': [ {
'id': str(f.id),
'name': f.name
} for f in store.find(Folder, Folder.root == True).order_by(Folder.name) ]
}
})
return request.formatter({
'musicFolders': {
'musicFolder': [ {
'id': str(f.id),
'name': f.name
} for f in store.find(Folder, Folder.root == True).order_by(Folder.name) ]
}
})
@app.route('/rest/getIndexes.view', methods = [ 'GET', 'POST' ])
def list_indexes():
musicFolderId = request.values.get('musicFolderId')
ifModifiedSince = request.values.get('ifModifiedSince')
if ifModifiedSince:
try:
ifModifiedSince = int(ifModifiedSince) / 1000
except:
return request.error_formatter(0, 'Invalid timestamp')
musicFolderId = request.values.get('musicFolderId')
ifModifiedSince = request.values.get('ifModifiedSince')
if ifModifiedSince:
try:
ifModifiedSince = int(ifModifiedSince) / 1000
except:
return request.error_formatter(0, 'Invalid timestamp')
if musicFolderId is None:
folder = store.find(Folder, Folder.root == True)
else:
try:
mfid = uuid.UUID(musicFolderId)
except:
return request.error_formatter(0, 'Invalid id')
if musicFolderId is None:
folder = store.find(Folder, Folder.root == True)
else:
try:
mfid = uuid.UUID(musicFolderId)
except:
return request.error_formatter(0, 'Invalid id')
folder = store.get(Folder, mfid)
folder = store.get(Folder, mfid)
if not folder or (type(folder) is Folder and not folder.root):
return request.error_formatter(70, 'Folder not found')
if not folder or (type(folder) is Folder and not folder.root):
return request.error_formatter(70, 'Folder not found')
last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is not Folder else folder.last_scan
last_modif = max(map(lambda f: f.last_scan, folder)) if type(folder) is not Folder else folder.last_scan
if (not ifModifiedSince is None) and last_modif < ifModifiedSince:
return request.formatter({ 'indexes': { 'lastModified': last_modif * 1000 } })
if (not ifModifiedSince is None) and last_modif < ifModifiedSince:
return request.formatter({ 'indexes': { 'lastModified': last_modif * 1000 } })
# The XSD lies, we don't return artists but a directory structure
if type(folder) is not Folder:
artists = []
childs = []
for f in folder:
artists += f.children
childs += f.tracks
else:
artists = folder.children
childs = folder.tracks
# The XSD lies, we don't return artists but a directory structure
if type(folder) is not Folder:
artists = []
childs = []
for f in folder:
artists += f.children
childs += f.tracks
else:
artists = folder.children
childs = folder.tracks
indexes = {}
for artist in artists:
index = artist.name[0].upper()
if index in map(str, xrange(10)):
index = '#'
elif index not in string.letters:
index = '?'
indexes = {}
for artist in artists:
index = artist.name[0].upper()
if index in map(str, xrange(10)):
index = '#'
elif index not in string.letters:
index = '?'
if index not in indexes:
indexes[index] = []
if index not in indexes:
indexes[index] = []
indexes[index].append(artist)
indexes[index].append(artist)
return request.formatter({
'indexes': {
'lastModified': last_modif * 1000,
'index': [ {
'name': k,
'artist': [ {
'id': str(a.id),
'name': a.name
} for a in sorted(v, key = lambda a: a.name.lower()) ]
} for k, v in sorted(indexes.iteritems()) ],
'child': [ c.as_subsonic_child(request.user, request.prefs) for c in sorted(childs, key = lambda t: t.sort_key()) ]
}
})
return request.formatter({
'indexes': {
'lastModified': last_modif * 1000,
'index': [ {
'name': k,
'artist': [ {
'id': str(a.id),
'name': a.name
} for a in sorted(v, key = lambda a: a.name.lower()) ]
} for k, v in sorted(indexes.iteritems()) ],
'child': [ c.as_subsonic_child(request.user, request.prefs) for c in sorted(childs, key = lambda t: t.sort_key()) ]
}
})
@app.route('/rest/getMusicDirectory.view', methods = [ 'GET', 'POST' ])
def show_directory():
status, res = get_entity(request, Folder)
if not status:
return res
status, res = get_entity(request, Folder)
if not status:
return res
directory = {
'id': str(res.id),
'name': res.name,
'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
}
if not res.root:
directory['parent'] = str(res.parent_id)
directory = {
'id': str(res.id),
'name': res.name,
'child': [ f.as_subsonic_child(request.user) for f in sorted(res.children, key = lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
}
if not res.root:
directory['parent'] = str(res.parent_id)
return request.formatter({ 'directory': directory })
return request.formatter({ 'directory': directory })
@app.route('/rest/getArtists.view', methods = [ 'GET', 'POST' ])
def list_artists():
# According to the API page, there are no parameters?
indexes = {}
for artist in store.find(Artist):
index = artist.name[0].upper() if artist.name else '?'
if index in map(str, xrange(10)):
index = '#'
elif index not in string.letters:
index = '?'
# According to the API page, there are no parameters?
indexes = {}
for artist in store.find(Artist):
index = artist.name[0].upper() if artist.name else '?'
if index in map(str, xrange(10)):
index = '#'
elif index not in string.letters:
index = '?'
if index not in indexes:
indexes[index] = []
if index not in indexes:
indexes[index] = []
indexes[index].append(artist)
indexes[index].append(artist)
return request.formatter({
'artists': {
'index': [ {
'name': k,
'artist': [ a.as_subsonic_artist(request.user) for a in sorted(v, key = lambda a: a.name.lower()) ]
} for k, v in sorted(indexes.iteritems()) ]
}
})
return request.formatter({
'artists': {
'index': [ {
'name': k,
'artist': [ a.as_subsonic_artist(request.user) for a in sorted(v, key = lambda a: a.name.lower()) ]
} for k, v in sorted(indexes.iteritems()) ]
}
})
@app.route('/rest/getArtist.view', methods = [ 'GET', 'POST' ])
def artist_info():
status, res = get_entity(request, Artist)
if not status:
return res
status, res = get_entity(request, Artist)
if not status:
return res
info = res.as_subsonic_artist(request.user)
albums = set(res.albums)
albums |= { t.album for t in res.tracks }
info['album'] = [ a.as_subsonic_album(request.user) for a in sorted(albums, key = lambda a: a.sort_key()) ]
info = res.as_subsonic_artist(request.user)
albums = set(res.albums)
albums |= { t.album for t in res.tracks }
info['album'] = [ a.as_subsonic_album(request.user) for a in sorted(albums, key = lambda a: a.sort_key()) ]
return request.formatter({ 'artist': info })
return request.formatter({ 'artist': info })
@app.route('/rest/getAlbum.view', methods = [ 'GET', 'POST' ])
def album_info():
status, res = get_entity(request, Album)
if not status:
return res
status, res = get_entity(request, Album)
if not status:
return res
info = res.as_subsonic_album(request.user)
info['song'] = [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
info = res.as_subsonic_album(request.user)
info['song'] = [ t.as_subsonic_child(request.user, request.prefs) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
return request.formatter({ 'album': info })
return request.formatter({ 'album': info })
@app.route('/rest/getSong.view', methods = [ 'GET', 'POST' ])
def track_info():
status, res = get_entity(request, Track)
if not status:
return res
status, res = get_entity(request, Track)
if not status:
return res
return request.formatter({ 'song': res.as_subsonic_child(request.user, request.prefs) })
return request.formatter({ 'song': res.as_subsonic_child(request.user, request.prefs) })
@app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ])
def list_videos():
return request.error_formatter(0, 'Video streaming not supported')
return request.error_formatter(0, 'Video streaming not supported')

View File

@ -24,28 +24,28 @@ from supysonic.db import ChatMessage
@app.route('/rest/getChatMessages.view', methods = [ 'GET', 'POST' ])
def get_chat():
since = request.values.get('since')
try:
since = int(since) / 1000 if since else None
except:
return request.error_formatter(0, 'Invalid parameter')
since = request.values.get('since')
try:
since = int(since) / 1000 if since else None
except:
return request.error_formatter(0, 'Invalid parameter')
query = store.find(ChatMessage).order_by(ChatMessage.time)
if since:
query = query.find(ChatMessage.time > since)
query = store.find(ChatMessage).order_by(ChatMessage.time)
if since:
query = query.find(ChatMessage.time > since)
return request.formatter({ 'chatMessages': { 'chatMessage': [ msg.responsize() for msg in query ] }})
return request.formatter({ 'chatMessages': { 'chatMessage': [ msg.responsize() for msg in query ] }})
@app.route('/rest/addChatMessage.view', methods = [ 'GET', 'POST' ])
def add_chat_message():
msg = request.values.get('message')
if not msg:
return request.error_formatter(10, 'Missing message')
msg = request.values.get('message')
if not msg:
return request.error_formatter(10, 'Missing message')
chat = ChatMessage()
chat.user_id = request.user.id
chat.message = msg
store.add(chat)
store.commit()
return request.formatter({})
chat = ChatMessage()
chat.user_id = request.user.id
chat.message = msg
store.add(chat)
store.commit()
return request.formatter({})

View File

@ -32,197 +32,197 @@ from supysonic.db import Track, Album, Artist, Folder, User, ClientPrefs, now
from . import get_entity
def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_format, output_bitrate):
if not base_cmdline:
return None
ret = base_cmdline.split()
for i in xrange(len(ret)):
ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
return ret
if not base_cmdline:
return None
ret = base_cmdline.split()
for i in xrange(len(ret)):
ret[i] = ret[i].replace('%srcpath', input_file).replace('%srcfmt', input_format).replace('%outfmt', output_format).replace('%outrate', str(output_bitrate))
return ret
@app.route('/rest/stream.view', methods = [ 'GET', 'POST' ])
def stream_media():
status, res = get_entity(request, Track)
if not status:
return res
status, res = get_entity(request, Track)
if not status:
return res
maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ])
if format:
format = format.lower()
maxBitRate, format, timeOffset, size, estimateContentLength = map(request.values.get, [ 'maxBitRate', 'format', 'timeOffset', 'size', 'estimateContentLength' ])
if format:
format = format.lower()
src_suffix = res.suffix()
dst_suffix = res.suffix()
dst_bitrate = res.bitrate
dst_mimetype = res.content_type
src_suffix = res.suffix()
dst_suffix = res.suffix()
dst_bitrate = res.bitrate
dst_mimetype = res.content_type
if request.prefs.format:
dst_suffix = request.prefs.format
if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate:
dst_bitrate = request.prefs.bitrate
if request.prefs.format:
dst_suffix = request.prefs.format
if request.prefs.bitrate and request.prefs.bitrate < dst_bitrate:
dst_bitrate = request.prefs.bitrate
if maxBitRate:
try:
maxBitRate = int(maxBitRate)
except:
return request.error_formatter(0, 'Invalid bitrate value')
if maxBitRate:
try:
maxBitRate = int(maxBitRate)
except:
return request.error_formatter(0, 'Invalid bitrate value')
if dst_bitrate > maxBitRate and maxBitRate != 0:
dst_bitrate = maxBitRate
if dst_bitrate > maxBitRate and maxBitRate != 0:
dst_bitrate = maxBitRate
if format and format != 'raw' and format != src_suffix:
dst_suffix = format
dst_mimetype = config.get_mime(dst_suffix)
if format and format != 'raw' and format != src_suffix:
dst_suffix = format
dst_mimetype = config.get_mime(dst_suffix)
if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate):
transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix))
decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder')
encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder')
if not transcoder and (not decoder or not encoder):
transcoder = config.get('transcoding', 'transcoder')
if not transcoder:
message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix)
app.logger.info(message)
return request.error_formatter(0, message)
if format != 'raw' and (dst_suffix != src_suffix or dst_bitrate != res.bitrate):
transcoder = config.get('transcoding', 'transcoder_{}_{}'.format(src_suffix, dst_suffix))
decoder = config.get('transcoding', 'decoder_' + src_suffix) or config.get('transcoding', 'decoder')
encoder = config.get('transcoding', 'encoder_' + dst_suffix) or config.get('transcoding', 'encoder')
if not transcoder and (not decoder or not encoder):
transcoder = config.get('transcoding', 'transcoder')
if not transcoder:
message = 'No way to transcode from {} to {}'.format(src_suffix, dst_suffix)
app.logger.info(message)
return request.error_formatter(0, message)
transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ])
try:
if transcoder:
dec_proc = None
proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE)
else:
dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE)
proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE)
except:
return request.error_formatter(0, 'Error while running the transcoding process')
transcoder, decoder, encoder = map(lambda x: prepare_transcoding_cmdline(x, res.path, src_suffix, dst_suffix, dst_bitrate), [ transcoder, decoder, encoder ])
try:
if transcoder:
dec_proc = None
proc = subprocess.Popen(transcoder, stdout = subprocess.PIPE)
else:
dec_proc = subprocess.Popen(decoder, stdout = subprocess.PIPE)
proc = subprocess.Popen(encoder, stdin = dec_proc.stdout, stdout = subprocess.PIPE)
except:
return request.error_formatter(0, 'Error while running the transcoding process')
def transcode():
try:
while True:
data = proc.stdout.read(8192)
if not data:
break
yield data
except:
if dec_proc != None:
dec_proc.terminate()
proc.terminate()
def transcode():
try:
while True:
data = proc.stdout.read(8192)
if not data:
break
yield data
except:
if dec_proc != None:
dec_proc.terminate()
proc.terminate()
if dec_proc != None:
dec_proc.wait()
proc.wait()
if dec_proc != None:
dec_proc.wait()
proc.wait()
app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate))
response = Response(transcode(), mimetype = dst_mimetype)
else:
response = send_file(res.path, mimetype = dst_mimetype, conditional=True)
app.logger.info('Transcoding track {0.id} for user {1.id}. Source: {2} at {0.bitrate}kbps. Dest: {3} at {4}kbps'.format(res, request.user, src_suffix, dst_suffix, dst_bitrate))
response = Response(transcode(), mimetype = dst_mimetype)
else:
response = send_file(res.path, mimetype = dst_mimetype, conditional=True)
res.play_count = res.play_count + 1
res.last_play = now()
request.user.last_play = res
request.user.last_play_date = now()
store.commit()
res.play_count = res.play_count + 1
res.last_play = now()
request.user.last_play = res
request.user.last_play_date = now()
store.commit()
return response
return response
@app.route('/rest/download.view', methods = [ 'GET', 'POST' ])
def download_media():
status, res = get_entity(request, Track)
if not status:
return res
status, res = get_entity(request, Track)
if not status:
return res
return send_file(res.path, conditional=True)
return send_file(res.path, conditional=True)
@app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ])
def cover_art():
status, res = get_entity(request, Folder)
if not status:
return res
status, res = get_entity(request, Folder)
if not status:
return res
if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')):
return request.error_formatter(70, 'Cover art not found')
if not res.has_cover_art or not os.path.isfile(os.path.join(res.path, 'cover.jpg')):
return request.error_formatter(70, 'Cover art not found')
size = request.values.get('size')
if size:
try:
size = int(size)
except:
return request.error_formatter(0, 'Invalid size value')
else:
return send_file(os.path.join(res.path, 'cover.jpg'))
size = request.values.get('size')
if size:
try:
size = int(size)
except:
return request.error_formatter(0, 'Invalid size value')
else:
return send_file(os.path.join(res.path, 'cover.jpg'))
im = Image.open(os.path.join(res.path, 'cover.jpg'))
if size > im.size[0] and size > im.size[1]:
return send_file(os.path.join(res.path, 'cover.jpg'))
im = Image.open(os.path.join(res.path, 'cover.jpg'))
if size > im.size[0] and size > im.size[1]:
return send_file(os.path.join(res.path, 'cover.jpg'))
size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size))
path = os.path.abspath(os.path.join(size_path, str(res.id)))
if os.path.exists(path):
return send_file(path)
if not os.path.exists(size_path):
os.makedirs(size_path)
size_path = os.path.join(config.get('webapp', 'cache_dir'), str(size))
path = os.path.abspath(os.path.join(size_path, str(res.id)))
if os.path.exists(path):
return send_file(path)
if not os.path.exists(size_path):
os.makedirs(size_path)
im.thumbnail([size, size], Image.ANTIALIAS)
im.save(path, 'JPEG')
return send_file(path)
im.thumbnail([size, size], Image.ANTIALIAS)
im.save(path, 'JPEG')
return send_file(path)
@app.route('/rest/getLyrics.view', methods = [ 'GET', 'POST' ])
def lyrics():
artist, title = map(request.values.get, [ 'artist', 'title' ])
if not artist:
return request.error_formatter(10, 'Missing artist parameter')
if not title:
return request.error_formatter(10, 'Missing title parameter')
artist, title = map(request.values.get, [ 'artist', 'title' ])
if not artist:
return request.error_formatter(10, 'Missing artist parameter')
if not title:
return request.error_formatter(10, 'Missing title parameter')
query = store.find(Track, Album.id == Track.album_id, Artist.id == Album.artist_id, Track.title.like(title), Artist.name.like(artist))
for track in query:
lyrics_path = os.path.splitext(track.path)[0] + '.txt'
if os.path.exists(lyrics_path):
app.logger.debug('Found lyrics file: ' + lyrics_path)
query = store.find(Track, Album.id == Track.album_id, Artist.id == Album.artist_id, Track.title.like(title), Artist.name.like(artist))
for track in query:
lyrics_path = os.path.splitext(track.path)[0] + '.txt'
if os.path.exists(lyrics_path):
app.logger.debug('Found lyrics file: ' + lyrics_path)
try:
lyrics = read_file_as_unicode(lyrics_path)
except UnicodeError:
# Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or
# return no lyrics. Log it anyway.
app.logger.warn('Unsupported encoding for lyrics file ' + lyrics_path)
continue
try:
lyrics = read_file_as_unicode(lyrics_path)
except UnicodeError:
# Lyrics file couldn't be decoded. Rather than displaying an error, try with the potential next files or
# return no lyrics. Log it anyway.
app.logger.warn('Unsupported encoding for lyrics file ' + lyrics_path)
continue
return request.formatter({ 'lyrics': {
'artist': track.album.artist.name,
'title': track.title,
'_value_': lyrics
} })
return request.formatter({ 'lyrics': {
'artist': track.album.artist.name,
'title': track.title,
'_value_': lyrics
} })
try:
r = requests.get("http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect",
params = { 'artist': artist, 'song': title })
root = ElementTree.fromstring(r.content)
try:
r = requests.get("http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect",
params = { 'artist': artist, 'song': title })
root = ElementTree.fromstring(r.content)
ns = { 'cl': 'http://api.chartlyrics.com/' }
return request.formatter({ 'lyrics': {
'artist': root.find('cl:LyricArtist', namespaces = ns).text,
'title': root.find('cl:LyricSong', namespaces = ns).text,
'_value_': root.find('cl:Lyric', namespaces = ns).text
} })
except requests.exceptions.RequestException, e:
app.logger.warn('Error while requesting the ChartLyrics API: ' + str(e))
ns = { 'cl': 'http://api.chartlyrics.com/' }
return request.formatter({ 'lyrics': {
'artist': root.find('cl:LyricArtist', namespaces = ns).text,
'title': root.find('cl:LyricSong', namespaces = ns).text,
'_value_': root.find('cl:Lyric', namespaces = ns).text
} })
except requests.exceptions.RequestException, e:
app.logger.warn('Error while requesting the ChartLyrics API: ' + str(e))
return request.formatter({ 'lyrics': {} })
return request.formatter({ 'lyrics': {} })
def read_file_as_unicode(path):
""" Opens a file trying with different encodings and returns the contents as a unicode string """
""" Opens a file trying with different encodings and returns the contents as a unicode string """
encodings = [ 'utf-8', 'latin1' ] # Should be extended to support more encodings
encodings = [ 'utf-8', 'latin1' ] # Should be extended to support more encodings
for enc in encodings:
try:
contents = codecs.open(path, 'r', encoding = enc).read()
app.logger.debug('Read file {} with {} encoding'.format(path, enc))
# Maybe save the encoding somewhere to prevent going through this loop each time for the same file
return contents
except UnicodeError:
pass
for enc in encodings:
try:
contents = codecs.open(path, 'r', encoding = enc).read()
app.logger.debug('Read file {} with {} encoding'.format(path, enc))
# Maybe save the encoding somewhere to prevent going through this loop each time for the same file
return contents
except UnicodeError:
pass
# Fallback to ASCII
app.logger.debug('Reading file {} with ascii encoding'.format(path))
return unicode(open(path, 'r').read())
# Fallback to ASCII
app.logger.debug('Reading file {} with ascii encoding'.format(path))
return unicode(open(path, 'r').read())

View File

@ -27,114 +27,114 @@ from . import get_entity
@app.route('/rest/getPlaylists.view', methods = [ 'GET', 'POST' ])
def list_playlists():
query = store.find(Playlist, Or(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(Playlist.name)
query = store.find(Playlist, Or(Playlist.user_id == request.user.id, Playlist.public == True)).order_by(Playlist.name)
username = request.values.get('username')
if username:
if not request.user.admin:
return request.error_formatter(50, 'Restricted to admins')
username = request.values.get('username')
if username:
if not request.user.admin:
return request.error_formatter(50, 'Restricted to admins')
query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name)
query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name)
return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
@app.route('/rest/getPlaylist.view', methods = [ 'GET', 'POST' ])
def show_playlist():
status, res = get_entity(request, Playlist)
if not status:
return res
status, res = get_entity(request, Playlist)
if not status:
return res
info = res.as_subsonic_playlist(request.user)
info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ]
return request.formatter({ 'playlist': info })
info = res.as_subsonic_playlist(request.user)
info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ]
return request.formatter({ 'playlist': info })
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
def create_playlist():
# Only(?) method where the android client uses form data rather than GET params
playlist_id, name = map(request.values.get, [ 'playlistId', 'name' ])
# songId actually doesn't seem to be required
songs = request.values.getlist('songId')
try:
playlist_id = uuid.UUID(playlist_id) if playlist_id else None
songs = map(uuid.UUID, songs)
except:
return request.error_formatter(0, 'Invalid parameter')
# Only(?) method where the android client uses form data rather than GET params
playlist_id, name = map(request.values.get, [ 'playlistId', 'name' ])
# songId actually doesn't seem to be required
songs = request.values.getlist('songId')
try:
playlist_id = uuid.UUID(playlist_id) if playlist_id else None
songs = map(uuid.UUID, songs)
except:
return request.error_formatter(0, 'Invalid parameter')
if playlist_id:
playlist = store.get(Playlist, playlist_id)
if not playlist:
return request.error_formatter(70, 'Unknwon playlist')
if playlist_id:
playlist = store.get(Playlist, playlist_id)
if not playlist:
return request.error_formatter(70, 'Unknwon playlist')
if playlist.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours")
if playlist.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours")
playlist.clear()
if name:
playlist.name = name
elif name:
playlist = Playlist()
playlist.user_id = request.user.id
playlist.name = name
store.add(playlist)
else:
return request.error_formatter(10, 'Missing playlist id or name')
playlist.clear()
if name:
playlist.name = name
elif name:
playlist = Playlist()
playlist.user_id = request.user.id
playlist.name = name
store.add(playlist)
else:
return request.error_formatter(10, 'Missing playlist id or name')
for sid in songs:
track = store.get(Track, sid)
if not track:
return request.error_formatter(70, 'Unknown song')
for sid in songs:
track = store.get(Track, sid)
if not track:
return request.error_formatter(70, 'Unknown song')
playlist.add(track)
playlist.add(track)
store.commit()
return request.formatter({})
store.commit()
return request.formatter({})
@app.route('/rest/deletePlaylist.view', methods = [ 'GET', 'POST' ])
def delete_playlist():
status, res = get_entity(request, Playlist)
if not status:
return res
status, res = get_entity(request, Playlist)
if not status:
return res
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
store.remove(res)
store.commit()
return request.formatter({})
store.remove(res)
store.commit()
return request.formatter({})
@app.route('/rest/updatePlaylist.view', methods = [ 'GET', 'POST' ])
def update_playlist():
status, res = get_entity(request, Playlist, 'playlistId')
if not status:
return res
status, res = get_entity(request, Playlist, 'playlistId')
if not status:
return res
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
if res.user_id != request.user.id and not request.user.admin:
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
playlist = res
name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ])
to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ])
try:
to_add = map(uuid.UUID, to_add)
to_remove = map(int, to_remove)
except:
return request.error_formatter(0, 'Invalid parameter')
playlist = res
name, comment, public = map(request.values.get, [ 'name', 'comment', 'public' ])
to_add, to_remove = map(request.values.getlist, [ 'songIdToAdd', 'songIndexToRemove' ])
try:
to_add = map(uuid.UUID, to_add)
to_remove = map(int, to_remove)
except:
return request.error_formatter(0, 'Invalid parameter')
if name:
playlist.name = name
if comment:
playlist.comment = comment
if public:
playlist.public = public in (True, 'True', 'true', 1, '1')
if name:
playlist.name = name
if comment:
playlist.comment = comment
if public:
playlist.public = public in (True, 'True', 'true', 1, '1')
for sid in to_add:
track = store.get(Track, sid)
if not track:
return request.error_formatter(70, 'Unknown song')
playlist.add(track)
for sid in to_add:
track = store.get(Track, sid)
if not track:
return request.error_formatter(70, 'Unknown song')
playlist.add(track)
playlist.remove_at_indexes(to_remove)
playlist.remove_at_indexes(to_remove)
store.commit()
return request.formatter({})
store.commit()
return request.formatter({})

View File

@ -25,98 +25,98 @@ from supysonic.db import Folder, Track, Artist, Album
@app.route('/rest/search.view', methods = [ 'GET', 'POST' ])
def old_search():
artist, album, title, anyf, count, offset, newer_than = map(request.values.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ])
try:
count = int(count) if count else 20
offset = int(offset) if offset else 0
newer_than = int(newer_than) if newer_than else 0
except:
return request.error_formatter(0, 'Invalid parameter')
artist, album, title, anyf, count, offset, newer_than = map(request.values.get, [ 'artist', 'album', 'title', 'any', 'count', 'offset', 'newerThan' ])
try:
count = int(count) if count else 20
offset = int(offset) if offset else 0
newer_than = int(newer_than) if newer_than else 0
except:
return request.error_formatter(0, 'Invalid parameter')
if artist:
parent = ClassAlias(Folder)
query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(artist)).config(distinct = True)
elif album:
query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(album)).config(distinct = True)
elif title:
query = store.find(Track, Track.title.contains_string(title))
elif anyf:
folders = store.find(Folder, Folder.name.contains_string(anyf))
tracks = store.find(Track, Track.title.contains_string(anyf))
res = list(folders[offset : offset + count])
if offset + count > folders.count():
toff = max(0, offset - folders.count())
tend = offset + count - folders.count()
res += list(tracks[toff : tend])
if artist:
parent = ClassAlias(Folder)
query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(artist)).config(distinct = True)
elif album:
query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(album)).config(distinct = True)
elif title:
query = store.find(Track, Track.title.contains_string(title))
elif anyf:
folders = store.find(Folder, Folder.name.contains_string(anyf))
tracks = store.find(Track, Track.title.contains_string(anyf))
res = list(folders[offset : offset + count])
if offset + count > folders.count():
toff = max(0, offset - folders.count())
tend = offset + count - folders.count()
res += list(tracks[toff : tend])
return request.formatter({ 'searchResult': {
'totalHits': folders.count() + tracks.count(),
'offset': offset,
'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in res ]
}})
else:
return request.error_formatter(10, 'Missing search parameter')
return request.formatter({ 'searchResult': {
'totalHits': folders.count() + tracks.count(),
'offset': offset,
'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in res ]
}})
else:
return request.error_formatter(10, 'Missing search parameter')
return request.formatter({ 'searchResult': {
'totalHits': query.count(),
'offset': offset,
'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ]
}})
return request.formatter({ 'searchResult': {
'totalHits': query.count(),
'offset': offset,
'match': [ r.as_subsonic_child(request.user) if r is Folder else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ]
}})
@app.route('/rest/search2.view', methods = [ 'GET', 'POST' ])
def new_search():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
try:
artist_count = int(artist_count) if artist_count else 20
artist_offset = int(artist_offset) if artist_offset else 0
album_count = int(album_count) if album_count else 20
album_offset = int(album_offset) if album_offset else 0
song_count = int(song_count) if song_count else 20
song_offset = int(song_offset) if song_offset else 0
except:
return request.error_formatter(0, 'Invalid parameter')
try:
artist_count = int(artist_count) if artist_count else 20
artist_offset = int(artist_offset) if artist_offset else 0
album_count = int(album_count) if album_count else 20
album_offset = int(album_offset) if album_offset else 0
song_count = int(song_count) if song_count else 20
song_offset = int(song_offset) if song_offset else 0
except:
return request.error_formatter(0, 'Invalid parameter')
if not query:
return request.error_formatter(10, 'Missing query parameter')
if not query:
return request.error_formatter(10, 'Missing query parameter')
parent = ClassAlias(Folder)
artist_query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct = True, offset = artist_offset, limit = artist_count)
album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config(distinct = True, offset = album_offset, limit = album_count)
song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count]
parent = ClassAlias(Folder)
artist_query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(query)).config(distinct = True, offset = artist_offset, limit = artist_count)
album_query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(query)).config(distinct = True, offset = album_offset, limit = album_count)
song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count]
return request.formatter({ 'searchResult2': {
'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ],
'album': [ f.as_subsonic_child(request.user) for f in album_query ],
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}})
return request.formatter({ 'searchResult2': {
'artist': [ { 'id': str(a.id), 'name': a.name } for a in artist_query ],
'album': [ f.as_subsonic_child(request.user) for f in album_query ],
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}})
@app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
def search_id3():
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
query, artist_count, artist_offset, album_count, album_offset, song_count, song_offset = map(
request.values.get, [ 'query', 'artistCount', 'artistOffset', 'albumCount', 'albumOffset', 'songCount', 'songOffset' ])
try:
artist_count = int(artist_count) if artist_count else 20
artist_offset = int(artist_offset) if artist_offset else 0
album_count = int(album_count) if album_count else 20
album_offset = int(album_offset) if album_offset else 0
song_count = int(song_count) if song_count else 20
song_offset = int(song_offset) if song_offset else 0
except:
return request.error_formatter(0, 'Invalid parameter')
try:
artist_count = int(artist_count) if artist_count else 20
artist_offset = int(artist_offset) if artist_offset else 0
album_count = int(album_count) if album_count else 20
album_offset = int(album_offset) if album_offset else 0
song_count = int(song_count) if song_count else 20
song_offset = int(song_offset) if song_offset else 0
except:
return request.error_formatter(0, 'Invalid parameter')
if not query:
return request.error_formatter(10, 'Missing query parameter')
if not query:
return request.error_formatter(10, 'Missing query parameter')
artist_query = store.find(Artist, Artist.name.contains_string(query))[artist_offset : artist_offset + artist_count]
album_query = store.find(Album, Album.name.contains_string(query))[album_offset : album_offset + album_count]
song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count]
artist_query = store.find(Artist, Artist.name.contains_string(query))[artist_offset : artist_offset + artist_count]
album_query = store.find(Album, Album.name.contains_string(query))[album_offset : album_offset + album_count]
song_query = store.find(Track, Track.title.contains_string(query))[song_offset : song_offset + song_count]
return request.formatter({ 'searchResult3': {
'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ],
'album': [ a.as_subsonic_album(request.user) for a in album_query ],
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}})
return request.formatter({ 'searchResult3': {
'artist': [ a.as_subsonic_artist(request.user) for a in artist_query ],
'album': [ a.as_subsonic_album(request.user) for a in album_query ],
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in song_query ]
}})

View File

@ -23,9 +23,9 @@ from supysonic.web import app
@app.route('/rest/ping.view', methods = [ 'GET', 'POST' ])
def ping():
return request.formatter({})
return request.formatter({})
@app.route('/rest/getLicense.view', methods = [ 'GET', 'POST' ])
def license():
return request.formatter({ 'license': { 'valid': True } })
return request.formatter({ 'license': { 'valid': True } })

View File

@ -25,70 +25,70 @@ from supysonic.managers.user import UserManager
@app.route('/rest/getUser.view', methods = [ 'GET', 'POST' ])
def user_info():
username = request.values.get('username')
if username is None:
return request.error_formatter(10, 'Missing username')
username = request.values.get('username')
if username is None:
return request.error_formatter(10, 'Missing username')
if username != request.username and not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
if username != request.username and not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
user = store.find(User, User.name == username).one()
if user is None:
return request.error_formatter(0, 'Unknown user')
user = store.find(User, User.name == username).one()
if user is None:
return request.error_formatter(0, 'Unknown user')
return request.formatter({ 'user': user.as_subsonic_user() })
return request.formatter({ 'user': user.as_subsonic_user() })
@app.route('/rest/getUsers.view', methods = [ 'GET', 'POST' ])
def users_info():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
return request.formatter({ 'users': { 'user': [ u.as_subsonic_user() for u in store.find(User) ] } })
return request.formatter({ 'users': { 'user': [ u.as_subsonic_user() for u in store.find(User) ] } })
@app.route('/rest/createUser.view', methods = [ 'GET', 'POST' ])
def user_add():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
username, password, email, admin = map(request.values.get, [ 'username', 'password', 'email', 'adminRole' ])
if not username or not password or not email:
return request.error_formatter(10, 'Missing parameter')
admin = True if admin in (True, 'True', 'true', 1, '1') else False
username, password, email, admin = map(request.values.get, [ 'username', 'password', 'email', 'adminRole' ])
if not username or not password or not email:
return request.error_formatter(10, 'Missing parameter')
admin = True if admin in (True, 'True', 'true', 1, '1') else False
status = UserManager.add(store, username, password, email, admin)
if status == UserManager.NAME_EXISTS:
return request.error_formatter(0, 'There is already a user with that username')
status = UserManager.add(store, username, password, email, admin)
if status == UserManager.NAME_EXISTS:
return request.error_formatter(0, 'There is already a user with that username')
return request.formatter({})
return request.formatter({})
@app.route('/rest/deleteUser.view', methods = [ 'GET', 'POST' ])
def user_del():
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
if not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
username = request.values.get('username')
user = store.find(User, User.name == username).one()
if not user:
return request.error_formatter(70, 'Unknown user')
username = request.values.get('username')
user = store.find(User, User.name == username).one()
if not user:
return request.error_formatter(70, 'Unknown user')
status = UserManager.delete(store, user.id)
if status != UserManager.SUCCESS:
return request.error_formatter(0, UserManager.error_str(status))
status = UserManager.delete(store, user.id)
if status != UserManager.SUCCESS:
return request.error_formatter(0, UserManager.error_str(status))
return request.formatter({})
return request.formatter({})
@app.route('/rest/changePassword.view', methods = [ 'GET', 'POST' ])
def user_changepass():
username, password = map(request.values.get, [ 'username', 'password' ])
if not username or not password:
return request.error_formatter(10, 'Missing parameter')
username, password = map(request.values.get, [ 'username', 'password' ])
if not username or not password:
return request.error_formatter(10, 'Missing parameter')
if username != request.username and not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
if username != request.username and not request.user.admin:
return request.error_formatter(50, 'Admin restricted')
status = UserManager.change_password2(store, username, password)
if status != UserManager.SUCCESS:
return request.error_formatter(0, UserManager.error_str(status))
status = UserManager.change_password2(store, username, password)
if status != UserManager.SUCCESS:
return request.error_formatter(0, UserManager.error_str(status))
return request.formatter({})
return request.formatter({})

View File

@ -30,397 +30,397 @@ import os.path
from supysonic import config
def now():
return datetime.datetime.now().replace(microsecond = 0)
return datetime.datetime.now().replace(microsecond = 0)
class UnicodeOrStrVariable(Variable):
__slots__ = ()
__slots__ = ()
def parse_set(self, value, from_db):
if isinstance(value, unicode):
return value
elif isinstance(value, str):
return unicode(value)
raise TypeError("Expected unicode, found %r: %r" % (type(value), value))
def parse_set(self, value, from_db):
if isinstance(value, unicode):
return value
elif isinstance(value, str):
return unicode(value)
raise TypeError("Expected unicode, found %r: %r" % (type(value), value))
Unicode.variable_class = UnicodeOrStrVariable
class Folder(object):
__storm_table__ = 'folder'
__storm_table__ = 'folder'
id = UUID(primary = True, default_factory = uuid.uuid4)
root = Bool(default = False)
name = Unicode()
path = Unicode() # unique
created = DateTime(default_factory = now)
has_cover_art = Bool(default = False)
last_scan = Int(default = 0)
id = UUID(primary = True, default_factory = uuid.uuid4)
root = Bool(default = False)
name = Unicode()
path = Unicode() # unique
created = DateTime(default_factory = now)
has_cover_art = Bool(default = False)
last_scan = Int(default = 0)
parent_id = UUID() # nullable
parent = Reference(parent_id, id)
children = ReferenceSet(id, parent_id)
parent_id = UUID() # nullable
parent = Reference(parent_id, id)
children = ReferenceSet(id, parent_id)
def as_subsonic_child(self, user):
info = {
'id': str(self.id),
'isDir': True,
'title': self.name,
'album': self.name,
'created': self.created.isoformat()
}
if not self.root:
info['parent'] = str(self.parent_id)
info['artist'] = self.parent.name
if self.has_cover_art:
info['coverArt'] = str(self.id)
def as_subsonic_child(self, user):
info = {
'id': str(self.id),
'isDir': True,
'title': self.name,
'album': self.name,
'created': self.created.isoformat()
}
if not self.root:
info['parent'] = str(self.parent_id)
info['artist'] = self.parent.name
if self.has_cover_art:
info['coverArt'] = str(self.id)
starred = Store.of(self).get(StarredFolder, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
starred = Store.of(self).get(StarredFolder, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
rating = Store.of(self).get(RatingFolder, (user.id, self.id))
if rating:
info['userRating'] = rating.rating
avgRating = Store.of(self).find(RatingFolder, RatingFolder.rated_id == self.id).avg(RatingFolder.rating)
if avgRating:
info['averageRating'] = avgRating
rating = Store.of(self).get(RatingFolder, (user.id, self.id))
if rating:
info['userRating'] = rating.rating
avgRating = Store.of(self).find(RatingFolder, RatingFolder.rated_id == self.id).avg(RatingFolder.rating)
if avgRating:
info['averageRating'] = avgRating
return info
return info
class Artist(object):
__storm_table__ = 'artist'
__storm_table__ = 'artist'
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode() # unique
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode() # unique
def as_subsonic_artist(self, user):
info = {
'id': str(self.id),
'name': self.name,
# coverArt
'albumCount': self.albums.count()
}
def as_subsonic_artist(self, user):
info = {
'id': str(self.id),
'name': self.name,
# coverArt
'albumCount': self.albums.count()
}
starred = Store.of(self).get(StarredArtist, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
starred = Store.of(self).get(StarredArtist, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
return info
return info
class Album(object):
__storm_table__ = 'album'
__storm_table__ = 'album'
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode()
artist_id = UUID()
artist = Reference(artist_id, Artist.id)
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode()
artist_id = UUID()
artist = Reference(artist_id, Artist.id)
def as_subsonic_album(self, user):
info = {
'id': str(self.id),
'name': self.name,
'artist': self.artist.name,
'artistId': str(self.artist_id),
'songCount': self.tracks.count(),
'duration': sum(self.tracks.values(Track.duration)),
'created': min(self.tracks.values(Track.created)).isoformat()
}
def as_subsonic_album(self, user):
info = {
'id': str(self.id),
'name': self.name,
'artist': self.artist.name,
'artistId': str(self.artist_id),
'songCount': self.tracks.count(),
'duration': sum(self.tracks.values(Track.duration)),
'created': min(self.tracks.values(Track.created)).isoformat()
}
track_with_cover = self.tracks.find(Track.folder_id == Folder.id, Folder.has_cover_art).any()
if track_with_cover:
info['coverArt'] = str(track_with_cover.folder_id)
track_with_cover = self.tracks.find(Track.folder_id == Folder.id, Folder.has_cover_art).any()
if track_with_cover:
info['coverArt'] = str(track_with_cover.folder_id)
starred = Store.of(self).get(StarredAlbum, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
starred = Store.of(self).get(StarredAlbum, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
return info
return info
def sort_key(self):
year = min(map(lambda t: t.year if t.year else 9999, self.tracks))
return '%i%s' % (year, self.name.lower())
def sort_key(self):
year = min(map(lambda t: t.year if t.year else 9999, self.tracks))
return '%i%s' % (year, self.name.lower())
Artist.albums = ReferenceSet(Artist.id, Album.artist_id)
class Track(object):
__storm_table__ = 'track'
__storm_table__ = 'track'
id = UUID(primary = True, default_factory = uuid.uuid4)
disc = Int()
number = Int()
title = Unicode()
year = Int() # nullable
genre = Unicode() # nullable
duration = Int()
album_id = UUID()
album = Reference(album_id, Album.id)
artist_id = UUID()
artist = Reference(artist_id, Artist.id)
bitrate = Int()
id = UUID(primary = True, default_factory = uuid.uuid4)
disc = Int()
number = Int()
title = Unicode()
year = Int() # nullable
genre = Unicode() # nullable
duration = Int()
album_id = UUID()
album = Reference(album_id, Album.id)
artist_id = UUID()
artist = Reference(artist_id, Artist.id)
bitrate = Int()
path = Unicode() # unique
content_type = Unicode()
created = DateTime(default_factory = now)
last_modification = Int()
path = Unicode() # unique
content_type = Unicode()
created = DateTime(default_factory = now)
last_modification = Int()
play_count = Int(default = 0)
last_play = DateTime() # nullable
play_count = Int(default = 0)
last_play = DateTime() # nullable
root_folder_id = UUID()
root_folder = Reference(root_folder_id, Folder.id)
folder_id = UUID()
folder = Reference(folder_id, Folder.id)
root_folder_id = UUID()
root_folder = Reference(root_folder_id, Folder.id)
folder_id = UUID()
folder = Reference(folder_id, Folder.id)
def as_subsonic_child(self, user, prefs):
info = {
'id': str(self.id),
'parent': str(self.folder_id),
'isDir': False,
'title': self.title,
'album': self.album.name,
'artist': self.artist.name,
'track': self.number,
'size': os.path.getsize(self.path),
'contentType': self.content_type,
'suffix': self.suffix(),
'duration': self.duration,
'bitRate': self.bitrate,
'path': self.path[len(self.root_folder.path) + 1:],
'isVideo': False,
'discNumber': self.disc,
'created': self.created.isoformat(),
'albumId': str(self.album_id),
'artistId': str(self.artist_id),
'type': 'music'
}
def as_subsonic_child(self, user, prefs):
info = {
'id': str(self.id),
'parent': str(self.folder_id),
'isDir': False,
'title': self.title,
'album': self.album.name,
'artist': self.artist.name,
'track': self.number,
'size': os.path.getsize(self.path),
'contentType': self.content_type,
'suffix': self.suffix(),
'duration': self.duration,
'bitRate': self.bitrate,
'path': self.path[len(self.root_folder.path) + 1:],
'isVideo': False,
'discNumber': self.disc,
'created': self.created.isoformat(),
'albumId': str(self.album_id),
'artistId': str(self.artist_id),
'type': 'music'
}
if self.year:
info['year'] = self.year
if self.genre:
info['genre'] = self.genre
if self.folder.has_cover_art:
info['coverArt'] = str(self.folder_id)
if self.year:
info['year'] = self.year
if self.genre:
info['genre'] = self.genre
if self.folder.has_cover_art:
info['coverArt'] = str(self.folder_id)
starred = Store.of(self).get(StarredTrack, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
starred = Store.of(self).get(StarredTrack, (user.id, self.id))
if starred:
info['starred'] = starred.date.isoformat()
rating = Store.of(self).get(RatingTrack, (user.id, self.id))
if rating:
info['userRating'] = rating.rating
avgRating = Store.of(self).find(RatingTrack, RatingTrack.rated_id == self.id).avg(RatingTrack.rating)
if avgRating:
info['averageRating'] = avgRating
rating = Store.of(self).get(RatingTrack, (user.id, self.id))
if rating:
info['userRating'] = rating.rating
avgRating = Store.of(self).find(RatingTrack, RatingTrack.rated_id == self.id).avg(RatingTrack.rating)
if avgRating:
info['averageRating'] = avgRating
if prefs and prefs.format and prefs.format != self.suffix():
info['transcodedSuffix'] = prefs.format
info['transcodedContentType'] = config.get_mime(prefs.format)
if prefs and prefs.format and prefs.format != self.suffix():
info['transcodedSuffix'] = prefs.format
info['transcodedContentType'] = config.get_mime(prefs.format)
return info
return info
def duration_str(self):
ret = '%02i:%02i' % ((self.duration % 3600) / 60, self.duration % 60)
if self.duration >= 3600:
ret = '%02i:%s' % (self.duration / 3600, ret)
return ret
def duration_str(self):
ret = '%02i:%02i' % ((self.duration % 3600) / 60, self.duration % 60)
if self.duration >= 3600:
ret = '%02i:%s' % (self.duration / 3600, ret)
return ret
def suffix(self):
return os.path.splitext(self.path)[1][1:].lower()
def suffix(self):
return os.path.splitext(self.path)[1][1:].lower()
def sort_key(self):
return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower()
def sort_key(self):
return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower()
Folder.tracks = ReferenceSet(Folder.id, Track.folder_id)
Album.tracks = ReferenceSet(Album.id, Track.album_id)
Artist.tracks = ReferenceSet(Artist.id, Track.artist_id)
class User(object):
__storm_table__ = 'user'
__storm_table__ = 'user'
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode() # unique
mail = Unicode()
password = Unicode()
salt = Unicode()
admin = Bool(default = False)
lastfm_session = Unicode() # nullable
lastfm_status = Bool(default = True) # True: ok/unlinked, False: invalid session
id = UUID(primary = True, default_factory = uuid.uuid4)
name = Unicode() # unique
mail = Unicode()
password = Unicode()
salt = Unicode()
admin = Bool(default = False)
lastfm_session = Unicode() # nullable
lastfm_status = Bool(default = True) # True: ok/unlinked, False: invalid session
last_play_id = UUID() # nullable
last_play = Reference(last_play_id, Track.id)
last_play_date = DateTime() # nullable
last_play_id = UUID() # nullable
last_play = Reference(last_play_id, Track.id)
last_play_date = DateTime() # nullable
def as_subsonic_user(self):
return {
'username': self.name,
'email': self.mail,
'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status,
'adminRole': self.admin,
'settingsRole': True,
'downloadRole': True,
'uploadRole': False,
'playlistRole': True,
'coverArtRole': False,
'commentRole': False,
'podcastRole': False,
'streamRole': True,
'jukeboxRole': False,
'shareRole': False
}
def as_subsonic_user(self):
return {
'username': self.name,
'email': self.mail,
'scrobblingEnabled': self.lastfm_session is not None and self.lastfm_status,
'adminRole': self.admin,
'settingsRole': True,
'downloadRole': True,
'uploadRole': False,
'playlistRole': True,
'coverArtRole': False,
'commentRole': False,
'podcastRole': False,
'streamRole': True,
'jukeboxRole': False,
'shareRole': False
}
class ClientPrefs(object):
__storm_table__ = 'client_prefs'
__storm_primary__ = 'user_id', 'client_name'
__storm_table__ = 'client_prefs'
__storm_primary__ = 'user_id', 'client_name'
user_id = UUID()
client_name = Unicode()
format = Unicode() # nullable
bitrate = Int() # nullable
user_id = UUID()
client_name = Unicode()
format = Unicode() # nullable
bitrate = Int() # nullable
class BaseStarred(object):
__storm_primary__ = 'user_id', 'starred_id'
__storm_primary__ = 'user_id', 'starred_id'
user_id = UUID()
starred_id = UUID()
date = DateTime(default_factory = now)
user_id = UUID()
starred_id = UUID()
date = DateTime(default_factory = now)
user = Reference(user_id, User.id)
user = Reference(user_id, User.id)
class StarredFolder(BaseStarred):
__storm_table__ = 'starred_folder'
__storm_table__ = 'starred_folder'
starred = Reference(BaseStarred.starred_id, Folder.id)
starred = Reference(BaseStarred.starred_id, Folder.id)
class StarredArtist(BaseStarred):
__storm_table__ = 'starred_artist'
__storm_table__ = 'starred_artist'
starred = Reference(BaseStarred.starred_id, Artist.id)
starred = Reference(BaseStarred.starred_id, Artist.id)
class StarredAlbum(BaseStarred):
__storm_table__ = 'starred_album'
__storm_table__ = 'starred_album'
starred = Reference(BaseStarred.starred_id, Album.id)
starred = Reference(BaseStarred.starred_id, Album.id)
class StarredTrack(BaseStarred):
__storm_table__ = 'starred_track'
__storm_table__ = 'starred_track'
starred = Reference(BaseStarred.starred_id, Track.id)
starred = Reference(BaseStarred.starred_id, Track.id)
class BaseRating(object):
__storm_primary__ = 'user_id', 'rated_id'
__storm_primary__ = 'user_id', 'rated_id'
user_id = UUID()
rated_id = UUID()
rating = Int()
user_id = UUID()
rated_id = UUID()
rating = Int()
user = Reference(user_id, User.id)
user = Reference(user_id, User.id)
class RatingFolder(BaseRating):
__storm_table__ = 'rating_folder'
__storm_table__ = 'rating_folder'
rated = Reference(BaseRating.rated_id, Folder.id)
rated = Reference(BaseRating.rated_id, Folder.id)
class RatingTrack(BaseRating):
__storm_table__ = 'rating_track'
__storm_table__ = 'rating_track'
rated = Reference(BaseRating.rated_id, Track.id)
rated = Reference(BaseRating.rated_id, Track.id)
class ChatMessage(object):
__storm_table__ = 'chat_message'
__storm_table__ = 'chat_message'
id = UUID(primary = True, default_factory = uuid.uuid4)
user_id = UUID()
time = Int(default_factory = lambda: int(time.time()))
message = Unicode()
id = UUID(primary = True, default_factory = uuid.uuid4)
user_id = UUID()
time = Int(default_factory = lambda: int(time.time()))
message = Unicode()
user = Reference(user_id, User.id)
user = Reference(user_id, User.id)
def responsize(self):
return {
'username': self.user.name,
'time': self.time * 1000,
'message': self.message
}
def responsize(self):
return {
'username': self.user.name,
'time': self.time * 1000,
'message': self.message
}
class Playlist(object):
__storm_table__ = 'playlist'
__storm_table__ = 'playlist'
id = UUID(primary = True, default_factory = uuid.uuid4)
user_id = UUID()
name = Unicode()
comment = Unicode() # nullable
public = Bool(default = False)
created = DateTime(default_factory = now)
tracks = Unicode()
id = UUID(primary = True, default_factory = uuid.uuid4)
user_id = UUID()
name = Unicode()
comment = Unicode() # nullable
public = Bool(default = False)
created = DateTime(default_factory = now)
tracks = Unicode()
user = Reference(user_id, User.id)
user = Reference(user_id, User.id)
def as_subsonic_playlist(self, user):
tracks = self.get_tracks()
info = {
'id': str(self.id),
'name': self.name if self.user_id == user.id else '[%s] %s' % (self.user.name, self.name),
'owner': self.user.name,
'public': self.public,
'songCount': len(tracks),
'duration': sum(map(lambda t: t.duration, tracks)),
'created': self.created.isoformat()
}
if self.comment:
info['comment'] = self.comment
return info
def as_subsonic_playlist(self, user):
tracks = self.get_tracks()
info = {
'id': str(self.id),
'name': self.name if self.user_id == user.id else '[%s] %s' % (self.user.name, self.name),
'owner': self.user.name,
'public': self.public,
'songCount': len(tracks),
'duration': sum(map(lambda t: t.duration, tracks)),
'created': self.created.isoformat()
}
if self.comment:
info['comment'] = self.comment
return info
def get_tracks(self):
if not self.tracks:
return []
def get_tracks(self):
if not self.tracks:
return []
tracks = []
should_fix = False
store = Store.of(self)
tracks = []
should_fix = False
store = Store.of(self)
for t in self.tracks.split(','):
try:
tid = uuid.UUID(t)
track = store.get(Track, tid)
if track:
tracks.append(track)
else:
should_fix = True
except:
should_fix = True
for t in self.tracks.split(','):
try:
tid = uuid.UUID(t)
track = store.get(Track, tid)
if track:
tracks.append(track)
else:
should_fix = True
except:
should_fix = True
if should_fix:
self.tracks = ','.join(map(lambda t: str(t.id), tracks))
store.commit()
if should_fix:
self.tracks = ','.join(map(lambda t: str(t.id), tracks))
store.commit()
return tracks
return tracks
def clear(self):
self.tracks = ""
def clear(self):
self.tracks = ""
def add(self, track):
if isinstance(track, uuid.UUID):
tid = track
elif isinstance(track, Track):
tid = track.id
elif isinstance(track, basestring):
tid = uuid.UUID(track)
def add(self, track):
if isinstance(track, uuid.UUID):
tid = track
elif isinstance(track, Track):
tid = track.id
elif isinstance(track, basestring):
tid = uuid.UUID(track)
if self.tracks and len(self.tracks) > 0:
self.tracks = "{},{}".format(self.tracks, tid)
else:
self.tracks = str(tid)
if self.tracks and len(self.tracks) > 0:
self.tracks = "{},{}".format(self.tracks, tid)
else:
self.tracks = str(tid)
def remove_at_indexes(self, indexes):
tracks = self.tracks.split(',')
for i in indexes:
if i < 0 or i >= len(tracks):
continue
tracks[i] = None
def remove_at_indexes(self, indexes):
tracks = self.tracks.split(',')
for i in indexes:
if i < 0 or i >= len(tracks):
continue
tracks[i] = None
self.tracks = ','.join(t for t in tracks if t)
self.tracks = ','.join(t for t in tracks if t)
def get_store(database_uri):
database = create_database(database_uri)
store = Store(database)
return store
database = create_database(database_uri)
store = Store(database)
return store

View File

@ -30,76 +30,76 @@ from supysonic.managers.folder import FolderManager
@app.before_request
def check_admin():
if not request.path.startswith('/folder'):
return
if not request.path.startswith('/folder'):
return
if not UserManager.get(store, session.get('userid'))[1].admin:
return redirect(url_for('index'))
if not UserManager.get(store, session.get('userid'))[1].admin:
return redirect(url_for('index'))
@app.route('/folder')
def folder_index():
return render_template('folders.html', folders = store.find(Folder, Folder.root == True), admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('folders.html', folders = store.find(Folder, Folder.root == True), admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/folder/add', methods = [ 'GET', 'POST' ])
def add_folder():
if request.method == 'GET':
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
if request.method == 'GET':
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
error = False
(name, path) = map(request.form.get, [ 'name', 'path' ])
if name in (None, ''):
flash('The name is required.')
error = True
if path in (None, ''):
flash('The path is required.')
error = True
if error:
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
error = False
(name, path) = map(request.form.get, [ 'name', 'path' ])
if name in (None, ''):
flash('The name is required.')
error = True
if path in (None, ''):
flash('The path is required.')
error = True
if error:
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
ret = FolderManager.add(store, name, path)
if ret != FolderManager.SUCCESS:
flash(FolderManager.error_str(ret))
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
ret = FolderManager.add(store, name, path)
if ret != FolderManager.SUCCESS:
flash(FolderManager.error_str(ret))
return render_template('addfolder.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
flash("Folder '%s' created. You should now run a scan" % name)
flash("Folder '%s' created. You should now run a scan" % name)
return redirect(url_for('folder_index'))
return redirect(url_for('folder_index'))
@app.route('/folder/del/<id>')
def del_folder(id):
try:
idid = uuid.UUID(id)
except ValueError:
flash('Invalid folder id')
return redirect(url_for('folder_index'))
try:
idid = uuid.UUID(id)
except ValueError:
flash('Invalid folder id')
return redirect(url_for('folder_index'))
ret = FolderManager.delete(store, idid)
if ret != FolderManager.SUCCESS:
flash(FolderManager.error_str(ret))
else:
flash('Deleted folder')
ret = FolderManager.delete(store, idid)
if ret != FolderManager.SUCCESS:
flash(FolderManager.error_str(ret))
else:
flash('Deleted folder')
return redirect(url_for('folder_index'))
return redirect(url_for('folder_index'))
@app.route('/folder/scan')
@app.route('/folder/scan/<id>')
def scan_folder(id = None):
scanner = Scanner(store)
if id is None:
for folder in store.find(Folder, Folder.root == True):
scanner.scan(folder)
else:
status, folder = FolderManager.get(store, id)
if status != FolderManager.SUCCESS:
flash(FolderManager.error_str(status))
return redirect(url_for('folder_index'))
scanner.scan(folder)
scanner = Scanner(store)
if id is None:
for folder in store.find(Folder, Folder.root == True):
scanner.scan(folder)
else:
status, folder = FolderManager.get(store, id)
if status != FolderManager.SUCCESS:
flash(FolderManager.error_str(status))
return redirect(url_for('folder_index'))
scanner.scan(folder)
scanner.finish()
added, deleted = scanner.stats()
store.commit()
scanner.finish()
added, deleted = scanner.stats()
store.commit()
flash('Added: %i artists, %i albums, %i tracks' % (added[0], added[1], added[2]))
flash('Deleted: %i artists, %i albums, %i tracks' % (deleted[0], deleted[1], deleted[2]))
return redirect(url_for('folder_index'))
flash('Added: %i artists, %i albums, %i tracks' % (added[0], added[1], added[2]))
flash('Deleted: %i artists, %i albums, %i tracks' % (deleted[0], deleted[1], deleted[2]))
return redirect(url_for('folder_index'))

View File

@ -26,67 +26,67 @@ from supysonic.managers.user import UserManager
@app.route('/playlist')
def playlist_index():
return render_template('playlists.html', mine = store.find(Playlist, Playlist.user_id == uuid.UUID(session.get('userid'))),
others = store.find(Playlist, Playlist.user_id != uuid.UUID(session.get('userid')), Playlist.public == True),
admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('playlists.html', mine = store.find(Playlist, Playlist.user_id == uuid.UUID(session.get('userid'))),
others = store.find(Playlist, Playlist.user_id != uuid.UUID(session.get('userid')), Playlist.public == True),
admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/playlist/<uid>')
def playlist_details(uid):
try:
uid = uuid.UUID(uid) if type(uid) in (str, unicode) else uid
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
try:
uid = uuid.UUID(uid) if type(uid) in (str, unicode) else uid
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
return render_template('playlist.html', playlist = playlist, admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('playlist.html', playlist = playlist, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/playlist/<uid>', methods = [ 'POST' ])
def playlist_update(uid):
try:
uid = uuid.UUID(uid)
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
try:
uid = uuid.UUID(uid)
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
return redirect(url_for('playlist_index'))
if str(playlist.user_id) != session.get('userid'):
flash("You're not allowed to edit this playlist")
elif not request.form.get('name'):
flash('Missing playlist name')
else:
playlist.name = request.form.get('name')
playlist.public = request.form.get('public') in (True, 'True', 1, '1', 'on', 'checked')
store.commit()
flash('Playlist updated.')
if str(playlist.user_id) != session.get('userid'):
flash("You're not allowed to edit this playlist")
elif not request.form.get('name'):
flash('Missing playlist name')
else:
playlist.name = request.form.get('name')
playlist.public = request.form.get('public') in (True, 'True', 1, '1', 'on', 'checked')
store.commit()
flash('Playlist updated.')
return playlist_details(uid)
return playlist_details(uid)
@app.route('/playlist/del/<uid>')
def playlist_delete(uid):
try:
uid = uuid.UUID(uid)
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
try:
uid = uuid.UUID(uid)
except:
flash('Invalid playlist id')
return redirect(url_for('playlist_index'))
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
elif str(playlist.user_id) != session.get('userid'):
flash("You're not allowed to delete this playlist")
else:
store.remove(playlist)
store.commit()
flash('Playlist deleted')
playlist = store.get(Playlist, uid)
if not playlist:
flash('Unknown playlist')
elif str(playlist.user_id) != session.get('userid'):
flash("You're not allowed to delete this playlist")
else:
store.remove(playlist)
store.commit()
flash('Playlist deleted')
return redirect(url_for('playlist_index'))
return redirect(url_for('playlist_index'))

View File

@ -29,53 +29,53 @@ from supysonic.lastfm import LastFm
@app.before_request
def check_admin():
if not request.path.startswith('/user'):
return
if not request.path.startswith('/user'):
return
if request.endpoint in ('user_index', 'add_user', 'del_user', 'export_users', 'import_users', 'do_user_import') and not UserManager.get(store, session.get('userid'))[1].admin:
return redirect(url_for('index'))
if request.endpoint in ('user_index', 'add_user', 'del_user', 'export_users', 'import_users', 'do_user_import') and not UserManager.get(store, session.get('userid'))[1].admin:
return redirect(url_for('index'))
@app.route('/user')
def user_index():
return render_template('users.html', users = store.find(User), admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('users.html', users = store.find(User), admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/<uid>')
def user_profile(uid):
if uid == 'me':
prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid')))
return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(uid))
return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin)
if uid == 'me':
prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(session.get('userid')))
return render_template('profile.html', user = UserManager.get(store, session.get('userid'))[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
prefs = store.find(ClientPrefs, ClientPrefs.user_id == uuid.UUID(uid))
return render_template('profile.html', user = UserManager.get(store, uid)[1], api_key = config.get('lastfm', 'api_key'), clients = prefs, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/<uid>', methods = [ 'POST' ])
def update_clients(uid):
clients_opts = {}
for client in set(map(lambda k: k.rsplit('_', 1)[0], request.form.keys())):
clients_opts[client] = { k.rsplit('_', 1)[1]: v for k, v in filter(lambda (k, v): k.startswith(client), request.form.iteritems()) }
app.logger.debug(clients_opts)
clients_opts = {}
for client in set(map(lambda k: k.rsplit('_', 1)[0], request.form.keys())):
clients_opts[client] = { k.rsplit('_', 1)[1]: v for k, v in filter(lambda (k, v): k.startswith(client), request.form.iteritems()) }
app.logger.debug(clients_opts)
if uid == 'me':
userid = uuid.UUID(session.get('userid'))
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
userid = uuid.UUID(uid)
if uid == 'me':
userid = uuid.UUID(session.get('userid'))
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
userid = uuid.UUID(uid)
for client, opts in clients_opts.iteritems():
prefs = store.get(ClientPrefs, (userid, client))
if 'delete' in opts and opts['delete'] in [ 'on', 'true', 'checked', 'selected', '1' ]:
store.remove(prefs)
continue
for client, opts in clients_opts.iteritems():
prefs = store.get(ClientPrefs, (userid, client))
if 'delete' in opts and opts['delete'] in [ 'on', 'true', 'checked', 'selected', '1' ]:
store.remove(prefs)
continue
prefs.format = opts['format'] if 'format' in opts and opts['format'] else None
prefs.bitrate = int(opts['bitrate']) if 'bitrate' in opts and opts['bitrate'] else None
prefs.format = opts['format'] if 'format' in opts and opts['format'] else None
prefs.bitrate = int(opts['bitrate']) if 'bitrate' in opts and opts['bitrate'] else None
store.commit()
flash('Clients preferences updated.')
return user_profile(uid)
store.commit()
flash('Clients preferences updated.')
return user_profile(uid)
@app.route('/user/<uid>/changeusername', methods = [ 'GET', 'POST' ])
def change_username(uid):
@ -106,209 +106,209 @@ def change_username(uid):
@app.route('/user/<uid>/changemail', methods = [ 'GET', 'POST' ])
def change_mail(uid):
if uid == 'me':
user = UserManager.get(store, session.get('userid'))[1]
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
user = UserManager.get(store, uid)[1]
if request.method == 'POST':
mail = request.form.get('mail')
# No validation, lol.
user.mail = mail
store.commit()
return redirect(url_for('user_profile', uid = uid))
if uid == 'me':
user = UserManager.get(store, session.get('userid'))[1]
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
user = UserManager.get(store, uid)[1]
if request.method == 'POST':
mail = request.form.get('mail')
# No validation, lol.
user.mail = mail
store.commit()
return redirect(url_for('user_profile', uid = uid))
return render_template('change_mail.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('change_mail.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/<uid>/changepass', methods = [ 'GET', 'POST' ])
def change_password(uid):
if uid == 'me':
user = UserManager.get(store, session.get('userid'))[1].name
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
user = UserManager.get(store, uid)[1].name
if request.method == 'POST':
error = False
if uid == 'me' or uid == session.get('userid'):
current, new, confirm = map(request.form.get, [ 'current', 'new', 'confirm' ])
if current in ('', None):
flash('The current password is required')
error = True
else:
new, confirm = map(request.form.get, [ 'new', 'confirm' ])
if new in ('', None):
flash('The new password is required')
error = True
if new != confirm:
flash("The new password and its confirmation don't match")
error = True
if uid == 'me':
user = UserManager.get(store, session.get('userid'))[1].name
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
user = UserManager.get(store, uid)[1].name
if request.method == 'POST':
error = False
if uid == 'me' or uid == session.get('userid'):
current, new, confirm = map(request.form.get, [ 'current', 'new', 'confirm' ])
if current in ('', None):
flash('The current password is required')
error = True
else:
new, confirm = map(request.form.get, [ 'new', 'confirm' ])
if new in ('', None):
flash('The new password is required')
error = True
if new != confirm:
flash("The new password and its confirmation don't match")
error = True
if not error:
if uid == 'me' or uid == session.get('userid'):
status = UserManager.change_password(store, session.get('userid'), current, new)
else:
status = UserManager.change_password2(store, UserManager.get(store, uid)[1].name, new)
if status != UserManager.SUCCESS:
flash(UserManager.error_str(status))
else:
flash('Password changed')
return redirect(url_for('user_profile', uid = uid))
if not error:
if uid == 'me' or uid == session.get('userid'):
status = UserManager.change_password(store, session.get('userid'), current, new)
else:
status = UserManager.change_password2(store, UserManager.get(store, uid)[1].name, new)
if status != UserManager.SUCCESS:
flash(UserManager.error_str(status))
else:
flash('Password changed')
return redirect(url_for('user_profile', uid = uid))
return render_template('change_pass.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('change_pass.html', user = user, admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/add', methods = [ 'GET', 'POST' ])
def add_user():
if request.method == 'GET':
return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
if request.method == 'GET':
return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
error = False
(name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'user', 'passwd', 'passwd_confirm', 'mail', 'admin' ])
if name in (None, ''):
flash('The name is required.')
error = True
if passwd in (None, ''):
flash('Please provide a password.')
error = True
elif passwd != passwd_confirm:
flash("The passwords don't match.")
error = True
error = False
(name, passwd, passwd_confirm, mail, admin) = map(request.form.get, [ 'user', 'passwd', 'passwd_confirm', 'mail', 'admin' ])
if name in (None, ''):
flash('The name is required.')
error = True
if passwd in (None, ''):
flash('Please provide a password.')
error = True
elif passwd != passwd_confirm:
flash("The passwords don't match.")
error = True
if admin is None:
admin = True if store.find(User, User.admin == True).count() == 0 else False
else:
admin = True
if admin is None:
admin = True if store.find(User, User.admin == True).count() == 0 else False
else:
admin = True
if not error:
status = UserManager.add(store, name, passwd, mail, admin)
if status == UserManager.SUCCESS:
flash("User '%s' successfully added" % name)
return redirect(url_for('user_index'))
else:
flash(UserManager.error_str(status))
if not error:
status = UserManager.add(store, name, passwd, mail, admin)
if status == UserManager.SUCCESS:
flash("User '%s' successfully added" % name)
return redirect(url_for('user_index'))
else:
flash(UserManager.error_str(status))
return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('adduser.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/del/<uid>')
def del_user(uid):
status = UserManager.delete(store, uid)
if status == UserManager.SUCCESS:
flash('Deleted user')
else:
flash(UserManager.error_str(status))
status = UserManager.delete(store, uid)
if status == UserManager.SUCCESS:
flash('Deleted user')
else:
flash(UserManager.error_str(status))
return redirect(url_for('user_index'))
return redirect(url_for('user_index'))
@app.route('/user/export')
def export_users():
resp = make_response('\n'.join([ '%s,%s,%s,%s,"%s",%s,%s,%s' % (u.id, u.name, u.mail, u.password, u.salt, u.admin, u.lastfm_session, u.lastfm_status)
for u in store.find(User) ]))
resp.headers['Content-disposition'] = 'attachment;filename=users.csv'
resp.headers['Content-type'] = 'text/csv'
return resp
resp = make_response('\n'.join([ '%s,%s,%s,%s,"%s",%s,%s,%s' % (u.id, u.name, u.mail, u.password, u.salt, u.admin, u.lastfm_session, u.lastfm_status)
for u in store.find(User) ]))
resp.headers['Content-disposition'] = 'attachment;filename=users.csv'
resp.headers['Content-type'] = 'text/csv'
return resp
@app.route('/user/import')
def import_users():
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
@app.route('/user/import', methods = [ 'POST' ])
def do_user_import():
if not request.files['file']:
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
if not request.files['file']:
return render_template('importusers.html', admin = UserManager.get(store, session.get('userid'))[1].admin)
users = []
reader = csv.reader(request.files['file'])
for id, name, mail, password, salt, admin, lfmsess, lfmstatus in reader:
mail = None if mail == 'None' else mail
admin = admin == 'True'
lfmsess = None if lfmsess == 'None' else lfmsess
lfmstatus = lfmstatus == 'True'
users = []
reader = csv.reader(request.files['file'])
for id, name, mail, password, salt, admin, lfmsess, lfmstatus in reader:
mail = None if mail == 'None' else mail
admin = admin == 'True'
lfmsess = None if lfmsess == 'None' else lfmsess
lfmstatus = lfmstatus == 'True'
user = User()
user.id = uuid.UUID(id)
user.name = name
user.password = password
user.salt = salt
user.admin = admin
user.lastfm_session = lfmsess
user.lastfm_status = lfmstatus
user = User()
user.id = uuid.UUID(id)
user.name = name
user.password = password
user.salt = salt
user.admin = admin
user.lastfm_session = lfmsess
user.lastfm_status = lfmstatus
users.append(user)
users.append(user)
store.find(User).remove()
for u in users:
store.add(u)
store.commit()
store.find(User).remove()
for u in users:
store.add(u)
store.commit()
return redirect(url_for('user_index'))
return redirect(url_for('user_index'))
@app.route('/user/<uid>/lastfm/link')
def lastfm_reg(uid):
token = request.args.get('token')
if token in ('', None):
flash('Missing LastFM auth token')
return redirect(url_for('user_profile', uid = uid))
token = request.args.get('token')
if token in ('', None):
flash('Missing LastFM auth token')
return redirect(url_for('user_profile', uid = uid))
if uid == 'me':
lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
lfm = LastFm(UserManager.get(store, uid)[1], app.logger)
status, error = lfm.link_account(token)
store.commit()
flash(error if not status else 'Successfully linked LastFM account')
if uid == 'me':
lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
lfm = LastFm(UserManager.get(store, uid)[1], app.logger)
status, error = lfm.link_account(token)
store.commit()
flash(error if not status else 'Successfully linked LastFM account')
return redirect(url_for('user_profile', uid = uid))
return redirect(url_for('user_profile', uid = uid))
@app.route('/user/<uid>/lastfm/unlink')
def lastfm_unreg(uid):
if uid == 'me':
lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
lfm = LastFm(UserManager.get(store, uid)[1], app.logger)
lfm.unlink_account()
store.commit()
flash('Unliked LastFM account')
return redirect(url_for('user_profile', uid = uid))
if uid == 'me':
lfm = LastFm(UserManager.get(store, session.get('userid'))[1], app.logger)
else:
if not UserManager.get(store, session.get('userid'))[1].admin or not UserManager.get(store, uid)[0] is UserManager.SUCCESS:
return redirect(url_for('index'))
lfm = LastFm(UserManager.get(store, uid)[1], app.logger)
lfm.unlink_account()
store.commit()
flash('Unliked LastFM account')
return redirect(url_for('user_profile', uid = uid))
@app.route('/user/login', methods = [ 'GET', 'POST'])
def login():
return_url = request.args.get('returnUrl') or url_for('index')
if session.get('userid'):
flash('Already logged in')
return redirect(return_url)
return_url = request.args.get('returnUrl') or url_for('index')
if session.get('userid'):
flash('Already logged in')
return redirect(return_url)
if request.method == 'GET':
return render_template('login.html')
if request.method == 'GET':
return render_template('login.html')
name, password = map(request.form.get, [ 'user', 'password' ])
error = False
if name in ('', None):
flash('Missing user name')
error = True
if password in ('', None):
flash('Missing password')
error = True
name, password = map(request.form.get, [ 'user', 'password' ])
error = False
if name in ('', None):
flash('Missing user name')
error = True
if password in ('', None):
flash('Missing password')
error = True
if not error:
status, user = UserManager.try_auth(store, name, password)
if status == UserManager.SUCCESS:
session['userid'] = str(user.id)
session['username'] = user.name
flash('Logged in!')
return redirect(return_url)
else:
flash(UserManager.error_str(status))
if not error:
status, user = UserManager.try_auth(store, name, password)
if status == UserManager.SUCCESS:
session['userid'] = str(user.id)
session['username'] = user.name
flash('Logged in!')
return redirect(return_url)
else:
flash(UserManager.error_str(status))
return render_template('login.html')
return render_template('login.html')
@app.route('/user/logout')
def logout():
session.clear()
flash('Logged out!')
return redirect(url_for('login'))
session.clear()
flash('Logged out!')
return redirect(url_for('login'))

View File

@ -22,81 +22,81 @@ import requests, hashlib
from supysonic import config
class LastFm:
def __init__(self, user, logger):
self.__user = user
self.__api_key = config.get('lastfm', 'api_key')
self.__api_secret = config.get('lastfm', 'secret')
self.__enabled = self.__api_key is not None and self.__api_secret is not None
self.__logger = logger
def __init__(self, user, logger):
self.__user = user
self.__api_key = config.get('lastfm', 'api_key')
self.__api_secret = config.get('lastfm', 'secret')
self.__enabled = self.__api_key is not None and self.__api_secret is not None
self.__logger = logger
def link_account(self, token):
if not self.__enabled:
return False, 'No API key set'
def link_account(self, token):
if not self.__enabled:
return False, 'No API key set'
res = self.__api_request(False, method = 'auth.getSession', token = token)
if not res:
return False, 'Error connecting to LastFM'
elif 'error' in res:
return False, 'Error %i: %s' % (res['error'], res['message'])
else:
self.__user.lastfm_session = res['session']['key']
self.__user.lastfm_status = True
return True, 'OK'
res = self.__api_request(False, method = 'auth.getSession', token = token)
if not res:
return False, 'Error connecting to LastFM'
elif 'error' in res:
return False, 'Error %i: %s' % (res['error'], res['message'])
else:
self.__user.lastfm_session = res['session']['key']
self.__user.lastfm_status = True
return True, 'OK'
def unlink_account(self):
self.__user.lastfm_session = None
self.__user.lastfm_status = True
def unlink_account(self):
self.__user.lastfm_session = None
self.__user.lastfm_status = True
def now_playing(self, track):
if not self.__enabled:
return
def now_playing(self, track):
if not self.__enabled:
return
self.__api_request(True, method = 'track.updateNowPlaying', artist = track.album.artist.name, track = track.title, album = track.album.name,
trackNumber = track.number, duration = track.duration)
self.__api_request(True, method = 'track.updateNowPlaying', artist = track.album.artist.name, track = track.title, album = track.album.name,
trackNumber = track.number, duration = track.duration)
def scrobble(self, track, ts):
if not self.__enabled:
return
def scrobble(self, track, ts):
if not self.__enabled:
return
self.__api_request(True, method = 'track.scrobble', artist = track.album.artist.name, track = track.title, album = track.album.name,
timestamp = ts, trackNumber = track.number, duration = track.duration)
self.__api_request(True, method = 'track.scrobble', artist = track.album.artist.name, track = track.title, album = track.album.name,
timestamp = ts, trackNumber = track.number, duration = track.duration)
def __api_request(self, write, **kwargs):
if not self.__enabled:
return
def __api_request(self, write, **kwargs):
if not self.__enabled:
return
if write:
if not self.__user.lastfm_session or not self.__user.lastfm_status:
return
kwargs['sk'] = self.__user.lastfm_session
if write:
if not self.__user.lastfm_session or not self.__user.lastfm_status:
return
kwargs['sk'] = self.__user.lastfm_session
kwargs['api_key'] = self.__api_key
kwargs['api_key'] = self.__api_key
sig_str = ''
for k, v in sorted(kwargs.iteritems()):
if type(v) is unicode:
sig_str += k + v.encode('utf-8')
else:
sig_str += k + str(v)
sig = hashlib.md5(sig_str + self.__api_secret).hexdigest()
sig_str = ''
for k, v in sorted(kwargs.iteritems()):
if type(v) is unicode:
sig_str += k + v.encode('utf-8')
else:
sig_str += k + str(v)
sig = hashlib.md5(sig_str + self.__api_secret).hexdigest()
kwargs['api_sig'] = sig
kwargs['format'] = 'json'
kwargs['api_sig'] = sig
kwargs['format'] = 'json'
try:
if write:
r = requests.post('http://ws.audioscrobbler.com/2.0/', data = kwargs)
else:
r = requests.get('http://ws.audioscrobbler.com/2.0/', params = kwargs)
except requests.exceptions.RequestException, e:
self.__logger.warn('Error while connecting to LastFM: ' + str(e))
return None
try:
if write:
r = requests.post('http://ws.audioscrobbler.com/2.0/', data = kwargs)
else:
r = requests.get('http://ws.audioscrobbler.com/2.0/', params = kwargs)
except requests.exceptions.RequestException, e:
self.__logger.warn('Error while connecting to LastFM: ' + str(e))
return None
json = r.json()
if 'error' in json:
if json['error'] in (9, '9'):
self.__user.lastfm_status = False
self.__logger.warn('LastFM error %i: %s' % (json['error'], json['message']))
json = r.json()
if 'error' in json:
if json['error'] in (9, '9'):
self.__user.lastfm_status = False
self.__logger.warn('LastFM error %i: %s' % (json['error'], json['message']))
return json
return json

View File

@ -23,101 +23,101 @@ from supysonic.db import Folder, Artist, Album, Track, StarredFolder, RatingFold
from supysonic.scanner import Scanner
class FolderManager:
SUCCESS = 0
INVALID_ID = 1
NAME_EXISTS = 2
INVALID_PATH = 3
PATH_EXISTS = 4
NO_SUCH_FOLDER = 5
SUBPATH_EXISTS = 6
SUCCESS = 0
INVALID_ID = 1
NAME_EXISTS = 2
INVALID_PATH = 3
PATH_EXISTS = 4
NO_SUCH_FOLDER = 5
SUBPATH_EXISTS = 6
@staticmethod
def get(store, uid):
if isinstance(uid, basestring):
try:
uid = uuid.UUID(uid)
except:
return FolderManager.INVALID_ID, None
elif type(uid) is uuid.UUID:
pass
else:
return FolderManager.INVALID_ID, None
@staticmethod
def get(store, uid):
if isinstance(uid, basestring):
try:
uid = uuid.UUID(uid)
except:
return FolderManager.INVALID_ID, None
elif type(uid) is uuid.UUID:
pass
else:
return FolderManager.INVALID_ID, None
folder = store.get(Folder, uid)
if not folder:
return FolderManager.NO_SUCH_FOLDER, None
folder = store.get(Folder, uid)
if not folder:
return FolderManager.NO_SUCH_FOLDER, None
return FolderManager.SUCCESS, folder
return FolderManager.SUCCESS, folder
@staticmethod
def add(store, name, path):
if not store.find(Folder, Folder.name == name, Folder.root == True).is_empty():
return FolderManager.NAME_EXISTS
@staticmethod
def add(store, name, path):
if not store.find(Folder, Folder.name == name, Folder.root == True).is_empty():
return FolderManager.NAME_EXISTS
path = unicode(os.path.abspath(path))
if not os.path.isdir(path):
return FolderManager.INVALID_PATH
if not store.find(Folder, Folder.path == path).is_empty():
return FolderManager.PATH_EXISTS
if any(path.startswith(p) for p in store.find(Folder).values(Folder.path)):
return FolderManager.PATH_EXISTS
if not store.find(Folder, Folder.path.startswith(path)).is_empty():
return FolderManager.SUBPATH_EXISTS
path = unicode(os.path.abspath(path))
if not os.path.isdir(path):
return FolderManager.INVALID_PATH
if not store.find(Folder, Folder.path == path).is_empty():
return FolderManager.PATH_EXISTS
if any(path.startswith(p) for p in store.find(Folder).values(Folder.path)):
return FolderManager.PATH_EXISTS
if not store.find(Folder, Folder.path.startswith(path)).is_empty():
return FolderManager.SUBPATH_EXISTS
folder = Folder()
folder.root = True
folder.name = name
folder.path = path
folder = Folder()
folder.root = True
folder.name = name
folder.path = path
store.add(folder)
store.commit()
store.add(folder)
store.commit()
return FolderManager.SUCCESS
return FolderManager.SUCCESS
@staticmethod
def delete(store, uid):
status, folder = FolderManager.get(store, uid)
if status != FolderManager.SUCCESS:
return status
@staticmethod
def delete(store, uid):
status, folder = FolderManager.get(store, uid)
if status != FolderManager.SUCCESS:
return status
if not folder.root:
return FolderManager.NO_SUCH_FOLDER
if not folder.root:
return FolderManager.NO_SUCH_FOLDER
scanner = Scanner(store)
for track in store.find(Track, Track.root_folder_id == folder.id):
scanner.remove_file(track.path)
scanner.finish()
scanner = Scanner(store)
for track in store.find(Track, Track.root_folder_id == folder.id):
scanner.remove_file(track.path)
scanner.finish()
store.find(StarredFolder, StarredFolder.starred_id == uid).remove()
store.find(RatingFolder, RatingFolder.rated_id == uid).remove()
store.find(StarredFolder, StarredFolder.starred_id == uid).remove()
store.find(RatingFolder, RatingFolder.rated_id == uid).remove()
store.remove(folder)
store.commit()
store.remove(folder)
store.commit()
return FolderManager.SUCCESS
return FolderManager.SUCCESS
@staticmethod
def delete_by_name(store, name):
folder = store.find(Folder, Folder.name == name, Folder.root == True).one()
if not folder:
return FolderManager.NO_SUCH_FOLDER
return FolderManager.delete(store, folder.id)
@staticmethod
def delete_by_name(store, name):
folder = store.find(Folder, Folder.name == name, Folder.root == True).one()
if not folder:
return FolderManager.NO_SUCH_FOLDER
return FolderManager.delete(store, folder.id)
@staticmethod
def error_str(err):
if err == FolderManager.SUCCESS:
return 'No error'
elif err == FolderManager.INVALID_ID:
return 'Invalid folder id'
elif err == FolderManager.NAME_EXISTS:
return 'There is already a folder with that name. Please pick another one.'
elif err == FolderManager.INVALID_PATH:
return "The path doesn't exists or isn't a directory"
elif err == FolderManager.PATH_EXISTS:
return 'This path is already registered'
elif err == FolderManager.NO_SUCH_FOLDER:
return 'No such folder'
elif err == FolderManager.SUBPATH_EXISTS:
return 'This path contains a folder that is already registered'
return 'Unknown error'
@staticmethod
def error_str(err):
if err == FolderManager.SUCCESS:
return 'No error'
elif err == FolderManager.INVALID_ID:
return 'Invalid folder id'
elif err == FolderManager.NAME_EXISTS:
return 'There is already a folder with that name. Please pick another one.'
elif err == FolderManager.INVALID_PATH:
return "The path doesn't exists or isn't a directory"
elif err == FolderManager.PATH_EXISTS:
return 'This path is already registered'
elif err == FolderManager.NO_SUCH_FOLDER:
return 'No such folder'
elif err == FolderManager.SUBPATH_EXISTS:
return 'This path contains a folder that is already registered'
return 'Unknown error'

View File

@ -32,288 +32,288 @@ from supysonic.db import RatingFolder, RatingTrack
# Hacking in support for a concatenation expression
class Concat(ComparableExpr):
__slots__ = ("left", "right", "db")
__slots__ = ("left", "right", "db")
def __init__(self, left, right, db):
self.left = left
self.right = right
self.db = db
def __init__(self, left, right, db):
self.left = left
self.right = right
self.db = db
@compile.when(Concat)
def compile_concat(compile, concat, state):
left = compile(concat.left, state)
right = compile(concat.right, state)
if concat.db in ('sqlite', 'postgres'):
statement = "%s||%s"
elif concat.db == 'mysql':
statement = "CONCAT(%s, %s)"
else:
raise NotSupportedError("Unspported database (%s)" % concat.db)
return statement % (left, right)
left = compile(concat.left, state)
right = compile(concat.right, state)
if concat.db in ('sqlite', 'postgres'):
statement = "%s||%s"
elif concat.db == 'mysql':
statement = "CONCAT(%s, %s)"
else:
raise NotSupportedError("Unspported database (%s)" % concat.db)
return statement % (left, right)
class Scanner:
def __init__(self, store, force = False):
self.__store = store
self.__force = force
def __init__(self, store, force = False):
self.__store = store
self.__force = force
self.__added_artists = 0
self.__added_albums = 0
self.__added_tracks = 0
self.__deleted_artists = 0
self.__deleted_albums = 0
self.__deleted_tracks = 0
self.__added_artists = 0
self.__added_albums = 0
self.__added_tracks = 0
self.__deleted_artists = 0
self.__deleted_albums = 0
self.__deleted_tracks = 0
extensions = config.get('base', 'scanner_extensions')
self.__extensions = map(str.lower, extensions.split()) if extensions else None
extensions = config.get('base', 'scanner_extensions')
self.__extensions = map(str.lower, extensions.split()) if extensions else None
self.__folders_to_check = set()
self.__artists_to_check = set()
self.__albums_to_check = set()
self.__folders_to_check = set()
self.__artists_to_check = set()
self.__albums_to_check = set()
def __del__(self):
if self.__folders_to_check or self.__artists_to_check or self.__albums_to_check:
raise Exception("There's still something to check. Did you run Scanner.finish()?")
def __del__(self):
if self.__folders_to_check or self.__artists_to_check or self.__albums_to_check:
raise Exception("There's still something to check. Did you run Scanner.finish()?")
def scan(self, folder, progress_callback = None):
# Scan new/updated files
files = [ os.path.join(root, f) for root, _, fs in os.walk(folder.path) for f in fs if self.__is_valid_path(os.path.join(root, f)) ]
total = len(files)
current = 0
def scan(self, folder, progress_callback = None):
# Scan new/updated files
files = [ os.path.join(root, f) for root, _, fs in os.walk(folder.path) for f in fs if self.__is_valid_path(os.path.join(root, f)) ]
total = len(files)
current = 0
for path in files:
self.scan_file(path)
current += 1
if progress_callback:
progress_callback(current, total)
for path in files:
self.scan_file(path)
current += 1
if progress_callback:
progress_callback(current, total)
# Remove files that have been deleted
for track in [ t for t in self.__store.find(Track, Track.root_folder_id == folder.id) if not self.__is_valid_path(t.path) ]:
self.remove_file(track.path)
# Remove files that have been deleted
for track in [ t for t in self.__store.find(Track, Track.root_folder_id == folder.id) if not self.__is_valid_path(t.path) ]:
self.remove_file(track.path)
# Update cover art info
folders = [ folder ]
while folders:
f = folders.pop()
f.has_cover_art = os.path.isfile(os.path.join(f.path, 'cover.jpg'))
folders += f.children
# Update cover art info
folders = [ folder ]
while folders:
f = folders.pop()
f.has_cover_art = os.path.isfile(os.path.join(f.path, 'cover.jpg'))
folders += f.children
folder.last_scan = int(time.time())
folder.last_scan = int(time.time())
def finish(self):
for album in [ a for a in self.__albums_to_check if not a.tracks.count() ]:
self.__store.find(StarredAlbum, StarredAlbum.starred_id == album.id).remove()
def finish(self):
for album in [ a for a in self.__albums_to_check if not a.tracks.count() ]:
self.__store.find(StarredAlbum, StarredAlbum.starred_id == album.id).remove()
self.__artists_to_check.add(album.artist)
self.__store.remove(album)
self.__deleted_albums += 1
self.__albums_to_check.clear()
self.__artists_to_check.add(album.artist)
self.__store.remove(album)
self.__deleted_albums += 1
self.__albums_to_check.clear()
for artist in [ a for a in self.__artists_to_check if not a.albums.count() and not a.tracks.count() ]:
self.__store.find(StarredArtist, StarredArtist.starred_id == artist.id).remove()
for artist in [ a for a in self.__artists_to_check if not a.albums.count() and not a.tracks.count() ]:
self.__store.find(StarredArtist, StarredArtist.starred_id == artist.id).remove()
self.__store.remove(artist)
self.__deleted_artists += 1
self.__artists_to_check.clear()
self.__store.remove(artist)
self.__deleted_artists += 1
self.__artists_to_check.clear()
while self.__folders_to_check:
folder = self.__folders_to_check.pop()
if folder.root:
continue
while self.__folders_to_check:
folder = self.__folders_to_check.pop()
if folder.root:
continue
if not folder.tracks.count() and not folder.children.count():
self.__store.find(StarredFolder, StarredFolder.starred_id == folder.id).remove()
self.__store.find(RatingFolder, RatingFolder.rated_id == folder.id).remove()
if not folder.tracks.count() and not folder.children.count():
self.__store.find(StarredFolder, StarredFolder.starred_id == folder.id).remove()
self.__store.find(RatingFolder, RatingFolder.rated_id == folder.id).remove()
self.__folders_to_check.add(folder.parent)
self.__store.remove(folder)
self.__folders_to_check.add(folder.parent)
self.__store.remove(folder)
def __is_valid_path(self, path):
if not os.path.exists(path):
return False
if not self.__extensions:
return True
return os.path.splitext(path)[1][1:].lower() in self.__extensions
def __is_valid_path(self, path):
if not os.path.exists(path):
return False
if not self.__extensions:
return True
return os.path.splitext(path)[1][1:].lower() in self.__extensions
def scan_file(self, path):
tr = self.__store.find(Track, Track.path == path).one()
add = False
if tr:
if not self.__force and not int(os.path.getmtime(path)) > tr.last_modification:
return
def scan_file(self, path):
tr = self.__store.find(Track, Track.path == path).one()
add = False
if tr:
if not self.__force and not int(os.path.getmtime(path)) > tr.last_modification:
return
tag = self.__try_load_tag(path)
if not tag:
self.remove_file(path)
return
else:
tag = self.__try_load_tag(path)
if not tag:
return
tag = self.__try_load_tag(path)
if not tag:
self.remove_file(path)
return
else:
tag = self.__try_load_tag(path)
if not tag:
return
tr = Track()
tr.path = path
add = True
tr = Track()
tr.path = path
add = True
artist = self.__try_read_tag(tag, 'artist', '')
album = self.__try_read_tag(tag, 'album', '')
albumartist = self.__try_read_tag(tag, 'albumartist', artist)
artist = self.__try_read_tag(tag, 'artist', '')
album = self.__try_read_tag(tag, 'album', '')
albumartist = self.__try_read_tag(tag, 'albumartist', artist)
tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0]))
tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0]))
tr.title = self.__try_read_tag(tag, 'title', '')
tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0]))
tr.genre = self.__try_read_tag(tag, 'genre')
tr.duration = int(tag.info.length)
tr.disc = self.__try_read_tag(tag, 'discnumber', 1, lambda x: int(x[0].split('/')[0]))
tr.number = self.__try_read_tag(tag, 'tracknumber', 1, lambda x: int(x[0].split('/')[0]))
tr.title = self.__try_read_tag(tag, 'title', '')
tr.year = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0]))
tr.genre = self.__try_read_tag(tag, 'genre')
tr.duration = int(tag.info.length)
tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000
tr.content_type = config.get_mime(os.path.splitext(path)[1][1:])
tr.last_modification = os.path.getmtime(path)
tr.bitrate = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) / 1000
tr.content_type = config.get_mime(os.path.splitext(path)[1][1:])
tr.last_modification = os.path.getmtime(path)
tralbum = self.__find_album(albumartist, album)
trartist = self.__find_artist(artist)
tralbum = self.__find_album(albumartist, album)
trartist = self.__find_artist(artist)
if add:
trroot = self.__find_root_folder(path)
trfolder = self.__find_folder(path)
if add:
trroot = self.__find_root_folder(path)
trfolder = self.__find_folder(path)
# Set the references at the very last as searching for them will cause the added track to be flushed, even if
# it is incomplete, causing not null constraints errors.
tr.album = tralbum
tr.artist = trartist
tr.folder = trfolder
tr.root_folder = trroot
# Set the references at the very last as searching for them will cause the added track to be flushed, even if
# it is incomplete, causing not null constraints errors.
tr.album = tralbum
tr.artist = trartist
tr.folder = trfolder
tr.root_folder = trroot
self.__store.add(tr)
self.__added_tracks += 1
else:
if tr.album.id != tralbum.id:
self.__albums_to_check.add(tr.album)
tr.album = tralbum
self.__store.add(tr)
self.__added_tracks += 1
else:
if tr.album.id != tralbum.id:
self.__albums_to_check.add(tr.album)
tr.album = tralbum
if tr.artist.id != trartist.id:
self.__artists_to_check.add(tr.artist)
tr.artist = trartist
if tr.artist.id != trartist.id:
self.__artists_to_check.add(tr.artist)
tr.artist = trartist
def remove_file(self, path):
tr = self.__store.find(Track, Track.path == path).one()
if not tr:
return
def remove_file(self, path):
tr = self.__store.find(Track, Track.path == path).one()
if not tr:
return
self.__store.find(StarredTrack, StarredTrack.starred_id == tr.id).remove()
self.__store.find(RatingTrack, RatingTrack.rated_id == tr.id).remove()
# Playlist autofix themselves
self.__store.find(User, User.last_play_id == tr.id).set(last_play_id = None)
self.__store.find(StarredTrack, StarredTrack.starred_id == tr.id).remove()
self.__store.find(RatingTrack, RatingTrack.rated_id == tr.id).remove()
# Playlist autofix themselves
self.__store.find(User, User.last_play_id == tr.id).set(last_play_id = None)
self.__folders_to_check.add(tr.folder)
self.__albums_to_check.add(tr.album)
self.__artists_to_check.add(tr.artist)
self.__store.remove(tr)
self.__deleted_tracks += 1
self.__folders_to_check.add(tr.folder)
self.__albums_to_check.add(tr.album)
self.__artists_to_check.add(tr.artist)
self.__store.remove(tr)
self.__deleted_tracks += 1
def move_file(self, src_path, dst_path):
tr = self.__store.find(Track, Track.path == src_path).one()
if not tr:
return
def move_file(self, src_path, dst_path):
tr = self.__store.find(Track, Track.path == src_path).one()
if not tr:
return
self.__folders_to_check.add(tr.folder)
tr_dst = self.__store.find(Track, Track.path == dst_path).one()
if tr_dst:
tr.root_folder = tr_dst.root_folder
tr.folder = tr_dst.folder
self.remove_file(dst_path)
else:
root = self.__find_root_folder(dst_path)
folder = self.__find_folder(dst_path)
tr.root_folder = root
tr.folder = folder
tr.path = dst_path
self.__folders_to_check.add(tr.folder)
tr_dst = self.__store.find(Track, Track.path == dst_path).one()
if tr_dst:
tr.root_folder = tr_dst.root_folder
tr.folder = tr_dst.folder
self.remove_file(dst_path)
else:
root = self.__find_root_folder(dst_path)
folder = self.__find_folder(dst_path)
tr.root_folder = root
tr.folder = folder
tr.path = dst_path
def __find_album(self, artist, album):
ar = self.__find_artist(artist)
al = ar.albums.find(name = album).one()
if al:
return al
def __find_album(self, artist, album):
ar = self.__find_artist(artist)
al = ar.albums.find(name = album).one()
if al:
return al
al = Album()
al.name = album
al.artist = ar
al = Album()
al.name = album
al.artist = ar
self.__store.add(al)
self.__added_albums += 1
self.__store.add(al)
self.__added_albums += 1
return al
return al
def __find_artist(self, artist):
ar = self.__store.find(Artist, Artist.name == artist).one()
if ar:
return ar
def __find_artist(self, artist):
ar = self.__store.find(Artist, Artist.name == artist).one()
if ar:
return ar
ar = Artist()
ar.name = artist
ar = Artist()
ar.name = artist
self.__store.add(ar)
self.__added_artists += 1
self.__store.add(ar)
self.__added_artists += 1
return ar
return ar
def __find_root_folder(self, path):
path = os.path.dirname(path)
db = self.__store.get_database().__module__[len('storm.databases.'):]
folders = self.__store.find(Folder, Like(path, Concat(Folder.path, u'%', db)), Folder.root == True)
count = folders.count()
if count > 1:
raise Exception("Found multiple root folders for '{}'.".format(path))
elif count == 0:
raise Exception("Couldn't find the root folder for '{}'.\nDon't scan files that aren't located in a defined music folder".format(path))
return folders.one()
def __find_root_folder(self, path):
path = os.path.dirname(path)
db = self.__store.get_database().__module__[len('storm.databases.'):]
folders = self.__store.find(Folder, Like(path, Concat(Folder.path, u'%', db)), Folder.root == True)
count = folders.count()
if count > 1:
raise Exception("Found multiple root folders for '{}'.".format(path))
elif count == 0:
raise Exception("Couldn't find the root folder for '{}'.\nDon't scan files that aren't located in a defined music folder".format(path))
return folders.one()
def __find_folder(self, path):
path = os.path.dirname(path)
folders = self.__store.find(Folder, Folder.path == path)
count = folders.count()
if count > 1:
raise Exception("Found multiple folders for '{}'.".format(path))
elif count == 1:
return folders.one()
def __find_folder(self, path):
path = os.path.dirname(path)
folders = self.__store.find(Folder, Folder.path == path)
count = folders.count()
if count > 1:
raise Exception("Found multiple folders for '{}'.".format(path))
elif count == 1:
return folders.one()
db = self.__store.get_database().__module__[len('storm.databases.'):]
folder = self.__store.find(Folder, Like(path, Concat(Folder.path, os.sep + u'%', db))).order_by(Folder.path).last()
db = self.__store.get_database().__module__[len('storm.databases.'):]
folder = self.__store.find(Folder, Like(path, Concat(Folder.path, os.sep + u'%', db))).order_by(Folder.path).last()
full_path = folder.path
path = path[len(folder.path) + 1:]
full_path = folder.path
path = path[len(folder.path) + 1:]
for name in path.split(os.sep):
full_path = os.path.join(full_path, name)
for name in path.split(os.sep):
full_path = os.path.join(full_path, name)
fold = Folder()
fold.root = False
fold.name = name
fold.path = full_path
fold.parent = folder
fold = Folder()
fold.root = False
fold.name = name
fold.path = full_path
fold.parent = folder
self.__store.add(fold)
self.__store.add(fold)
folder = fold
folder = fold
return folder
return folder
def __try_load_tag(self, path):
try:
return mutagen.File(path, easy = True)
except:
return None
def __try_load_tag(self, path):
try:
return mutagen.File(path, easy = True)
except:
return None
def __try_read_tag(self, metadata, field, default = None, transform = lambda x: x[0]):
try:
value = metadata[field]
if not value:
return default
if transform:
value = transform(value)
return value if value else default
except:
return default
def __try_read_tag(self, metadata, field, default = None, transform = lambda x: x[0]):
try:
value = metadata[field]
if not value:
return default
if transform:
value = transform(value)
return value if value else default
except:
return default
def stats(self):
return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)
def stats(self):
return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)