From 53fd4865cb0c67a2f356c114594217a823ed6c35 Mon Sep 17 00:00:00 2001 From: spl0k Date: Thu, 21 Dec 2017 23:29:00 +0100 Subject: [PATCH] Get on my pony --- supysonic/api/__init__.py | 20 ++++----- supysonic/api/albums_songs.py | 3 -- supysonic/api/annotation.py | 1 - supysonic/api/browse.py | 1 - supysonic/api/chat.py | 21 +++++---- supysonic/api/media.py | 1 - supysonic/api/playlists.py | 2 - supysonic/api/search.py | 82 ++++++++++++++++++----------------- supysonic/api/user.py | 19 ++++---- supysonic/db.py | 4 +- tests/__init__.py | 5 ++- tests/api/test_search.py | 75 +++++++++++++++----------------- tests/testbase.py | 6 +-- 13 files changed, 115 insertions(+), 125 deletions(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 1db12b0..d8e0c96 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -18,15 +18,15 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import binascii import simplejson import uuid -import binascii from flask import request, current_app as app +from pony.orm import db_session, ObjectNotFound from xml.dom import minidom from xml.etree import ElementTree -from ..web import store from ..managers.user import UserManager @app.before_request @@ -70,7 +70,7 @@ def authorize(): error = request.error_formatter(40, 'Unauthorized'), 401 if request.authorization: - status, user = UserManager.try_auth(store, request.authorization.username, request.authorization.password) + status, user = UserManager.try_auth(request.authorization.username, request.authorization.password) if status == UserManager.SUCCESS: request.username = request.authorization.username request.user = user @@ -81,7 +81,7 @@ def authorize(): return error password = decode_password(password) - status, user = UserManager.try_auth(store, username, password) + status, user = UserManager.try_auth(username, password) if status != UserManager.SUCCESS: return error @@ -97,13 +97,11 @@ def get_client_prefs(): 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() + with db_session: + try: + prefs = ClientPrefs[request.user.id, client] + except ObjectNotFound: + prefs = ClientPrefs(user = User[request.user.id], client_name = client) request.prefs = prefs diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py index 90f22ab..acc60c0 100644 --- a/supysonic/api/albums_songs.py +++ b/supysonic/api/albums_songs.py @@ -23,12 +23,9 @@ import uuid from datetime import timedelta from flask import request, current_app as app -from storm.expr import Desc, Avg, Min, Max -from storm.info import ClassAlias from ..db import Folder, Artist, Album, Track, RatingFolder, StarredFolder, StarredArtist, StarredAlbum, StarredTrack, User from ..db import now -from ..web import store @app.route('/rest/getRandomSongs.view', methods = [ 'GET', 'POST' ]) def rand_songs(): diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 215c52b..16e958a 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -27,7 +27,6 @@ from ..db import Track, Album, Artist, Folder from ..db import StarredTrack, StarredAlbum, StarredArtist, StarredFolder from ..db import RatingTrack, RatingFolder from ..lastfm import LastFm -from ..web import store from . import get_entity diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index eed9db7..0f597f7 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -24,7 +24,6 @@ import uuid from flask import request, current_app as app from ..db import Folder, Artist, Album, Track -from ..web import store from . import get_entity diff --git a/supysonic/api/chat.py b/supysonic/api/chat.py index bab6c1d..9f5cdef 100644 --- a/supysonic/api/chat.py +++ b/supysonic/api/chat.py @@ -19,9 +19,9 @@ # along with this program. If not, see . from flask import request, current_app as app +from pony.orm import db_session -from ..db import ChatMessage -from ..web import store +from ..db import ChatMessage, User @app.route('/rest/getChatMessages.view', methods = [ 'GET', 'POST' ]) def get_chat(): @@ -31,11 +31,12 @@ def get_chat(): except: return request.error_formatter(0, 'Invalid parameter') - query = store.find(ChatMessage).order_by(ChatMessage.time) - if since: - query = query.find(ChatMessage.time > since) + with db_session: + query = ChatMessage.select().order_by(ChatMessage.time) + if since: + query = query.filter(lambda m: m.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(): @@ -43,10 +44,8 @@ def add_chat_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() + with db_session: + ChatMessage(user = User[request.user.id], message = msg) + return request.formatter({}) diff --git a/supysonic/api/media.py b/supysonic/api/media.py index 53beca5..3f91dc3 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -29,7 +29,6 @@ from PIL import Image from xml.etree import ElementTree from .. import scanner -from ..web import store from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now from . import get_entity diff --git a/supysonic/api/playlists.py b/supysonic/api/playlists.py index 78c7c0d..61ea77c 100644 --- a/supysonic/api/playlists.py +++ b/supysonic/api/playlists.py @@ -21,10 +21,8 @@ import uuid from flask import request, current_app as app -from storm.expr import Or from ..db import Playlist, User, Track -from ..web import store from . import get_entity diff --git a/supysonic/api/search.py b/supysonic/api/search.py index 49d0c1d..08d535b 100644 --- a/supysonic/api/search.py +++ b/supysonic/api/search.py @@ -20,10 +20,9 @@ from datetime import datetime from flask import request, current_app as app -from storm.info import ClassAlias +from pony.orm import db_session, select from ..db import Folder, Track, Artist, Album -from ..web import store @app.route('/rest/search.view', methods = [ 'GET', 'POST' ]) def old_search(): @@ -38,34 +37,36 @@ def old_search(): min_date = datetime.fromtimestamp(newer_than) if artist: - parent = ClassAlias(Folder) - query = store.find(parent, Folder.parent_id == parent.id, Track.folder_id == Folder.id, parent.name.contains_string(artist), parent.created > min_date).config(distinct = True) + query = select(t.folder.parent for t in Track if artist in t.folder.parent.name and t.folder.parent.created > min_date) elif album: - query = store.find(Folder, Track.folder_id == Folder.id, Folder.name.contains_string(album), Folder.created > min_date).config(distinct = True) + query = select(t.folder for t in Track if album in t.folder.name and t.folder.created > min_date) elif title: - query = store.find(Track, Track.title.contains_string(title), Track.created > min_date) + query = Track.select(lambda t: title in t.title and t.created > min_date) elif anyf: - folders = store.find(Folder, Folder.name.contains_string(anyf), Folder.created > min_date) - tracks = store.find(Track, Track.title.contains_string(anyf), Track.created > min_date) - 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]) + folders = Folder.select(lambda f: anyf in f.name and f.created > min_date) + tracks = Track.select(lambda t: anyf in t.title and t.created > min_date) + with db_session: + res = folders[offset : offset + count] + fcount = folders.count() + if offset + count > fcount: + toff = max(0, offset - fcount) + tend = offset + count - fcount + res += tracks[toff : tend] - 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 ] - }}) + 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 ] + }}) 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 isinstance(r, Folder) else r.as_subsonic_child(request.user, request.prefs) for r in query[offset : offset + count] ] - }}) + with db_session: + 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] ] + }}) @app.route('/rest/search2.view', methods = [ 'GET', 'POST' ]) def new_search(): @@ -85,16 +86,16 @@ def new_search(): 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] + with db_session: + artists = select(t.folder.parent for t in Track if query in t.folder.parent.name).limit(artist_count, artist_offset) + albums = select(t.folder for t in Track if query in t.folder.name).limit(album_count, album_offset) + songs = Track.select(lambda t: query in t.title).limit(song_count, song_offset) - 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 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 ] + }}) @app.route('/rest/search3.view', methods = [ 'GET', 'POST' ]) def search_id3(): @@ -114,13 +115,14 @@ def search_id3(): 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] + with db_session: + artists = Artist.select(lambda a: query in a.name).limit(artist_count, artist_offset) + albums = Album.select(lambda a: query in a.name).limit(album_count, album_offset) + songs = Track.select(lambda t: query in t.title).limit(song_count, song_offset) - 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 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 ] + }}) diff --git a/supysonic/api/user.py b/supysonic/api/user.py index 00e45d1..43ca1e1 100644 --- a/supysonic/api/user.py +++ b/supysonic/api/user.py @@ -19,10 +19,10 @@ # along with this program. If not, see . from flask import request, current_app as app +from pony.orm import db_session from ..db import User from ..managers.user import UserManager -from ..web import store from . import decode_password @@ -35,7 +35,8 @@ def user_info(): if username != request.username and not request.user.admin: return request.error_formatter(50, 'Admin restricted') - user = store.find(User, User.name == username).one() + with db_session: + user = User.get(name = username) if user is None: return request.error_formatter(70, 'Unknown user') @@ -46,7 +47,8 @@ def users_info(): 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) ] } }) + with db_session: + return request.formatter({ 'users': { 'user': [ u.as_subsonic_user() for u in User.select() ] } }) @app.route('/rest/createUser.view', methods = [ 'GET', 'POST' ]) def user_add(): @@ -59,7 +61,7 @@ def user_add(): admin = True if admin in (True, 'True', 'true', 1, '1') else False password = decode_password(password) - status = UserManager.add(store, username, password, email, admin) + status = UserManager.add(username, password, email, admin) if status == UserManager.NAME_EXISTS: return request.error_formatter(0, 'There is already a user with that username') @@ -74,11 +76,12 @@ def user_del(): if not username: return request.error_formatter(10, 'Missing parameter') - 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, 'Unknown user') - status = UserManager.delete(store, user.id) + status = UserManager.delete(user.id) if status != UserManager.SUCCESS: return request.error_formatter(0, UserManager.error_str(status)) @@ -94,7 +97,7 @@ def user_changepass(): return request.error_formatter(50, 'Admin restricted') password = decode_password(password) - status = UserManager.change_password2(store, username, password) + status = UserManager.change_password2(username, password) if status != UserManager.SUCCESS: code = 0 if status == UserManager.NO_SUCH_USER: diff --git a/supysonic/db.py b/supysonic/db.py index 9b1da74..b8adfe7 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -130,8 +130,8 @@ class Album(db.Entity): 'created': min(self.tracks.created).isoformat() } - track_with_cover = self.tracks.select(lambda t: t.folder.has_cover_art)[:1][0] - if track_with_cover: + track_with_cover = self.tracks.select(lambda t: t.folder.has_cover_art).first() + if track_with_cover is not None: info['coverArt'] = str(track_with_cover.folder.id) try: diff --git a/tests/__init__.py b/tests/__init__.py index 80a348f..7087cc1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,7 +11,10 @@ import unittest -from . import base, managers, api, frontend +from . import base +from . import managers +from . import api +from . import frontend def suite(): suite = unittest.TestSuite() diff --git a/tests/api/test_search.py b/tests/api/test_search.py index 8a52268..2935136 100644 --- a/tests/api/test_search.py +++ b/tests/api/test_search.py @@ -12,6 +12,8 @@ import time import unittest +from pony.orm import db_session, commit + from supysonic.db import Folder, Artist, Album, Track from .apitestbase import ApiTestBase @@ -20,53 +22,44 @@ class SearchTestCase(ApiTestBase): def setUp(self): super(SearchTestCase, 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') - for letter in 'ABC': - folder = Folder() - folder.name = letter + 'rtist' - folder.path = 'tests/assets/{}rtist'.format(letter) - folder.parent = root + for letter in 'ABC': + folder = Folder(name = letter + 'rtist', path = 'tests/assets/{}rtist'.format(letter), 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) + commit() - self.store.commit() - - self.assertEqual(self.store.find(Folder).count(), 10) - 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(), 10) + self.assertEqual(Artist.select().count(), 3) + self.assertEqual(Album.select().count(), 6) + self.assertEqual(Track.select().count(), 18) def __track_as_pseudo_unique_str(self, elem): return elem.get('artist') + elem.get('album') + elem.get('title') diff --git a/tests/testbase.py b/tests/testbase.py index 1ae5330..c604fab 100644 --- a/tests/testbase.py +++ b/tests/testbase.py @@ -68,8 +68,8 @@ class TestBase(unittest.TestCase): release_database() app = create_application(config) - #self.__ctx = app.app_context() - #self.__ctx.push() + self.__ctx = app.app_context() + self.__ctx.push() self.client = app.test_client() @@ -83,7 +83,7 @@ class TestBase(unittest.TestCase): return False def tearDown(self): - #self.__ctx.pop() + self.__ctx.pop() release_database() shutil.rmtree(self.__dir) os.remove(self.__dbfile)