mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +00:00
Look at my pony, my pony is amazing
This commit is contained in:
parent
53fd4865cb
commit
df63919634
@ -99,11 +99,11 @@ def get_client_prefs():
|
||||
client = request.values.get('c')
|
||||
with db_session:
|
||||
try:
|
||||
prefs = ClientPrefs[request.user.id, client]
|
||||
ClientPrefs[request.user.id, client]
|
||||
except ObjectNotFound:
|
||||
prefs = ClientPrefs(user = User[request.user.id], client_name = client)
|
||||
ClientPrefs(user = User[request.user.id], client_name = client)
|
||||
|
||||
request.prefs = prefs
|
||||
request.client = client
|
||||
|
||||
@app.after_request
|
||||
def set_headers(response):
|
||||
@ -216,19 +216,20 @@ class ResponseHelper:
|
||||
return str(value).lower()
|
||||
return str(value)
|
||||
|
||||
def get_entity(req, ent, param = 'id'):
|
||||
def get_entity(req, cls, param = 'id'):
|
||||
eid = req.values.get(param)
|
||||
if not eid:
|
||||
return False, req.error_formatter(10, 'Missing %s id' % ent.__name__)
|
||||
return False, req.error_formatter(10, 'Missing %s id' % cls.__name__)
|
||||
|
||||
try:
|
||||
eid = uuid.UUID(eid)
|
||||
except:
|
||||
return False, req.error_formatter(0, 'Invalid %s id' % ent.__name__)
|
||||
return False, req.error_formatter(0, 'Invalid %s id' % cls.__name__)
|
||||
|
||||
entity = store.get(ent, eid)
|
||||
if not entity:
|
||||
return False, (req.error_formatter(70, '%s not found' % ent.__name__), 404)
|
||||
try:
|
||||
entity = cls[eid]
|
||||
except ObjectNotFound:
|
||||
return False, (req.error_formatter(70, '%s not found' % cls.__name__), 404)
|
||||
|
||||
return True, entity
|
||||
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
|
||||
from datetime import timedelta
|
||||
from flask import request, current_app as app
|
||||
from pony.orm import db_session, select, desc, avg, max, min, count
|
||||
|
||||
from ..db import Folder, Artist, Album, Track, RatingFolder, StarredFolder, StarredArtist, StarredAlbum, StarredTrack, User
|
||||
from ..db import now
|
||||
@ -40,33 +41,26 @@ def rand_songs():
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid parameter format')
|
||||
|
||||
query = store.find(Track)
|
||||
query = Track.select()
|
||||
if fromYear:
|
||||
query = query.find(Track.year >= fromYear)
|
||||
query = query.filter(lambda t: t.year >= fromYear)
|
||||
if toYear:
|
||||
query = query.find(Track.year <= toYear)
|
||||
query = query.filter(lambda t: t.year <= toYear)
|
||||
if genre:
|
||||
query = query.find(Track.genre == genre)
|
||||
query = query.filter(lambda t: t.genre == genre)
|
||||
if fid:
|
||||
if not store.find(Folder, Folder.id == fid, Folder.root == True).one():
|
||||
return request.error_formatter(70, 'Unknown folder')
|
||||
with db_session:
|
||||
if not Folder.exists(id = fid, root = True):
|
||||
return request.error_formatter(70, 'Unknown folder')
|
||||
|
||||
query = query.find(Track.root_folder_id == fid)
|
||||
query = query.filter(lambda t: t.root_folder.id == fid)
|
||||
|
||||
count = query.count()
|
||||
if not count:
|
||||
return request.formatter({ 'randomSongs': {} })
|
||||
|
||||
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 ]
|
||||
}
|
||||
})
|
||||
with db_session:
|
||||
return request.formatter({
|
||||
'randomSongs': {
|
||||
'song': [ t.as_subsonic_child(request.user, request.client) for t in query.random(size) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getAlbumList.view', methods = [ 'GET', 'POST' ])
|
||||
def album_list():
|
||||
@ -79,46 +73,37 @@ def album_list():
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid parameter format')
|
||||
|
||||
query = store.find(Folder, Track.folder_id == Folder.id)
|
||||
query = select(t.folder for t in Track)
|
||||
if ltype == 'random':
|
||||
albums = []
|
||||
count = query.count()
|
||||
|
||||
if not count:
|
||||
return request.formatter({ 'albumList': {} })
|
||||
|
||||
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 ]
|
||||
}
|
||||
})
|
||||
with db_session:
|
||||
return request.formatter({
|
||||
'albumList': {
|
||||
'album': [ a.as_subsonic_child(request.user) for a in query.random(size) ]
|
||||
}
|
||||
})
|
||||
elif ltype == 'newest':
|
||||
query = query.order_by(Desc(Folder.created)).config(distinct = True)
|
||||
query = query.order_by(desc(Folder.created))
|
||||
elif ltype == 'highest':
|
||||
query = query.find(RatingFolder.rated_id == Folder.id).group_by(Folder.id).order_by(Desc(Avg(RatingFolder.rating)))
|
||||
query = query.order_by(lambda f: desc(avg(f.ratings.rating)))
|
||||
elif ltype == 'frequent':
|
||||
query = query.group_by(Folder.id).order_by(Desc(Avg(Track.play_count)))
|
||||
query = query.order_by(lambda f: desc(avg(f.tracks.play_count)))
|
||||
elif ltype == 'recent':
|
||||
query = query.group_by(Folder.id).order_by(Desc(Max(Track.last_play)))
|
||||
query = query.order_by(lambda f: desc(max(f.tracks.last_play)))
|
||||
elif ltype == 'starred':
|
||||
query = query.find(StarredFolder.starred_id == Folder.id, User.id == StarredFolder.user_id, User.name == request.username)
|
||||
query = select(s.starred for s in StarredFolder if s.user.id == request.user.id and count(s.starred.tracks) > 0)
|
||||
elif ltype == 'alphabeticalByName':
|
||||
query = query.order_by(Folder.name).config(distinct = True)
|
||||
query = query.order_by(Folder.name)
|
||||
elif ltype == 'alphabeticalByArtist':
|
||||
parent = ClassAlias(Folder)
|
||||
query = query.find(Folder.parent_id == parent.id).order_by(parent.name, Folder.name).config(distinct = True)
|
||||
query = query.order_by(lambda f: f.parent.name + f.name)
|
||||
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] ]
|
||||
}
|
||||
})
|
||||
with db_session:
|
||||
return request.formatter({
|
||||
'albumList': {
|
||||
'album': [ f.as_subsonic_child(request.user) for f in query.limit(size, offset) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getAlbumList2.view', methods = [ 'GET', 'POST' ])
|
||||
def album_list_id3():
|
||||
@ -131,76 +116,71 @@ def album_list_id3():
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid parameter format')
|
||||
|
||||
query = store.find(Album)
|
||||
query = Album.select()
|
||||
if ltype == 'random':
|
||||
albums = []
|
||||
count = query.count()
|
||||
|
||||
if not count:
|
||||
return request.formatter({ 'albumList2': {} })
|
||||
|
||||
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 ]
|
||||
}
|
||||
})
|
||||
with db_session:
|
||||
return request.formatter({
|
||||
'albumList2': {
|
||||
'album': [ a.as_subsonic_album(request.user) for a in query.random(size) ]
|
||||
}
|
||||
})
|
||||
elif ltype == 'newest':
|
||||
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Min(Track.created)))
|
||||
query = query.order_by(lambda a: desc(min(a.tracks.created)))
|
||||
elif ltype == 'frequent':
|
||||
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Avg(Track.play_count)))
|
||||
query = query.order_by(lambda a: desc(avg(a.tracks.play_count)))
|
||||
elif ltype == 'recent':
|
||||
query = query.find(Track.album_id == Album.id).group_by(Album.id).order_by(Desc(Max(Track.last_play)))
|
||||
query = query.order_by(lambda a: desc(max(a.tracks.last_play)))
|
||||
elif ltype == 'starred':
|
||||
query = query.find(StarredAlbum.starred_id == Album.id, User.id == StarredAlbum.user_id, User.name == request.username)
|
||||
query = select(s.starred for s in StarredAlbum if s.user.id == request.user.id)
|
||||
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)
|
||||
query = query.order_by(lambda a: a.artist.name + a.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] ]
|
||||
}
|
||||
})
|
||||
with db_session:
|
||||
return request.formatter({
|
||||
'albumList2': {
|
||||
'album': [ f.as_subsonic_album(request.user) for f in query.limit(size, offset) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getNowPlaying.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def now_playing():
|
||||
query = store.find(User, Track.id == User.last_play_id)
|
||||
query = User.select(lambda u: u.last_play is not None and u.last_play_date + timedelta(minutes = 3) > now())
|
||||
|
||||
return request.formatter({
|
||||
'nowPlaying': {
|
||||
'entry': [ dict(
|
||||
u.last_play.as_subsonic_child(request.user, request.prefs).items() +
|
||||
u.last_play.as_subsonic_child(request.user, request.client).items() +
|
||||
{ 'username': u.name, 'minutesAgo': (now() - u.last_play_date).seconds / 60, 'playerId': 0 }.items()
|
||||
) for u in query if u.last_play_date + timedelta(seconds = u.last_play.duration * 2) > now() ]
|
||||
) for u in query ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getStarred.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def get_starred():
|
||||
folders = store.find(StarredFolder, StarredFolder.user_id == User.id, User.name == request.username)
|
||||
folders = select(s.starred for s in StarredFolder if s.user.id == request.user.id)
|
||||
|
||||
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) ]
|
||||
'artist': [ { 'id': str(sf.id), 'name': sf.name } for sf in folders.filter(lambda f: count(f.tracks) == 0) ],
|
||||
'album': [ sf.as_subsonic_child(request.user) for sf in folders.filter(lambda f: count(f.tracks) > 0) ],
|
||||
'song': [ st.as_subsonic_child(request.user, request.client) for st in select(s.starred for s in StarredTrack if s.user.id == request.user.id) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getStarred2.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
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) ]
|
||||
'artist': [ sa.as_subsonic_artist(request.user) for sa in select(s.starred for s in StarredArtist if s.user.id == request.user.id) ],
|
||||
'album': [ sa.as_subsonic_album(request.user) for sa in select(s.starred for s in StarredAlbum if s.user.id == request.user.id) ],
|
||||
'song': [ st.as_subsonic_child(request.user, request.client) for st in select(s.starred for s in StarredTrack if s.user.id == request.user.id) ]
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -22,19 +22,22 @@ import time
|
||||
import uuid
|
||||
|
||||
from flask import request, current_app as app
|
||||
from pony.orm import db_session, delete
|
||||
from pony.orm import ObjectNotFound
|
||||
|
||||
from ..db import Track, Album, Artist, Folder
|
||||
from ..db import Track, Album, Artist, Folder, User
|
||||
from ..db import StarredTrack, StarredAlbum, StarredArtist, StarredFolder
|
||||
from ..db import RatingTrack, RatingFolder
|
||||
from ..lastfm import LastFm
|
||||
|
||||
from . import get_entity
|
||||
|
||||
def try_star(ent, starred_ent, eid):
|
||||
@db_session
|
||||
def try_star(cls, starred_cls, eid):
|
||||
""" Stars an entity
|
||||
|
||||
:param ent: entity class, Folder, Artist, Album or Track
|
||||
:param starred_ent: class used for the db representation of the starring of ent
|
||||
:param cls: entity class, Folder, Artist, Album or Track
|
||||
:param starred_cls: class used for the db representation of the starring of ent
|
||||
:param eid: id of the entity to star
|
||||
:return error dict, if any. None otherwise
|
||||
"""
|
||||
@ -42,26 +45,27 @@ def try_star(ent, starred_ent, eid):
|
||||
try:
|
||||
uid = uuid.UUID(eid)
|
||||
except:
|
||||
return { 'code': 0, 'message': 'Invalid {} id {}'.format(ent.__name__, eid) }
|
||||
return { 'code': 0, 'message': 'Invalid {} id {}'.format(cls.__name__, eid) }
|
||||
|
||||
if store.get(starred_ent, (request.user.id, uid)):
|
||||
return { 'code': 0, 'message': '{} {} already starred'.format(ent.__name__, eid) }
|
||||
try:
|
||||
e = cls[uid]
|
||||
except ObjectNotFound:
|
||||
return { 'code': 70, 'message': 'Unknown {} id {}'.format(cls.__name__, eid) }
|
||||
|
||||
e = store.get(ent, uid)
|
||||
if not e:
|
||||
return { 'code': 70, 'message': 'Unknown {} id {}'.format(ent.__name__, eid) }
|
||||
|
||||
starred = starred_ent()
|
||||
starred.user_id = request.user.id
|
||||
starred.starred_id = uid
|
||||
store.add(starred)
|
||||
try:
|
||||
starred_cls[request.user.id, uid]
|
||||
return { 'code': 0, 'message': '{} {} already starred'.format(cls.__name__, eid) }
|
||||
except ObjectNotFound:
|
||||
pass
|
||||
|
||||
starred_cls(user = User[request.user.id], starred = e)
|
||||
return None
|
||||
|
||||
def try_unstar(starred_ent, eid):
|
||||
@db_session
|
||||
def try_unstar(starred_cls, eid):
|
||||
""" Unstars an entity
|
||||
|
||||
:param starred_ent: class used for the db representation of the starring of the entity
|
||||
:param starred_cls: class used for the db representation of the starring of the entity
|
||||
:param eid: id of the entity to unstar
|
||||
:return error dict, if any. None otherwise
|
||||
"""
|
||||
@ -71,7 +75,7 @@ def try_unstar(starred_ent, eid):
|
||||
except:
|
||||
return { 'code': 0, 'message': 'Invalid id {}'.format(eid) }
|
||||
|
||||
store.find(starred_ent, starred_ent.user_id == request.user.id, starred_ent.starred_id == uid).remove()
|
||||
delete(s for s in starred_cls if s.user.id == request.user.id and s.starred.id == uid)
|
||||
return None
|
||||
|
||||
def merge_errors(errors):
|
||||
@ -105,7 +109,6 @@ def star():
|
||||
for arId in artistId:
|
||||
errors.append(try_star(Artist, StarredArtist, arId))
|
||||
|
||||
store.commit()
|
||||
error = merge_errors(errors)
|
||||
return request.formatter({ 'error': error }, error = True) if error else request.formatter({})
|
||||
|
||||
@ -129,7 +132,6 @@ def unstar():
|
||||
for arId in artistId:
|
||||
errors.append(try_unstar(StarredArtist, arId))
|
||||
|
||||
store.commit()
|
||||
error = merge_errors(errors)
|
||||
return request.formatter({ 'error': error }, error = True) if error else request.formatter({})
|
||||
|
||||
@ -148,32 +150,31 @@ def rate():
|
||||
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')
|
||||
|
||||
rating_info = store.get(rating_ent, (request.user.id, uid))
|
||||
if rating_info:
|
||||
rating_info.rating = rating
|
||||
with db_session:
|
||||
if rating == 0:
|
||||
delete(r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == uid)
|
||||
delete(r for r in RatingFolder if r.user.id == request.user.id and r.rated.id == uid)
|
||||
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)
|
||||
try:
|
||||
rated = Track[uid]
|
||||
rating_cls = RatingTrack
|
||||
except ObjectNotFound:
|
||||
try:
|
||||
rated = Folder[uid]
|
||||
rating_cls = RatingFolder
|
||||
except ObjectNotFound:
|
||||
return request.error_formatter(70, 'Unknown id')
|
||||
|
||||
try:
|
||||
rating_info = rating_cls[request.user.id, uid]
|
||||
rating_info.rating = rating
|
||||
except ObjectNotFound:
|
||||
rating_cls(user = User[request.user.id], rated = rated, rating = rating)
|
||||
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
|
||||
@app.route('/rest/scrobble.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def scrobble():
|
||||
status, res = get_entity(request, Track)
|
||||
if not status:
|
||||
@ -189,7 +190,7 @@ def scrobble():
|
||||
else:
|
||||
t = int(time.time())
|
||||
|
||||
lfm = LastFm(app.config['LASTFM'], request.user, app.logger)
|
||||
lfm = LastFm(app.config['LASTFM'], User[request.user.id], app.logger)
|
||||
|
||||
if submission in (None, '', True, 'true', 'True', 1, '1'):
|
||||
lfm.scrobble(res, t)
|
||||
|
@ -22,23 +22,27 @@ import string
|
||||
import uuid
|
||||
|
||||
from flask import request, current_app as app
|
||||
from pony.orm import db_session
|
||||
from pony.orm import ObjectNotFound
|
||||
|
||||
from ..db import Folder, Artist, Album, Track
|
||||
|
||||
from . import get_entity
|
||||
|
||||
@app.route('/rest/getMusicFolders.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
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) ]
|
||||
} for f in Folder.select(lambda f: f.root).order_by(Folder.name) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getIndexes.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def list_indexes():
|
||||
musicFolderId = request.values.get('musicFolderId')
|
||||
ifModifiedSince = request.values.get('ifModifiedSince')
|
||||
@ -49,33 +53,31 @@ def list_indexes():
|
||||
return request.error_formatter(0, 'Invalid timestamp')
|
||||
|
||||
if musicFolderId is None:
|
||||
folder = store.find(Folder, Folder.root == True)
|
||||
folders = Folder.select(lambda f: f.root)[:]
|
||||
else:
|
||||
try:
|
||||
mfid = uuid.UUID(musicFolderId)
|
||||
except:
|
||||
return request.error_formatter(0, 'Invalid id')
|
||||
|
||||
folder = store.get(Folder, mfid)
|
||||
try:
|
||||
folder = Folder[mfid]
|
||||
except ObjectNotFound:
|
||||
return request.error_formatter(70, 'Folder not found')
|
||||
if not folder.root:
|
||||
return request.error_formatter(70, 'Folder not found')
|
||||
folders = [ folder ]
|
||||
|
||||
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
|
||||
|
||||
if (not ifModifiedSince is None) and last_modif < ifModifiedSince:
|
||||
last_modif = max(map(lambda f: f.last_scan, folders))
|
||||
if ifModifiedSince is not 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
|
||||
artists = []
|
||||
children = []
|
||||
for f in folders:
|
||||
artists += f.children.select()[:]
|
||||
children += f.tracks.select()[:]
|
||||
|
||||
indexes = {}
|
||||
for artist in artists:
|
||||
@ -100,11 +102,12 @@ def list_indexes():
|
||||
'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()) ]
|
||||
'child': [ c.as_subsonic_child(request.user, request.client) for c in sorted(children, key = lambda t: t.sort_key()) ]
|
||||
}
|
||||
})
|
||||
|
||||
@app.route('/rest/getMusicDirectory.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def show_directory():
|
||||
status, res = get_entity(request, Folder)
|
||||
if not status:
|
||||
@ -113,18 +116,19 @@ def show_directory():
|
||||
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()) ]
|
||||
'child': [ f.as_subsonic_child(request.user) for f in res.children.order_by(lambda c: c.name.lower()) ] + [ t.as_subsonic_child(request.user, request.client) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
|
||||
}
|
||||
if not res.root:
|
||||
directory['parent'] = str(res.parent_id)
|
||||
directory['parent'] = str(res.parent.id)
|
||||
|
||||
return request.formatter({ 'directory': directory })
|
||||
|
||||
@app.route('/rest/getArtists.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def list_artists():
|
||||
# According to the API page, there are no parameters?
|
||||
indexes = {}
|
||||
for artist in store.find(Artist):
|
||||
for artist in Artist.select():
|
||||
index = artist.name[0].upper() if artist.name else '?'
|
||||
if index in map(str, xrange(10)):
|
||||
index = '#'
|
||||
@ -146,6 +150,7 @@ def list_artists():
|
||||
})
|
||||
|
||||
@app.route('/rest/getArtist.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def artist_info():
|
||||
status, res = get_entity(request, Artist)
|
||||
if not status:
|
||||
@ -159,23 +164,25 @@ def artist_info():
|
||||
return request.formatter({ 'artist': info })
|
||||
|
||||
@app.route('/rest/getAlbum.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def album_info():
|
||||
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['song'] = [ t.as_subsonic_child(request.user, request.client) for t in sorted(res.tracks, key = lambda t: t.sort_key()) ]
|
||||
|
||||
return request.formatter({ 'album': info })
|
||||
|
||||
@app.route('/rest/getSong.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def track_info():
|
||||
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.client) })
|
||||
|
||||
@app.route('/rest/getVideos.view', methods = [ 'GET', 'POST' ])
|
||||
def list_videos():
|
||||
|
@ -26,6 +26,7 @@ import subprocess
|
||||
|
||||
from flask import request, send_file, Response, current_app as app
|
||||
from PIL import Image
|
||||
from pony.orm import db_session
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from .. import scanner
|
||||
@ -42,6 +43,7 @@ def prepare_transcoding_cmdline(base_cmdline, input_file, input_format, output_f
|
||||
return ret
|
||||
|
||||
@app.route('/rest/stream.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def stream_media():
|
||||
status, res = get_entity(request, Track)
|
||||
if not status:
|
||||
@ -56,10 +58,11 @@ def stream_media():
|
||||
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
|
||||
prefs = ClientPrefs.get(lambda p: p.user.id == request.user.id and p.client_name == request.client)
|
||||
if prefs.format:
|
||||
dst_suffix = prefs.format
|
||||
if prefs.bitrate and prefs.bitrate < dst_bitrate:
|
||||
dst_bitrate = prefs.bitrate
|
||||
|
||||
if maxBitRate:
|
||||
try:
|
||||
@ -120,15 +123,16 @@ def stream_media():
|
||||
|
||||
res.play_count = res.play_count + 1
|
||||
res.last_play = now()
|
||||
request.user.last_play = res
|
||||
request.user.last_play_date = now()
|
||||
store.commit()
|
||||
user = User[request.user.id]
|
||||
user.last_play = res
|
||||
user.last_play_date = now()
|
||||
|
||||
return response
|
||||
|
||||
@app.route('/rest/download.view', methods = [ 'GET', 'POST' ])
|
||||
def download_media():
|
||||
status, res = get_entity(request, Track)
|
||||
with db_session:
|
||||
status, res = get_entity(request, Track)
|
||||
if not status:
|
||||
return res
|
||||
|
||||
@ -136,7 +140,8 @@ def download_media():
|
||||
|
||||
@app.route('/rest/getCoverArt.view', methods = [ 'GET', 'POST' ])
|
||||
def cover_art():
|
||||
status, res = get_entity(request, Folder)
|
||||
with db_session:
|
||||
status, res = get_entity(request, Folder)
|
||||
if not status:
|
||||
return res
|
||||
|
||||
@ -175,25 +180,26 @@ def lyrics():
|
||||
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)
|
||||
with db_session:
|
||||
query = Track.select(lambda t: title in t.title and artist in t.artist.name)
|
||||
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",
|
||||
|
@ -21,6 +21,8 @@
|
||||
import uuid
|
||||
|
||||
from flask import request, current_app as app
|
||||
from pony.orm import db_session, rollback
|
||||
from pony.orm import ObjectNotFound
|
||||
|
||||
from ..db import Playlist, User, Track
|
||||
|
||||
@ -28,37 +30,40 @@ 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 = Playlist.select(lambda p: p.user.id == request.user.id or p.public).order_by(Playlist.name)
|
||||
|
||||
username = request.values.get('username')
|
||||
if username:
|
||||
if not request.user.admin:
|
||||
return request.error_formatter(50, 'Restricted to admins')
|
||||
|
||||
user = store.find(User, User.name == username).one()
|
||||
if not user:
|
||||
with db_session:
|
||||
user = User.get(name = username)
|
||||
if user is None:
|
||||
return request.error_formatter(70, 'No such user')
|
||||
|
||||
query = store.find(Playlist, Playlist.user_id == User.id, User.name == username).order_by(Playlist.name)
|
||||
query = Playlist.select(lambda p: p.user.name == username).order_by(Playlist.name)
|
||||
|
||||
return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
|
||||
with db_session:
|
||||
return request.formatter({ 'playlists': { 'playlist': [ p.as_subsonic_playlist(request.user) for p in query ] } })
|
||||
|
||||
@app.route('/rest/getPlaylist.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def show_playlist():
|
||||
status, res = get_entity(request, Playlist)
|
||||
if not status:
|
||||
return res
|
||||
|
||||
if res.user_id != request.user.id and not request.user.admin:
|
||||
if res.user.id != request.user.id and not request.user.admin:
|
||||
return request.error_formatter('50', 'Private playlist')
|
||||
|
||||
info = res.as_subsonic_playlist(request.user)
|
||||
info['entry'] = [ t.as_subsonic_child(request.user, request.prefs) for t in res.get_tracks() ]
|
||||
info['entry'] = [ t.as_subsonic_child(request.user, request.client) for t in res.get_tracks() ]
|
||||
return request.formatter({ 'playlist': info })
|
||||
|
||||
@app.route('/rest/createPlaylist.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
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')
|
||||
@ -69,55 +74,54 @@ def create_playlist():
|
||||
return request.error_formatter(0, 'Invalid parameter')
|
||||
|
||||
if playlist_id:
|
||||
playlist = store.get(Playlist, playlist_id)
|
||||
if not playlist:
|
||||
try:
|
||||
playlist = Playlist[playlist_id]
|
||||
except ObjectNotFound:
|
||||
return request.error_formatter(70, 'Unknwon playlist')
|
||||
|
||||
if playlist.user_id != request.user.id and not request.user.admin:
|
||||
if playlist.user.id != request.user.id and not request.user.admin:
|
||||
return request.error_formatter(50, "You're not allowed to modify a playlist that isn't yours")
|
||||
|
||||
playlist.clear()
|
||||
if name:
|
||||
playlist.name = name
|
||||
elif name:
|
||||
playlist = Playlist()
|
||||
playlist.user_id = request.user.id
|
||||
playlist.name = name
|
||||
store.add(playlist)
|
||||
playlist = Playlist(user = User[request.user.id], name = name)
|
||||
else:
|
||||
return request.error_formatter(10, 'Missing playlist id or name')
|
||||
|
||||
for sid in songs:
|
||||
track = store.get(Track, sid)
|
||||
if not track:
|
||||
store.rollback()
|
||||
try:
|
||||
track = Track[sid]
|
||||
except ObjectNotFound:
|
||||
rollback()
|
||||
return request.error_formatter(70, 'Unknown song')
|
||||
|
||||
playlist.add(track)
|
||||
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
|
||||
@app.route('/rest/deletePlaylist.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def delete_playlist():
|
||||
status, res = get_entity(request, Playlist)
|
||||
if not status:
|
||||
return res
|
||||
|
||||
if res.user_id != request.user.id and not request.user.admin:
|
||||
if res.user.id != request.user.id and not request.user.admin:
|
||||
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
|
||||
|
||||
store.remove(res)
|
||||
store.commit()
|
||||
res.delete()
|
||||
return request.formatter({})
|
||||
|
||||
@app.route('/rest/updatePlaylist.view', methods = [ 'GET', 'POST' ])
|
||||
@db_session
|
||||
def update_playlist():
|
||||
status, res = get_entity(request, Playlist, 'playlistId')
|
||||
if not status:
|
||||
return res
|
||||
|
||||
if res.user_id != request.user.id and not request.user.admin:
|
||||
if res.user.id != request.user.id and not request.user.admin:
|
||||
return request.error_formatter(50, "You're not allowed to delete a playlist that isn't yours")
|
||||
|
||||
playlist = res
|
||||
@ -137,13 +141,13 @@ def update_playlist():
|
||||
playlist.public = public in (True, 'True', 'true', 1, '1')
|
||||
|
||||
for sid in to_add:
|
||||
track = store.get(Track, sid)
|
||||
if not track:
|
||||
try:
|
||||
track = Track[sid]
|
||||
except ObjectNotFound:
|
||||
return request.error_formatter(70, 'Unknown song')
|
||||
playlist.add(track)
|
||||
|
||||
playlist.remove_at_indexes(to_remove)
|
||||
|
||||
store.commit()
|
||||
return request.formatter({})
|
||||
|
||||
|
@ -56,7 +56,7 @@ def old_search():
|
||||
return request.formatter({ 'searchResult': {
|
||||
'totalHits': folders.count() + tracks.count(),
|
||||
'offset': offset,
|
||||
'match': [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.prefs) for r in res ]
|
||||
'match': [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.client) for r in res ]
|
||||
}})
|
||||
else:
|
||||
return request.error_formatter(10, 'Missing search parameter')
|
||||
@ -65,7 +65,7 @@ def old_search():
|
||||
return request.formatter({ 'searchResult': {
|
||||
'totalHits': query.count(),
|
||||
'offset': offset,
|
||||
'match': [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ]
|
||||
'match': [ r.as_subsonic_child(request.user) if isinstance(r, Folder) else r.as_subsonic_child(request.user, request.client) for r in query[offset : offset + count] ]
|
||||
}})
|
||||
|
||||
@app.route('/rest/search2.view', methods = [ 'GET', 'POST' ])
|
||||
@ -94,7 +94,7 @@ def new_search():
|
||||
return request.formatter({ 'searchResult2': {
|
||||
'artist': [ { 'id': str(a.id), 'name': a.name } for a in artists ],
|
||||
'album': [ f.as_subsonic_child(request.user) for f in albums ],
|
||||
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in songs ]
|
||||
'song': [ t.as_subsonic_child(request.user, request.client) for t in songs ]
|
||||
}})
|
||||
|
||||
@app.route('/rest/search3.view', methods = [ 'GET', 'POST' ])
|
||||
@ -123,6 +123,6 @@ def search_id3():
|
||||
return request.formatter({ 'searchResult3': {
|
||||
'artist': [ a.as_subsonic_artist(request.user) for a in artists ],
|
||||
'album': [ a.as_subsonic_album(request.user) for a in albums ],
|
||||
'song': [ t.as_subsonic_child(request.user, request.prefs) for t in songs ]
|
||||
'song': [ t.as_subsonic_child(request.user, request.client) for t in songs ]
|
||||
}})
|
||||
|
||||
|
@ -177,7 +177,7 @@ class Track(db.Entity):
|
||||
stars = Set(lambda: StarredTrack)
|
||||
ratings = Set(lambda: RatingTrack)
|
||||
|
||||
def as_subsonic_child(self, user, prefs):
|
||||
def as_subsonic_child(self, user, client):
|
||||
info = {
|
||||
'id': str(self.id),
|
||||
'parent': str(self.folder.id),
|
||||
@ -221,7 +221,8 @@ class Track(db.Entity):
|
||||
if avgRating:
|
||||
info['averageRating'] = avgRating
|
||||
|
||||
if prefs and prefs.format and prefs.format != self.suffix():
|
||||
prefs = ClientPrefs.get(lambda p: p.user.id == user.id and p.client_name == client)
|
||||
if prefs is not None and prefs.format is not None and prefs.format != self.suffix():
|
||||
info['transcodedSuffix'] = prefs.format
|
||||
info['transcodedContentType'] = mimetypes.guess_type('dummyname.' + prefs.format, False)[0] or 'application/octet-stream'
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Artist, Album, Track
|
||||
|
||||
from .apitestbase import ApiTestBase
|
||||
@ -22,34 +24,25 @@ class AlbumSongsTestCase(ApiTestBase):
|
||||
def setUp(self):
|
||||
super(AlbumSongsTestCase, self).setUp()
|
||||
|
||||
folder = Folder()
|
||||
folder.name = 'Root'
|
||||
folder.root = True
|
||||
folder.path = 'tests/assets'
|
||||
with db_session:
|
||||
folder = Folder(name = 'Root', root = True, path = 'tests/assets')
|
||||
artist = Artist(name = 'Artist')
|
||||
album = Album(name = 'Album', artist = artist)
|
||||
|
||||
artist = Artist()
|
||||
artist.name = 'Artist'
|
||||
|
||||
album = Album()
|
||||
album.name = 'Album'
|
||||
album.artist = artist
|
||||
|
||||
track = Track()
|
||||
track.title = 'Track'
|
||||
track.album = album
|
||||
track.artist = artist
|
||||
track.disc = 1
|
||||
track.number = 1
|
||||
track.path = 'tests/assets/empty'
|
||||
track.folder = folder
|
||||
track.root_folder = folder
|
||||
track.duration = 2
|
||||
track.bitrate = 320
|
||||
track.content_type = 'audio/mpeg'
|
||||
track.last_modification = 0
|
||||
|
||||
self.store.add(track)
|
||||
self.store.commit()
|
||||
track = Track(
|
||||
title = 'Track',
|
||||
album = album,
|
||||
artist = artist,
|
||||
disc = 1,
|
||||
number = 1,
|
||||
path = 'tests/assets/empty',
|
||||
folder = folder,
|
||||
root_folder = folder,
|
||||
duration = 2,
|
||||
bitrate = 320,
|
||||
content_type = 'audio/mpeg',
|
||||
last_modification = 0
|
||||
)
|
||||
|
||||
def test_get_album_list(self):
|
||||
self._make_request('getAlbumList', error = 10)
|
||||
@ -63,11 +56,9 @@ class AlbumSongsTestCase(ApiTestBase):
|
||||
self._make_request('getAlbumList', { 'type': t }, tag = 'albumList', skip_post = True)
|
||||
|
||||
rv, child = self._make_request('getAlbumList', { 'type': 'random' }, tag = 'albumList', skip_post = True)
|
||||
self.assertEqual(len(child), 10)
|
||||
rv, child = self._make_request('getAlbumList', { 'type': 'random', 'size': 3 }, tag = 'albumList', skip_post = True)
|
||||
self.assertEqual(len(child), 3)
|
||||
|
||||
self.store.remove(self.store.find(Folder).one())
|
||||
with db_session:
|
||||
Folder.get().delete()
|
||||
rv, child = self._make_request('getAlbumList', { 'type': 'random' }, tag = 'albumList')
|
||||
self.assertEqual(len(child), 0)
|
||||
|
||||
@ -82,12 +73,10 @@ class AlbumSongsTestCase(ApiTestBase):
|
||||
self._make_request('getAlbumList2', { 'type': t }, tag = 'albumList2', skip_post = True)
|
||||
|
||||
rv, child = self._make_request('getAlbumList2', { 'type': 'random' }, tag = 'albumList2', skip_post = True)
|
||||
self.assertEqual(len(child), 10)
|
||||
rv, child = self._make_request('getAlbumList2', { 'type': 'random', 'size': 3 }, tag = 'albumList2', skip_post = True)
|
||||
self.assertEqual(len(child), 3)
|
||||
|
||||
self.store.remove(self.store.find(Track).one())
|
||||
self.store.remove(self.store.find(Album).one())
|
||||
with db_session:
|
||||
Track.get().delete()
|
||||
Album.get().delete()
|
||||
rv, child = self._make_request('getAlbumList2', { 'type': 'random' }, tag = 'albumList2')
|
||||
self.assertEqual(len(child), 0)
|
||||
|
||||
@ -98,12 +87,10 @@ class AlbumSongsTestCase(ApiTestBase):
|
||||
self._make_request('getRandomSongs', { 'musicFolderId': 'idid' }, error = 0)
|
||||
self._make_request('getRandomSongs', { 'musicFolderId': uuid.uuid4() }, error = 70)
|
||||
|
||||
rv, child = self._make_request('getRandomSongs', tag = 'randomSongs')
|
||||
self.assertEqual(len(child), 10)
|
||||
rv, child = self._make_request('getRandomSongs', { 'size': 3 }, tag = 'randomSongs')
|
||||
self.assertEqual(len(child), 3)
|
||||
rv, child = self._make_request('getRandomSongs', tag = 'randomSongs', skip_post = True)
|
||||
|
||||
fid = self.store.find(Folder).one().id
|
||||
with db_session:
|
||||
fid = Folder.get().id
|
||||
self._make_request('getRandomSongs', { 'fromYear': -52, 'toYear': '1984', 'genre': 'some cryptic subgenre youve never heard of', 'musicFolderId': fid }, tag = 'randomSongs')
|
||||
|
||||
def test_now_playing(self):
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Artist, Album, Track, User, ClientPrefs
|
||||
|
||||
from .apitestbase import ApiTestBase
|
||||
@ -19,45 +21,32 @@ class AnnotationTestCase(ApiTestBase):
|
||||
def setUp(self):
|
||||
super(AnnotationTestCase, self).setUp()
|
||||
|
||||
root = Folder()
|
||||
root.name = 'Root'
|
||||
root.root = True
|
||||
root.path = 'tests/assets'
|
||||
with db_session:
|
||||
root = Folder(name = 'Root', root = True, path = 'tests')
|
||||
folder = Folder(name = 'Folder', path = 'tests/assets', parent = root)
|
||||
artist = Artist(name = 'Artist')
|
||||
album = Album(name = 'Album', artist = artist)
|
||||
|
||||
folder = Folder()
|
||||
folder.name = 'Folder'
|
||||
folder.path = 'tests/assets'
|
||||
folder.parent = root
|
||||
track = Track(
|
||||
title = 'Track',
|
||||
album = album,
|
||||
artist = artist,
|
||||
disc = 1,
|
||||
number = 1,
|
||||
path = 'tests/assets/empty',
|
||||
folder = folder,
|
||||
root_folder = root,
|
||||
duration = 2,
|
||||
bitrate = 320,
|
||||
content_type = 'audio/mpeg',
|
||||
last_modification = 0
|
||||
)
|
||||
|
||||
artist = Artist()
|
||||
artist.name = 'Artist'
|
||||
|
||||
album = Album()
|
||||
album.name = 'Album'
|
||||
album.artist = artist
|
||||
|
||||
track = Track()
|
||||
track.title = 'Track'
|
||||
track.album = album
|
||||
track.artist = artist
|
||||
track.disc = 1
|
||||
track.number = 1
|
||||
track.path = 'tests/assets/empty'
|
||||
track.folder = folder
|
||||
track.root_folder = root
|
||||
track.duration = 2
|
||||
track.bitrate = 320
|
||||
track.content_type = 'audio/mpeg'
|
||||
track.last_modification = 0
|
||||
|
||||
self.store.add(track)
|
||||
self.store.commit()
|
||||
|
||||
self.folder = folder
|
||||
self.artist = artist
|
||||
self.album = album
|
||||
self.track = track
|
||||
self.user = self.store.find(User, User.name == 'alice').one()
|
||||
self.folderid = folder.id
|
||||
self.artistid = artist.id
|
||||
self.albumid = album.id
|
||||
self.trackid = track.id
|
||||
self.user = User.get(name = 'alice')
|
||||
|
||||
def test_star(self):
|
||||
self._make_request('star', error = 10)
|
||||
@ -68,88 +57,101 @@ class AnnotationTestCase(ApiTestBase):
|
||||
self._make_request('star', { 'albumId': str(uuid.uuid4()) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
self._make_request('star', { 'id': str(self.artist.id) }, error = 70, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.album.id) }, error = 70, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.track.id) }, skip_post = True)
|
||||
self.assertIn('starred', self.track.as_subsonic_child(self.user, ClientPrefs()))
|
||||
self._make_request('star', { 'id': str(self.track.id) }, error = 0, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.artistid) }, error = 70, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.albumid) }, error = 70, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.trackid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertIn('starred', Track[self.trackid].as_subsonic_child(self.user, 'tests'))
|
||||
self._make_request('star', { 'id': str(self.trackid) }, error = 0, skip_xsd = True)
|
||||
|
||||
self._make_request('star', { 'id': str(self.folder.id) }, skip_post = True)
|
||||
self.assertIn('starred', self.folder.as_subsonic_child(self.user))
|
||||
self._make_request('star', { 'id': str(self.folder.id) }, error = 0, skip_xsd = True)
|
||||
self._make_request('star', { 'id': str(self.folderid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertIn('starred', Folder[self.folderid].as_subsonic_child(self.user))
|
||||
self._make_request('star', { 'id': str(self.folderid) }, error = 0, skip_xsd = True)
|
||||
|
||||
self._make_request('star', { 'albumId': str(self.folder.id) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.artist.id) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.track.id) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.album.id) }, skip_post = True)
|
||||
self.assertIn('starred', self.album.as_subsonic_album(self.user))
|
||||
self._make_request('star', { 'albumId': str(self.album.id) }, error = 0)
|
||||
self._make_request('star', { 'albumId': str(self.folderid) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.artistid) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.trackid) }, error = 70)
|
||||
self._make_request('star', { 'albumId': str(self.albumid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertIn('starred', Album[self.albumid].as_subsonic_album(self.user))
|
||||
self._make_request('star', { 'albumId': str(self.albumid) }, error = 0)
|
||||
|
||||
self._make_request('star', { 'artistId': str(self.folder.id) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.album.id) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.track.id) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.artist.id) }, skip_post = True)
|
||||
self.assertIn('starred', self.artist.as_subsonic_artist(self.user))
|
||||
self._make_request('star', { 'artistId': str(self.artist.id) }, error = 0)
|
||||
self._make_request('star', { 'artistId': str(self.folderid) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.albumid) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.trackid) }, error = 70)
|
||||
self._make_request('star', { 'artistId': str(self.artistid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertIn('starred', Artist[self.artistid].as_subsonic_artist(self.user))
|
||||
self._make_request('star', { 'artistId': str(self.artistid) }, error = 0)
|
||||
|
||||
def test_unstar(self):
|
||||
self._make_request('star', { 'id': [ str(self.folder.id), str(self.track.id) ], 'artistId': str(self.artist.id), 'albumId': str(self.album.id) }, skip_post = True)
|
||||
self._make_request('star', { 'id': [ str(self.folderid), str(self.trackid) ], 'artistId': str(self.artistid), 'albumId': str(self.albumid) }, skip_post = True)
|
||||
|
||||
self._make_request('unstar', error = 10)
|
||||
self._make_request('unstar', { 'id': 'unknown' }, error = 0, skip_xsd = True)
|
||||
self._make_request('unstar', { 'albumId': 'unknown' }, error = 0)
|
||||
self._make_request('unstar', { 'artistId': 'unknown' }, error = 0)
|
||||
|
||||
self._make_request('unstar', { 'id': str(self.track.id) }, skip_post = True)
|
||||
self.assertNotIn('starred', self.track.as_subsonic_child(self.user, ClientPrefs()))
|
||||
self._make_request('unstar', { 'id': str(self.trackid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('starred', Track[self.trackid].as_subsonic_child(self.user, 'tests'))
|
||||
|
||||
self._make_request('unstar', { 'id': str(self.folder.id) }, skip_post = True)
|
||||
self.assertNotIn('starred', self.folder.as_subsonic_child(self.user))
|
||||
self._make_request('unstar', { 'id': str(self.folderid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('starred', Folder[self.folderid].as_subsonic_child(self.user))
|
||||
|
||||
self._make_request('unstar', { 'albumId': str(self.album.id) }, skip_post = True)
|
||||
self.assertNotIn('starred', self.album.as_subsonic_album(self.user))
|
||||
self._make_request('unstar', { 'albumId': str(self.albumid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('starred', Album[self.albumid].as_subsonic_album(self.user))
|
||||
|
||||
self._make_request('unstar', { 'artistId': str(self.artist.id) }, skip_post = True)
|
||||
self.assertNotIn('starred', self.artist.as_subsonic_artist(self.user))
|
||||
self._make_request('unstar', { 'artistId': str(self.artistid) }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('starred', Artist[self.artistid].as_subsonic_artist(self.user))
|
||||
|
||||
def test_set_rating(self):
|
||||
self._make_request('setRating', error = 10)
|
||||
self._make_request('setRating', { 'id': str(self.track.id) }, error = 10)
|
||||
self._make_request('setRating', { 'id': str(self.trackid) }, error = 10)
|
||||
self._make_request('setRating', { 'rating': 3 }, error = 10)
|
||||
self._make_request('setRating', { 'id': 'string', 'rating': 3 }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(uuid.uuid4()), 'rating': 3 }, error = 70)
|
||||
self._make_request('setRating', { 'id': str(self.artist.id), 'rating': 3 }, error = 70)
|
||||
self._make_request('setRating', { 'id': str(self.album.id), 'rating': 3 }, error = 70)
|
||||
self._make_request('setRating', { 'id': str(self.track.id), 'rating': 'string' }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(self.track.id), 'rating': -1 }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(self.track.id), 'rating': 6 }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(self.artistid), 'rating': 3 }, error = 70)
|
||||
self._make_request('setRating', { 'id': str(self.albumid), 'rating': 3 }, error = 70)
|
||||
self._make_request('setRating', { 'id': str(self.trackid), 'rating': 'string' }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(self.trackid), 'rating': -1 }, error = 0)
|
||||
self._make_request('setRating', { 'id': str(self.trackid), 'rating': 6 }, error = 0)
|
||||
|
||||
prefs = ClientPrefs()
|
||||
self.assertNotIn('userRating', self.track.as_subsonic_child(self.user, prefs))
|
||||
with db_session:
|
||||
self.assertNotIn('userRating', Track[self.trackid].as_subsonic_child(self.user, 'tests'))
|
||||
|
||||
for i in range(1, 6):
|
||||
self._make_request('setRating', { 'id': str(self.track.id), 'rating': i }, skip_post = True)
|
||||
self.assertEqual(self.track.as_subsonic_child(self.user, prefs)['userRating'], i)
|
||||
self._make_request('setRating', { 'id': str(self.track.id), 'rating': 0 }, skip_post = True)
|
||||
self.assertNotIn('userRating', self.track.as_subsonic_child(self.user, prefs))
|
||||
self._make_request('setRating', { 'id': str(self.trackid), 'rating': i }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertEqual(Track[self.trackid].as_subsonic_child(self.user, 'tests')['userRating'], i)
|
||||
|
||||
self.assertNotIn('userRating', self.folder.as_subsonic_child(self.user))
|
||||
self._make_request('setRating', { 'id': str(self.trackid), 'rating': 0 }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('userRating', Track[self.trackid].as_subsonic_child(self.user, 'tests'))
|
||||
|
||||
self.assertNotIn('userRating', Folder[self.folderid].as_subsonic_child(self.user))
|
||||
for i in range(1, 6):
|
||||
self._make_request('setRating', { 'id': str(self.folder.id), 'rating': i }, skip_post = True)
|
||||
self.assertEqual(self.folder.as_subsonic_child(self.user)['userRating'], i)
|
||||
self._make_request('setRating', { 'id': str(self.folder.id), 'rating': 0 }, skip_post = True)
|
||||
self.assertNotIn('userRating', self.folder.as_subsonic_child(self.user))
|
||||
self._make_request('setRating', { 'id': str(self.folderid), 'rating': i }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertEqual(Folder[self.folderid].as_subsonic_child(self.user)['userRating'], i)
|
||||
self._make_request('setRating', { 'id': str(self.folderid), 'rating': 0 }, skip_post = True)
|
||||
with db_session:
|
||||
self.assertNotIn('userRating', Folder[self.folderid].as_subsonic_child(self.user))
|
||||
|
||||
def test_scrobble(self):
|
||||
self._make_request('scrobble', error = 10)
|
||||
self._make_request('scrobble', { 'id': 'song' }, error = 0)
|
||||
self._make_request('scrobble', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
self._make_request('scrobble', { 'id': str(self.folder.id) }, error = 70)
|
||||
self._make_request('scrobble', { 'id': str(self.folderid) }, error = 70)
|
||||
|
||||
self.skipTest('Weird request context/logger issue at exit')
|
||||
self._make_request('scrobble', { 'id': str(self.track.id) })
|
||||
self._make_request('scrobble', { 'id': str(self.track.id), 'submission': True })
|
||||
self._make_request('scrobble', { 'id': str(self.track.id), 'submission': False })
|
||||
self._make_request('scrobble', { 'id': str(self.trackid) })
|
||||
self._make_request('scrobble', { 'id': str(self.trackid), 'submission': True })
|
||||
self._make_request('scrobble', { 'id': str(self.trackid), 'submission': False })
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -9,10 +9,12 @@
|
||||
#
|
||||
# Distributed under terms of the GNU AGPLv3 license.
|
||||
|
||||
from lxml import etree
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Artist, Album, Track
|
||||
|
||||
from .apitestbase import ApiTestBase
|
||||
@ -21,64 +23,53 @@ class BrowseTestCase(ApiTestBase):
|
||||
def setUp(self):
|
||||
super(BrowseTestCase, self).setUp()
|
||||
|
||||
empty = Folder()
|
||||
empty.root = True
|
||||
empty.name = 'Empty root'
|
||||
empty.path = '/tmp'
|
||||
self.store.add(empty)
|
||||
with db_session:
|
||||
Folder(root = True, name = 'Empty root', path = '/tmp')
|
||||
root = Folder(root = True, name = 'Root folder', path = 'tests/assets')
|
||||
|
||||
root = Folder()
|
||||
root.root = True
|
||||
root.name = 'Root folder'
|
||||
root.path = 'tests/assets'
|
||||
self.store.add(root)
|
||||
for letter in 'ABC':
|
||||
folder = Folder(
|
||||
name = letter + 'rtist',
|
||||
path = 'tests/assets/{}rtist'.format(letter),
|
||||
parent = root
|
||||
)
|
||||
|
||||
for letter in 'ABC':
|
||||
folder = Folder()
|
||||
folder.name = letter + 'rtist'
|
||||
folder.path = 'tests/assets/{}rtist'.format(letter)
|
||||
folder.parent = root
|
||||
artist = Artist(name = letter + 'rtist')
|
||||
|
||||
artist = Artist()
|
||||
artist.name = letter + 'rtist'
|
||||
for lether in 'AB':
|
||||
afolder = Folder(
|
||||
name = letter + lether + 'lbum',
|
||||
path = 'tests/assets/{0}rtist/{0}{1}lbum'.format(letter, lether),
|
||||
parent = folder
|
||||
)
|
||||
|
||||
for lether in 'AB':
|
||||
afolder = Folder()
|
||||
afolder.name = letter + lether + 'lbum'
|
||||
afolder.path = 'tests/assets/{0}rtist/{0}{1}lbum'.format(letter, lether)
|
||||
afolder.parent = folder
|
||||
album = Album(name = letter + lether + 'lbum', artist = artist)
|
||||
|
||||
album = Album()
|
||||
album.name = letter + lether + 'lbum'
|
||||
album.artist = artist
|
||||
for num, song in enumerate([ 'One', 'Two', 'Three' ]):
|
||||
track = Track(
|
||||
disc = 1,
|
||||
number = num,
|
||||
title = song,
|
||||
duration = 2,
|
||||
album = album,
|
||||
artist = artist,
|
||||
bitrate = 320,
|
||||
path = 'tests/assets/{0}rtist/{0}{1}lbum/{2}'.format(letter, lether, song),
|
||||
content_type = 'audio/mpeg',
|
||||
last_modification = 0,
|
||||
root_folder = root,
|
||||
folder = afolder
|
||||
)
|
||||
|
||||
for num, song in enumerate([ 'One', 'Two', 'Three' ]):
|
||||
track = Track()
|
||||
track.disc = 1
|
||||
track.number = num
|
||||
track.title = song
|
||||
track.duration = 2
|
||||
track.album = album
|
||||
track.artist = artist
|
||||
track.bitrate = 320
|
||||
track.path = 'tests/assets/{0}rtist/{0}{1}lbum/{2}'.format(letter, lether, song)
|
||||
track.content_type = 'audio/mpeg'
|
||||
track.last_modification = 0
|
||||
track.root_folder = root
|
||||
track.folder = afolder
|
||||
self.store.add(track)
|
||||
|
||||
self.store.commit()
|
||||
|
||||
self.assertEqual(self.store.find(Folder).count(), 11)
|
||||
self.assertEqual(self.store.find(Folder, Folder.root == True).count(), 2)
|
||||
self.assertEqual(self.store.find(Artist).count(), 3)
|
||||
self.assertEqual(self.store.find(Album).count(), 6)
|
||||
self.assertEqual(self.store.find(Track).count(), 18)
|
||||
self.assertEqual(Folder.select().count(), 11)
|
||||
self.assertEqual(Folder.select(lambda f: f.root).count(), 2)
|
||||
self.assertEqual(Artist.select().count(), 3)
|
||||
self.assertEqual(Album.select().count(), 6)
|
||||
self.assertEqual(Track.select().count(), 18)
|
||||
|
||||
def test_get_music_folders(self):
|
||||
# Do not validate against the XSD here, this is the only place where the API should return ids as ints
|
||||
# all our ids are uuids :/
|
||||
# all our ids are uuids :/
|
||||
rv, child = self._make_request('getMusicFolders', tag = 'musicFolders', skip_xsd = True)
|
||||
self.assertEqual(len(child), 2)
|
||||
self.assertSequenceEqual(sorted(self._xpath(child, './musicFolder/@name')), [ 'Empty root', 'Root folder' ])
|
||||
@ -91,7 +82,8 @@ class BrowseTestCase(ApiTestBase):
|
||||
rv, child = self._make_request('getIndexes', { 'ifModifiedSince': int(time.time() * 1000 + 1000) }, tag = 'indexes')
|
||||
self.assertEqual(len(child), 0)
|
||||
|
||||
fid = self.store.find(Folder, Folder.name == 'Empty root').one().id
|
||||
with db_session:
|
||||
fid = Folder.get(name = 'Empty root').id
|
||||
rv, child = self._make_request('getIndexes', { 'musicFolderId': str(fid) }, tag = 'indexes')
|
||||
self.assertEqual(len(child), 0)
|
||||
|
||||
@ -108,18 +100,19 @@ class BrowseTestCase(ApiTestBase):
|
||||
self._make_request('getMusicDirectory', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
# should test with folders with both children folders and tracks. this code would break in that case
|
||||
for f in self.store.find(Folder):
|
||||
rv, child = self._make_request('getMusicDirectory', { 'id': str(f.id) }, tag = 'directory')
|
||||
self.assertEqual(child.get('id'), str(f.id))
|
||||
self.assertEqual(child.get('name'), f.name)
|
||||
self.assertEqual(len(child), f.children.count() + f.tracks.count())
|
||||
for dbc, xmlc in zip(sorted(f.children, key = lambda c: c.name), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(dbc.name, xmlc.get('title'))
|
||||
self.assertEqual(xmlc.get('artist'), f.name)
|
||||
self.assertEqual(xmlc.get('parent'), str(f.id))
|
||||
for t, xmlc in zip(sorted(f.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(t.title, xmlc.get('title'))
|
||||
self.assertEqual(xmlc.get('parent'), str(f.id))
|
||||
with db_session:
|
||||
for f in Folder.select():
|
||||
rv, child = self._make_request('getMusicDirectory', { 'id': str(f.id) }, tag = 'directory')
|
||||
self.assertEqual(child.get('id'), str(f.id))
|
||||
self.assertEqual(child.get('name'), f.name)
|
||||
self.assertEqual(len(child), f.children.count() + f.tracks.count())
|
||||
for dbc, xmlc in zip(sorted(f.children, key = lambda c: c.name), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(dbc.name, xmlc.get('title'))
|
||||
self.assertEqual(xmlc.get('artist'), f.name)
|
||||
self.assertEqual(xmlc.get('parent'), str(f.id))
|
||||
for t, xmlc in zip(sorted(f.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(t.title, xmlc.get('title'))
|
||||
self.assertEqual(xmlc.get('parent'), str(f.id))
|
||||
|
||||
def test_get_artists(self):
|
||||
# same as getIndexes standard case
|
||||
@ -138,38 +131,41 @@ class BrowseTestCase(ApiTestBase):
|
||||
self._make_request('getArtist', { 'id': 'artist' }, error = 0)
|
||||
self._make_request('getArtist', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
for ar in self.store.find(Artist):
|
||||
rv, child = self._make_request('getArtist', { 'id': str(ar.id) }, tag = 'artist')
|
||||
self.assertEqual(child.get('id'), str(ar.id))
|
||||
self.assertEqual(child.get('albumCount'), str(len(child)))
|
||||
self.assertEqual(len(child), ar.albums.count())
|
||||
for dal, xal in zip(sorted(ar.albums, key = lambda a: a.name), sorted(child, key = lambda c: c.get('name'))):
|
||||
self.assertEqual(dal.name, xal.get('name'))
|
||||
self.assertEqual(xal.get('artist'), ar.name) # could break with a better dataset
|
||||
self.assertEqual(xal.get('artistId'), str(ar.id)) # see above
|
||||
with db_session:
|
||||
for ar in Artist.select():
|
||||
rv, child = self._make_request('getArtist', { 'id': str(ar.id) }, tag = 'artist')
|
||||
self.assertEqual(child.get('id'), str(ar.id))
|
||||
self.assertEqual(child.get('albumCount'), str(len(child)))
|
||||
self.assertEqual(len(child), ar.albums.count())
|
||||
for dal, xal in zip(sorted(ar.albums, key = lambda a: a.name), sorted(child, key = lambda c: c.get('name'))):
|
||||
self.assertEqual(dal.name, xal.get('name'))
|
||||
self.assertEqual(xal.get('artist'), ar.name) # could break with a better dataset
|
||||
self.assertEqual(xal.get('artistId'), str(ar.id)) # see above
|
||||
|
||||
def test_get_album(self):
|
||||
self._make_request('getAlbum', error = 10)
|
||||
self._make_request('getAlbum', { 'id': 'nastynasty' }, error = 0)
|
||||
self._make_request('getAlbum', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
a = self.store.find(Album)[0]
|
||||
rv, child = self._make_request('getAlbum', { 'id': str(a.id) }, tag = 'album')
|
||||
self.assertEqual(child.get('id'), str(a.id))
|
||||
self.assertEqual(child.get('songCount'), str(len(child)))
|
||||
with db_session:
|
||||
a = Album.select().first()
|
||||
rv, child = self._make_request('getAlbum', { 'id': str(a.id) }, tag = 'album')
|
||||
self.assertEqual(child.get('id'), str(a.id))
|
||||
self.assertEqual(child.get('songCount'), str(len(child)))
|
||||
|
||||
self.assertEqual(len(child), a.tracks.count())
|
||||
for dal, xal in zip(sorted(a.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(dal.title, xal.get('title'))
|
||||
self.assertEqual(xal.get('album'), a.name)
|
||||
self.assertEqual(xal.get('albumId'), str(a.id))
|
||||
self.assertEqual(len(child), a.tracks.count())
|
||||
for dal, xal in zip(sorted(a.tracks, key = lambda t: t.title), sorted(child, key = lambda c: c.get('title'))):
|
||||
self.assertEqual(dal.title, xal.get('title'))
|
||||
self.assertEqual(xal.get('album'), a.name)
|
||||
self.assertEqual(xal.get('albumId'), str(a.id))
|
||||
|
||||
def test_get_song(self):
|
||||
self._make_request('getSong', error = 10)
|
||||
self._make_request('getSong', { 'id': 'nastynasty' }, error = 0)
|
||||
self._make_request('getSong', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
s = self.store.find(Track)[0]
|
||||
with db_session:
|
||||
s = Track.select().first()
|
||||
self._make_request('getSong', { 'id': str(s.id) }, tag = 'song')
|
||||
|
||||
def test_get_videos(self):
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
import os.path
|
||||
import uuid
|
||||
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Artist, Album, Track
|
||||
|
||||
@ -22,69 +24,69 @@ class MediaTestCase(ApiTestBase):
|
||||
def setUp(self):
|
||||
super(MediaTestCase, self).setUp()
|
||||
|
||||
self.folder = Folder()
|
||||
self.folder.name = 'Root'
|
||||
self.folder.path = os.path.abspath('tests/assets')
|
||||
self.folder.root = True
|
||||
self.folder.has_cover_art = True # 420x420 PNG
|
||||
with db_session:
|
||||
folder = Folder(
|
||||
name = 'Root',
|
||||
path = os.path.abspath('tests/assets'),
|
||||
root = True,
|
||||
has_cover_art = True # 420x420 PNG
|
||||
)
|
||||
self.folderid = folder.id
|
||||
|
||||
artist = Artist()
|
||||
artist.name = 'Artist'
|
||||
artist = Artist(name = 'Artist')
|
||||
album = Album(artist = artist, name = 'Album')
|
||||
|
||||
album = Album()
|
||||
album.artist = artist
|
||||
album.name = 'Album'
|
||||
|
||||
self.track = Track()
|
||||
self.track.title = '23bytes'
|
||||
self.track.number = 1
|
||||
self.track.disc = 1
|
||||
self.track.artist = artist
|
||||
self.track.album = album
|
||||
self.track.path = os.path.abspath('tests/assets/23bytes')
|
||||
self.track.root_folder = self.folder
|
||||
self.track.folder = self.folder
|
||||
self.track.duration = 2
|
||||
self.track.bitrate = 320
|
||||
self.track.content_type = 'audio/mpeg'
|
||||
self.track.last_modification = 0
|
||||
|
||||
self.store.add(self.track)
|
||||
self.store.commit()
|
||||
track = Track(
|
||||
title = '23bytes',
|
||||
number = 1,
|
||||
disc = 1,
|
||||
artist = artist,
|
||||
album = album,
|
||||
path = os.path.abspath('tests/assets/23bytes'),
|
||||
root_folder = folder,
|
||||
folder = folder,
|
||||
duration = 2,
|
||||
bitrate = 320,
|
||||
content_type = 'audio/mpeg',
|
||||
last_modification = 0
|
||||
)
|
||||
self.trackid = track.id
|
||||
|
||||
def test_stream(self):
|
||||
self._make_request('stream', error = 10)
|
||||
self._make_request('stream', { 'id': 'string' }, error = 0)
|
||||
self._make_request('stream', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
self._make_request('stream', { 'id': str(self.folder.id) }, error = 70)
|
||||
self._make_request('stream', { 'id': str(self.track.id), 'maxBitRate': 'string' }, error = 0)
|
||||
self._make_request('stream', { 'id': str(self.folderid) }, error = 70)
|
||||
self._make_request('stream', { 'id': str(self.trackid), 'maxBitRate': 'string' }, error = 0)
|
||||
|
||||
rv = self.client.get('/rest/stream.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.track.id) })
|
||||
rv = self.client.get('/rest/stream.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.trackid) })
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(rv.mimetype, 'audio/mpeg')
|
||||
self.assertEqual(len(rv.data), 23)
|
||||
self.assertEqual(self.track.play_count, 1)
|
||||
with db_session:
|
||||
self.assertEqual(Track[self.trackid].play_count, 1)
|
||||
|
||||
def test_download(self):
|
||||
self._make_request('download', error = 10)
|
||||
self._make_request('download', { 'id': 'string' }, error = 0)
|
||||
self._make_request('download', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
self._make_request('download', { 'id': str(self.folder.id) }, error = 70)
|
||||
self._make_request('download', { 'id': str(self.folderid) }, error = 70)
|
||||
|
||||
rv = self.client.get('/rest/download.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.track.id) })
|
||||
rv = self.client.get('/rest/download.view', query_string = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.trackid) })
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(rv.mimetype, 'audio/mpeg')
|
||||
self.assertEqual(len(rv.data), 23)
|
||||
self.assertEqual(self.track.play_count, 0)
|
||||
with db_session:
|
||||
self.assertEqual(Track[self.trackid].play_count, 0)
|
||||
|
||||
def test_get_cover_art(self):
|
||||
self._make_request('getCoverArt', error = 10)
|
||||
self._make_request('getCoverArt', { 'id': 'string' }, error = 0)
|
||||
self._make_request('getCoverArt', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
self._make_request('getCoverArt', { 'id': str(self.track.id) }, error = 70)
|
||||
self._make_request('getCoverArt', { 'id': str(self.folder.id), 'size': 'large' }, error = 0)
|
||||
self._make_request('getCoverArt', { 'id': str(self.trackid) }, error = 70)
|
||||
self._make_request('getCoverArt', { 'id': str(self.folderid), 'size': 'large' }, error = 0)
|
||||
|
||||
args = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.folder.id) }
|
||||
args = { 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'id': str(self.folderid) }
|
||||
rv = self.client.get('/rest/getCoverArt.view', query_string = args)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(rv.mimetype, 'image/jpeg')
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Artist, Album, Track, Playlist, User
|
||||
|
||||
from .apitestbase import ApiTestBase
|
||||
@ -19,63 +21,42 @@ class PlaylistTestCase(ApiTestBase):
|
||||
def setUp(self):
|
||||
super(PlaylistTestCase, self).setUp()
|
||||
|
||||
root = Folder()
|
||||
root.root = True
|
||||
root.name = 'Root folder'
|
||||
root.path = 'tests/assets'
|
||||
self.store.add(root)
|
||||
with db_session:
|
||||
root = Folder(root = True, name = 'Root folder', path = 'tests/assets')
|
||||
artist = Artist(name = 'Artist')
|
||||
album = Album(name = 'Album', artist = artist)
|
||||
|
||||
artist = Artist()
|
||||
artist.name = 'Artist'
|
||||
songs = {}
|
||||
for num, song in enumerate([ 'One', 'Two', 'Three', 'Four' ]):
|
||||
track = Track(
|
||||
disc = 1,
|
||||
number = num,
|
||||
title = song,
|
||||
duration = 2,
|
||||
album = album,
|
||||
artist = artist,
|
||||
bitrate = 320,
|
||||
path = 'tests/assets/' + song,
|
||||
content_type = 'audio/mpeg',
|
||||
last_modification = 0,
|
||||
root_folder = root,
|
||||
folder = root
|
||||
)
|
||||
songs[song] = track
|
||||
|
||||
album = Album()
|
||||
album.name = 'Album'
|
||||
album.artist = artist
|
||||
users = { u.name: u for u in User.select() }
|
||||
|
||||
songs = {}
|
||||
for num, song in enumerate([ 'One', 'Two', 'Three', 'Four' ]):
|
||||
track = Track()
|
||||
track.disc = 1
|
||||
track.number = num
|
||||
track.title = song
|
||||
track.duration = 2
|
||||
track.album = album
|
||||
track.artist = artist
|
||||
track.bitrate = 320
|
||||
track.path = 'tests/assets/empty'
|
||||
track.content_type = 'audio/mpeg'
|
||||
track.last_modification = 0
|
||||
track.root_folder = root
|
||||
track.folder = root
|
||||
playlist = Playlist(user = users['alice'], name = "Alice's")
|
||||
playlist.add(songs['One'])
|
||||
playlist.add(songs['Three'])
|
||||
|
||||
self.store.add(track)
|
||||
songs[song] = track
|
||||
playlist = Playlist(user = users['alice'], public = True, name = "Alice's public")
|
||||
playlist.add(songs['One'])
|
||||
playlist.add(songs['Two'])
|
||||
|
||||
users = { u.name: u for u in self.store.find(User) }
|
||||
|
||||
playlist = Playlist()
|
||||
playlist.user = users['alice']
|
||||
playlist.name = "Alice's"
|
||||
playlist.add(songs['One'])
|
||||
playlist.add(songs['Three'])
|
||||
self.store.add(playlist)
|
||||
|
||||
playlist = Playlist()
|
||||
playlist.user = users['alice']
|
||||
playlist.public = True
|
||||
playlist.name = "Alice's public"
|
||||
playlist.add(songs['One'])
|
||||
playlist.add(songs['Two'])
|
||||
self.store.add(playlist)
|
||||
|
||||
playlist = Playlist()
|
||||
playlist.user = users['bob']
|
||||
playlist.name = "Bob's"
|
||||
playlist.add(songs['Two'])
|
||||
playlist.add(songs['Four'])
|
||||
self.store.add(playlist)
|
||||
|
||||
self.store.commit()
|
||||
playlist = Playlist(user = users['bob'], name = "Bob's")
|
||||
playlist.add(songs['Two'])
|
||||
playlist.add(songs['Four'])
|
||||
|
||||
def test_get_playlists(self):
|
||||
# get own playlists
|
||||
@ -113,7 +94,8 @@ class PlaylistTestCase(ApiTestBase):
|
||||
self._make_request('getPlaylist', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
# other's private from non admin
|
||||
playlist = self.store.find(Playlist, Playlist.public == False, Playlist.user_id == User.id, User.name == 'alice').one()
|
||||
with db_session:
|
||||
playlist = Playlist.get(lambda p: not p.public == False and p.user.name == 'alice')
|
||||
self._make_request('getPlaylist', { 'u': 'bob', 'p': 'B0b', 'id': str(playlist.id) }, error = 50)
|
||||
|
||||
# standard
|
||||
@ -156,9 +138,11 @@ class PlaylistTestCase(ApiTestBase):
|
||||
self._make_request('createPlaylist', { 'u': 'bob', 'p': 'B0b', 'playlistId': playlist.get('id') }, error = 50)
|
||||
|
||||
# create more useful playlist
|
||||
songs = { s.title: str(s.id) for s in self.store.find(Track) }
|
||||
with db_session:
|
||||
songs = { s.title: str(s.id) for s in Track.select() }
|
||||
self._make_request('createPlaylist', { 'name': 'songs', 'songId': map(lambda s: songs[s], [ 'Three', 'One', 'Two' ]) }, skip_post = True)
|
||||
playlist = self.store.find(Playlist, Playlist.name == 'songs').one()
|
||||
with db_session:
|
||||
playlist = Playlist.get(name = 'songs')
|
||||
self.assertIsNotNone(playlist)
|
||||
rv, child = self._make_request('getPlaylist', { 'id': str(playlist.id) }, tag = 'playlist')
|
||||
self.assertEqual(child.get('songCount'), '3')
|
||||
@ -174,6 +158,10 @@ class PlaylistTestCase(ApiTestBase):
|
||||
self.assertEqual(self._xpath(child, 'count(./entry)'), 1)
|
||||
self.assertEqual(child[0].get('title'), 'Two')
|
||||
|
||||
@db_session
|
||||
def assertPlaylistCountEqual(self, count):
|
||||
self.assertEqual(Playlist.select().count(), count)
|
||||
|
||||
def test_delete_playlist(self):
|
||||
# check params
|
||||
self._make_request('deletePlaylist', error = 10)
|
||||
@ -181,27 +169,30 @@ class PlaylistTestCase(ApiTestBase):
|
||||
self._make_request('deletePlaylist', { 'id': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
# delete unowned when not admin
|
||||
playlist = self.store.find(Playlist, Playlist.user_id == User.id, User.name == 'alice')[0]
|
||||
with db_session:
|
||||
playlist = Playlist.select(lambda p: p.user.name == 'alice').first()
|
||||
self._make_request('deletePlaylist', { 'u': 'bob', 'p': 'B0b', 'id': str(playlist.id) }, error = 50)
|
||||
self.assertEqual(self.store.find(Playlist).count(), 3)
|
||||
self.assertPlaylistCountEqual(3);
|
||||
|
||||
# delete owned
|
||||
self._make_request('deletePlaylist', { 'id': str(playlist.id) }, skip_post = True)
|
||||
self.assertEqual(self.store.find(Playlist).count(), 2)
|
||||
self.assertPlaylistCountEqual(2);
|
||||
self._make_request('deletePlaylist', { 'id': str(playlist.id) }, error = 70)
|
||||
self.assertEqual(self.store.find(Playlist).count(), 2)
|
||||
self.assertPlaylistCountEqual(2);
|
||||
|
||||
# delete unowned when admin
|
||||
playlist = self.store.find(Playlist, Playlist.user_id == User.id, User.name == 'bob').one()
|
||||
with db_session:
|
||||
playlist = Playlist.get(lambda p: p.user.name == 'bob')
|
||||
self._make_request('deletePlaylist', { 'id': str(playlist.id) }, skip_post = True)
|
||||
self.assertEqual(self.store.find(Playlist).count(), 1)
|
||||
self.assertPlaylistCountEqual(1);
|
||||
|
||||
def test_update_playlist(self):
|
||||
self._make_request('updatePlaylist', error = 10)
|
||||
self._make_request('updatePlaylist', { 'playlistId': 1234 }, error = 0)
|
||||
self._make_request('updatePlaylist', { 'playlistId': str(uuid.uuid4()) }, error = 70)
|
||||
|
||||
playlist = self.store.find(Playlist, Playlist.user_id == User.id, User.name == 'alice')[0]
|
||||
with db_session:
|
||||
playlist = Playlist.select(lambda p: p.user.name == 'alice').order_by(Playlist.created).first()
|
||||
pid = str(playlist.id)
|
||||
self._make_request('updatePlaylist', { 'playlistId': pid, 'songIdToAdd': 'string' }, error = 0)
|
||||
self._make_request('updatePlaylist', { 'playlistId': pid, 'songIndexToRemove': 'string' }, error = 0)
|
||||
@ -226,7 +217,8 @@ class PlaylistTestCase(ApiTestBase):
|
||||
self.assertEqual(self._xpath(child, 'count(./entry)'), 1)
|
||||
self.assertEqual(self._find(child, './entry').get('title'), 'Three')
|
||||
|
||||
songs = { s.title: str(s.id) for s in self.store.find(Track) }
|
||||
with db_session:
|
||||
songs = { s.title: str(s.id) for s in Track.select() }
|
||||
|
||||
self._make_request('updatePlaylist', { 'playlistId': pid, 'songIdToAdd': [ songs['One'], songs['Two'], songs['Two'] ] }, skip_post = True)
|
||||
rv, child = self._make_request('getPlaylist', { 'id': pid }, tag = 'playlist')
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from pony.orm import db_session
|
||||
|
||||
from supysonic.db import Folder, Track
|
||||
from supysonic.managers.folder import FolderManager
|
||||
from supysonic.scanner import Scanner
|
||||
@ -23,12 +25,13 @@ class TranscodingTestCase(ApiTestBase):
|
||||
|
||||
super(TranscodingTestCase, self).setUp()
|
||||
|
||||
FolderManager.add(self.store, 'Folder', 'tests/assets/folder')
|
||||
scanner = Scanner(self.store)
|
||||
scanner.scan(self.store.find(Folder).one())
|
||||
scanner.finish()
|
||||
FolderManager.add('Folder', 'tests/assets/folder')
|
||||
scanner = Scanner()
|
||||
with db_session:
|
||||
scanner.scan(Folder.get())
|
||||
scanner.finish()
|
||||
|
||||
self.trackid = self.store.find(Track).one().id
|
||||
self.trackid = Track.get().id
|
||||
|
||||
def _stream(self, **kwargs):
|
||||
kwargs.update({ 'u': 'alice', 'p': 'Alic3', 'c': 'tests', 'v': '1.8.0', 'id': self.trackid })
|
||||
|
Loading…
Reference in New Issue
Block a user