From 3e51bf2a6d42b60c4a2335023a5ce74fdda41465 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sat, 14 Sep 2019 12:39:05 +0200 Subject: [PATCH 1/9] Change music folder IDs to int --- supysonic/api/browse.py | 5 ++++- supysonic/config.py | 7 ++++++- supysonic/db.py | 2 +- supysonic/managers/folder.py | 16 +++++++--------- supysonic/schema/sqlite.sql | 12 ++++++------ 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index 72949f5..fed98ee 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -42,7 +42,10 @@ def list_indexes(): if musicFolderId is None: folders = Folder.select(lambda f: f.root)[:] else: - mfid = uuid.UUID(musicFolderId) + try: + mfid = int(musicFolderId) + except ValueError: + raise ValueError("Invalid folder ID") folder = Folder[mfid] if not folder.root: raise ObjectNotFound(Folder, mfid) diff --git a/supysonic/config.py b/supysonic/config.py index a83616d..de9d5ea 100644 --- a/supysonic/config.py +++ b/supysonic/config.py @@ -90,8 +90,13 @@ class IniConfig(DefaultConfig): lv = value.lower() if lv in ("yes", "true", "on"): return True - elif lv in ("no", "false", "off"): + if lv in ("no", "false", "off"): return False + try: + if isinstance(value, unicode): + return str(value) + except NameError: + pass return value @classmethod diff --git a/supysonic/db.py b/supysonic/db.py index d23ac53..f079dfb 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -79,7 +79,7 @@ class PathMixin(object): class Folder(PathMixin, db.Entity): _table_ = "folder" - id = PrimaryKey(UUID, default=uuid4) + id = PrimaryKey(int, auto=True) root = Required(bool, default=False) name = Required(str, autostrip=False) path = Required(str, 4096, autostrip=False) # unique diff --git a/supysonic/managers/folder.py b/supysonic/managers/folder.py index db8cb46..eede1da 100644 --- a/supysonic/managers/folder.py +++ b/supysonic/managers/folder.py @@ -21,15 +21,13 @@ from ..py23 import strtype class FolderManager: @staticmethod - def get(uid): - if isinstance(uid, strtype): - uid = uuid.UUID(uid) - elif isinstance(uid, uuid.UUID): - pass - else: + def get(id): + try: + id = int(id) + except ValueError: raise ValueError("Invalid folder id") - return Folder[uid] + return Folder[id] @staticmethod def add(name, path): @@ -55,8 +53,8 @@ class FolderManager: return folder @staticmethod - def delete(uid): - folder = FolderManager.get(uid) + def delete(id): + folder = FolderManager.get(id) if not folder.root: raise ObjectNotFound(Folder) diff --git a/supysonic/schema/sqlite.sql b/supysonic/schema/sqlite.sql index 23a7ff8..40e8441 100644 --- a/supysonic/schema/sqlite.sql +++ b/supysonic/schema/sqlite.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS folder ( - id CHAR(36) PRIMARY KEY, + id INTEGER NOT NULL PRIMARY KEY, root BOOLEAN NOT NULL, name VARCHAR(256) NOT NULL COLLATE NOCASE, path VARCHAR(4096) NOT NULL, @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder ( created DATETIME NOT NULL, cover_art VARCHAR(256), last_scan INTEGER NOT NULL, - parent_id CHAR(36) REFERENCES folder + parent_id INTEGER REFERENCES folder ); CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); CREATE UNIQUE INDEX IF NOT EXISTS index_folder_path ON folder(path_hash); @@ -42,8 +42,8 @@ CREATE TABLE IF NOT EXISTS track ( last_modification INTEGER NOT NULL, play_count INTEGER NOT NULL, last_play DATETIME, - root_folder_id CHAR(36) NOT NULL REFERENCES folder, - folder_id CHAR(36) NOT NULL REFERENCES folder + root_folder_id INTEGER NOT NULL REFERENCES folder, + folder_id INTEGER NOT NULL REFERENCES folder ); CREATE INDEX IF NOT EXISTS index_track_album_id_fk ON track(album_id); CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_id); @@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS client_prefs ( CREATE TABLE IF NOT EXISTS starred_folder ( user_id CHAR(36) NOT NULL REFERENCES user, - starred_id CHAR(36) NOT NULL REFERENCES folder, + starred_id INTEGER NOT NULL REFERENCES folder, date DATETIME NOT NULL, PRIMARY KEY (user_id, starred_id) ); @@ -111,7 +111,7 @@ CREATE INDEX IF NOT EXISTS index_starred_track_starred_id_fk ON starred_track(st CREATE TABLE IF NOT EXISTS rating_folder ( user_id CHAR(36) NOT NULL REFERENCES user, - rated_id CHAR(36) NOT NULL REFERENCES folder, + rated_id INTEGER NOT NULL REFERENCES folder, rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5), PRIMARY KEY (user_id, rated_id) ); From c3fd94343fbac27b26823a63cb531cf71ef4c511 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sat, 14 Sep 2019 22:53:55 +0200 Subject: [PATCH 2/9] Fix api get_entity --- supysonic/api/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 3427acf..a6f560f 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -17,6 +17,7 @@ from flask import Blueprint from pony.orm import ObjectNotFound from pony.orm import commit +from ..db import Folder from ..managers.user import UserManager from ..py23 import dict @@ -82,7 +83,10 @@ def get_client_prefs(): def get_entity(cls, param="id"): eid = request.values[param] - eid = uuid.UUID(eid) + if cls == Folder: + eid = int(eid) + else: + eid = uuid.UUID(eid) entity = cls[eid] return entity From 99ce42c9ffa6673bba29f77d90a5754615c5926c Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 15 Sep 2019 11:46:32 +0200 Subject: [PATCH 3/9] Fix Folder ID bugs, add initial DB migration work --- supysonic/api/__init__.py | 10 ++ supysonic/api/albums_songs.py | 9 +- supysonic/api/annotation.py | 20 +-- supysonic/api/browse.py | 7 +- supysonic/api/media.py | 5 +- supysonic/schema/migration/mysql/20190915.sql | 70 ++++++++++ .../schema/migration/sqlite/20190915.sql | 129 ++++++++++++++++++ supysonic/schema/mysql.sql | 12 +- supysonic/schema/postgres.sql | 12 +- supysonic/utils.py | 3 +- 10 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 supysonic/schema/migration/mysql/20190915.sql create mode 100644 supysonic/schema/migration/sqlite/20190915.sql diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index a6f560f..7d8e485 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -91,6 +91,16 @@ def get_entity(cls, param="id"): return entity +def get_entity_id(cls, eid): + """Return the entity ID as its proper type.""" + if cls == Folder: + try: + return int(eid) + except ValueError: + raise ValueError("Invalid Folder ID: %s", eid) + return uuid.UUID(eid) + + from .errors import * from .system import * diff --git a/supysonic/api/albums_songs.py b/supysonic/api/albums_songs.py index 4d502ad..0e84a5d 100644 --- a/supysonic/api/albums_songs.py +++ b/supysonic/api/albums_songs.py @@ -7,8 +7,6 @@ # # Distributed under terms of the GNU AGPLv3 license. -import uuid - from datetime import timedelta from flask import request from pony.orm import select, desc, avg, max, min, count @@ -42,7 +40,12 @@ def rand_songs(): size = int(size) if size else 10 fromYear = int(fromYear) if fromYear else None toYear = int(toYear) if toYear else None - fid = uuid.UUID(musicFolderId) if musicFolderId else None + fid = None + if musicFolderId: + try: + fid = int(musicFolderId) + except ValueError: + raise ValueError("Invalid folder ID") query = Track.select() if fromYear: diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 08612fb..27d777f 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -21,7 +21,7 @@ from ..db import RatingTrack, RatingFolder from ..lastfm import LastFm from ..py23 import dict -from . import api, get_entity +from . import api, get_entity, get_entity_id from .exceptions import AggregateException, GenericError, MissingParameter, NotFound @@ -32,7 +32,7 @@ def star_single(cls, eid): :param eid: id of the entity to star """ - uid = uuid.UUID(eid) + uid = get_entity_id(cls, eid) e = cls[uid] starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) @@ -52,7 +52,7 @@ def unstar_single(cls, eid): :param eid: id of the entity to unstar """ - uid = uuid.UUID(eid) + uid = get_entity_id(cls, eid) starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) delete( s for s in starred_cls if s.user.id == request.user.id and s.starred.id == uid @@ -115,7 +115,9 @@ def rate(): id = request.values["id"] rating = request.values["rating"] - uid = uuid.UUID(id) + tid = get_entity_id(Track, id) + fid = get_entity_id(Folder, id) + uid = None rating = int(rating) if not 0 <= rating <= 5: @@ -123,21 +125,23 @@ def rate(): if rating == 0: delete( - r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == uid + r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == tid ) delete( r for r in RatingFolder - if r.user.id == request.user.id and r.rated.id == uid + if r.user.id == request.user.id and r.rated.id == fid ) else: try: - rated = Track[uid] + rated = Track[tid] rating_cls = RatingTrack + uid = tid except ObjectNotFound: try: - rated = Folder[uid] + rated = Folder[fid] rating_cls = RatingFolder + uid = fid except ObjectNotFound: raise NotFound("Track or Folder") diff --git a/supysonic/api/browse.py b/supysonic/api/browse.py index fed98ee..884b066 100644 --- a/supysonic/api/browse.py +++ b/supysonic/api/browse.py @@ -16,7 +16,7 @@ from pony.orm import ObjectNotFound, select from ..db import Folder, Artist, Album, Track from ..py23 import dict -from . import api, get_entity +from . import api, get_entity, get_entity_id @api.route("/getMusicFolders.view", methods=["GET", "POST"]) @@ -42,10 +42,7 @@ def list_indexes(): if musicFolderId is None: folders = Folder.select(lambda f: f.root)[:] else: - try: - mfid = int(musicFolderId) - except ValueError: - raise ValueError("Invalid folder ID") + mfid = get_entity_id(Folder, musicFolderId) folder = Folder[mfid] if not folder.root: raise ObjectNotFound(Folder, mfid) diff --git a/supysonic/api/media.py b/supysonic/api/media.py index 71825fc..c26763c 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -187,7 +187,8 @@ def stream_media(): @api.route("/download.view", methods=["GET", "POST"]) def download_media(): id = request.values["id"] - uid = uuid.UUID(id) + uid = get_entity_id(Track, id) + fid = get_entity_id(Folder, id) try: # Track -> direct download rv = Track[uid] @@ -196,7 +197,7 @@ def download_media(): pass try: # Folder -> stream zipped tracks, non recursive - rv = Folder[uid] + rv = Folder[fid] except ObjectNotFound: try: # Album -> stream zipped tracks rv = Album[uid] diff --git a/supysonic/schema/migration/mysql/20190915.sql b/supysonic/schema/migration/mysql/20190915.sql new file mode 100644 index 0000000..6b258e4 --- /dev/null +++ b/supysonic/schema/migration/mysql/20190915.sql @@ -0,0 +1,70 @@ +START TRANSACTION; + +CREATE TEMPORARY TABLE IF NOT EXISTS folder_id_to_int ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + uuid BINARY(16) NOT NULL +); + +INSERT INTO folder_id_to_int(uuid) SELECT id FROM folder; + +DROP INDEX index_folder_parent_id_fk ON folder; +DROP INDEX index_track_folder_id_fk ON track; +DROP INDEX index_track_root_folder_id_fk ON track; +DROP INDEX index_starred_folder_starred_id_fk ON starred_folder; +DROP INDEX index_rating_folder_rated_id_fk ON rating_folder; + + +ALTER TABLE folder + ADD int_id INTEGER AFTER id, + ADD int_parent_id INTEGER REFERENCES folder AFTER parent_id; +UPDATE folder SET int_id = (SELECT id FROM folder_id_to_int WHERE uuid == folder.id); +UPDATE folder SET int_parent_id = (SELECT id FROM folder_id_to_int WHERE uuid == folder.parent_id); +ALTER TABLE folder + DROP PRIMARY KEY, + DROP COLUMN id, + DROP COLUMN parent_id, + RENAME COLUMN int_id TO id, + RENAME COLUMN int_parent_id TO parent_id, + MODIFY id INTEGER AUTO_INCREMENT, + ADD PRIMARY KEY (id); + + +ALTER TABLE track + ADD int_root_folder_id INTEGER NOT NULL REFERENCES folder AFTER root_folder_id, + ADD int_folder_id INTEGER NOT NULL REFERENCES folder AFTER folder_id; +UPDATE track SET int_root_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid == track.root_folder_id); +UPDATE track SET int_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid == track.folder_id); +ALTER TABLE track + DROP COLUMN root_folder_id, + DROP COLUMN folder_id, + RENAME COLUMN int_root_folder_id TO root_folder_id, + RENAME COLUMN int_folder_id TO folder_id, + + +ALTER TABLE starred_folder ADD int_starred_id INTEGER NOT NULL REFERENCES folder AFTER starred_id; +UPDATE starred_folder SET int_starred_id = (SELECT id FROM folder_id_to_int WHERE uuid == starred_folder.starred_id); +ALTER TABLE starred_folder + DROP PRIMARY KEY, + DROP COLUMN starred_id, + RENAME COLUMN int_starred_id TO starred_id, + ADD PRIMARY KEY (user_id, starred_id); + + +ALTER TABLE rating_folder ADD int_rated_id INTEGER NOT NULL REFERENCES folder AFTER rated_id; +UPDATE rating_folder SET int_rated_id = (SELECT id FROM folder_id_to_int WHERE uuid == rating_folder.rated_id); +ALTER TABLE rating_folder + DROP PRIMARY KEY, + DROP COLUMN rated_id, + RENAME COLUMN int_rated_id TO rated_id, + ADD PRIMARY KEY (user_id, rated_id); + + +CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); +CREATE INDEX IF NOT EXISTS index_track_folder_id_fk ON track(folder_id); +CREATE INDEX IF NOT EXISTS index_track_root_folder_id_fk ON track(root_folder_id); +CREATE INDEX IF NOT EXISTS index_starred_folder_starred_id_fk ON starred_folder(starred_id); +CREATE INDEX IF NOT EXISTS index_rating_folder_rated_id_fk ON rating_folder(rated_id); + +DROP TABLE folder_id_to_int; + +COMMIT; diff --git a/supysonic/schema/migration/sqlite/20190915.sql b/supysonic/schema/migration/sqlite/20190915.sql new file mode 100644 index 0000000..4823e2e --- /dev/null +++ b/supysonic/schema/migration/sqlite/20190915.sql @@ -0,0 +1,129 @@ +COMMIT; +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; + +CREATE TEMPORARY TABLE IF NOT EXISTS folder_id_to_int ( + id INTEGER NOT NULL PRIMARY KEY, + uuid CHAR(36) NOT NULL +); + +INSERT INTO folder_id_to_int(uuid) SELECT id FROM folder; + +DROP INDEX index_folder_parent_id_fk; +DROP INDEX index_folder_path; + +CREATE TABLE IF NOT EXISTS folder_new ( + id INTEGER NOT NULL PRIMARY KEY, + root BOOLEAN NOT NULL, + name VARCHAR(256) NOT NULL COLLATE NOCASE, + path VARCHAR(4096) NOT NULL, + path_hash BLOB NOT NULL, + created DATETIME NOT NULL, + cover_art VARCHAR(256), + last_scan INTEGER NOT NULL, + parent_id INTEGER REFERENCES folder +); +CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder_new(parent_id); +CREATE UNIQUE INDEX IF NOT EXISTS index_folder_path ON folder_new(path_hash); + +INSERT INTO folder_new(id, root, name, path, path_hash, created, cover_art, last_scan, parent_id) +SELECT id_int.id, root, name, path, path_hash, created, cover_art, last_scan, parent_id_int.id +FROM folder +JOIN folder_id_to_int id_int ON folder.id == id_int.uuid +JOIN folder_id_to_int parent_id_int ON folder.parent_id == parent_id_int.uuid; + +DROP TABLE folder; +ALTER TABLE folder_new RENAME TO folder; + + +DROP INDEX index_track_album_id_fk; +DROP INDEX index_track_artist_id_fk; +DROP INDEX index_track_folder_id_fk; +DROP INDEX index_track_root_folder_id_fk; +DROP INDEX index_track_path; + +CREATE TABLE IF NOT EXISTS track_new ( + id CHAR(36) PRIMARY KEY, + disc INTEGER NOT NULL, + number INTEGER NOT NULL, + title VARCHAR(256) NOT NULL COLLATE NOCASE, + year INTEGER, + genre VARCHAR(256), + duration INTEGER NOT NULL, + has_art BOOLEAN NOT NULL DEFAULT false, + album_id CHAR(36) NOT NULL REFERENCES album, + artist_id CHAR(36) NOT NULL REFERENCES artist, + bitrate INTEGER NOT NULL, + path VARCHAR(4096) NOT NULL, + path_hash BLOB NOT NULL, + created DATETIME NOT NULL, + last_modification INTEGER NOT NULL, + play_count INTEGER NOT NULL, + last_play DATETIME, + root_folder_id INTEGER NOT NULL REFERENCES folder, + folder_id INTEGER NOT NULL REFERENCES folder +); +CREATE INDEX IF NOT EXISTS index_track_album_id_fk ON track_new(album_id); +CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track_new(artist_id); +CREATE INDEX IF NOT EXISTS index_track_folder_id_fk ON track_new(folder_id); +CREATE INDEX IF NOT EXISTS index_track_root_folder_id_fk ON track_new(root_folder_id); +CREATE UNIQUE INDEX IF NOT EXISTS index_track_path ON track_new(path_hash); + +INSERT INTO track_new(id, disc, number, title, year, genre, duration, has_art, album_id, artist_id, bitrate, path, path_hash, created, last_modification, play_count, last_play, root_folder_id, folder_id) +SELECT track.id, disc, number, title, year, genre, duration, has_art, album_id, artist_id, bitrate, path, path_hash, created, last_modification, play_count, last_play, root_id_int.id, folder_id_int.id +FROM track +JOIN folder_id_to_int root_id_int ON track.root_folder_id == root_id_int.uuid +JOIN folder_id_to_int folder_id_int ON track.folder_id == folder_id_int.uuid; + +DROP TABLE track; +ALTER TABLE track_new RENAME TO track; + + +DROP INDEX index_starred_folder_user_id_fk; +DROP INDEX index_starred_folder_starred_id_fk; + +CREATE TABLE IF NOT EXISTS starred_folder_new ( + user_id CHAR(36) NOT NULL REFERENCES user, + starred_id INTEGER NOT NULL REFERENCES folder, + date DATETIME NOT NULL, + PRIMARY KEY (user_id, starred_id) +); +CREATE INDEX IF NOT EXISTS index_starred_folder_user_id_fk ON starred_folder_new(user_id); +CREATE INDEX IF NOT EXISTS index_starred_folder_starred_id_fk ON starred_folder_new(starred_id); + +INSERT INTO starred_folder_new(user_id, starred_id, date) +SELECT user_id, id_int.id, date +FROM starred_folder +JOIN folder_id_to_int id_int ON starred_folder_new.starred_id == id_int.uuid; + +DROP TABLE starred_folder; +ALTER TABLE starred_folder_new RENAME TO starred_folder; + + +DROP INDEX index_rating_folder_user_id_fk; +DROP INDEX index_rating_folder_rated_id_fk; + +CREATE TABLE IF NOT EXISTS rating_folder_new ( + user_id CHAR(36) NOT NULL REFERENCES user, + rated_id INTEGER NOT NULL REFERENCES folder, + rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5), + PRIMARY KEY (user_id, rated_id) +); +CREATE INDEX IF NOT EXISTS index_rating_folder_user_id_fk ON rating_folder_new(user_id); +CREATE INDEX IF NOT EXISTS index_rating_folder_rated_id_fk ON rating_folder_new(rated_id); + +INSERT INTO rating_folder_new(user_id, rated_id, rating) +SELECT user_id, id_int.id, rating +FROM rating_folder +JOIN folder_id_to_int id_int ON rating_folder.rated_id == id_int.uuid; + +DROP TABLE rating_folder; +ALTER TABLE rating_folder_new RENAME TO rating_folder; + + +DROP TABLE folder_id_to_int; + +COMMIT; +VACUUM; +PRAGMA foreign_keys = ON; +BEGIN TRANSACTION; diff --git a/supysonic/schema/mysql.sql b/supysonic/schema/mysql.sql index 864b2a8..ebe0820 100644 --- a/supysonic/schema/mysql.sql +++ b/supysonic/schema/mysql.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS folder ( - id BINARY(16) PRIMARY KEY, + id INTEGER PRIMARY KEY AUTO_INCREMENT, root BOOLEAN NOT NULL, name VARCHAR(256) NOT NULL, path VARCHAR(4096) NOT NULL, @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder ( created DATETIME NOT NULL, cover_art VARCHAR(256), last_scan INTEGER NOT NULL, - parent_id BINARY(16) REFERENCES folder + parent_id INTEGER REFERENCES folder ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); @@ -41,8 +41,8 @@ CREATE TABLE IF NOT EXISTS track ( last_modification INTEGER NOT NULL, play_count INTEGER NOT NULL, last_play DATETIME, - root_folder_id BINARY(16) NOT NULL REFERENCES folder, - folder_id BINARY(16) NOT NULL REFERENCES folder + root_folder_id INTEGER NOT NULL REFERENCES folder, + folder_id INTEGER NOT NULL REFERENCES folder ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE INDEX IF NOT EXISTS index_track_album_id_fk ON track(album_id); CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_id); @@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS client_prefs ( CREATE TABLE IF NOT EXISTS starred_folder ( user_id BINARY(16) NOT NULL REFERENCES user, - starred_id BINARY(16) NOT NULL REFERENCES folder, + starred_id INTEGER NOT NULL REFERENCES folder, date DATETIME NOT NULL, PRIMARY KEY (user_id, starred_id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -109,7 +109,7 @@ CREATE INDEX IF NOT EXISTS index_starred_track_starred_id_fk ON starred_track(st CREATE TABLE IF NOT EXISTS rating_folder ( user_id BINARY(16) NOT NULL REFERENCES user, - rated_id BINARY(16) NOT NULL REFERENCES folder, + rated_id INTEGER NOT NULL REFERENCES folder, rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5), PRIMARY KEY (user_id, rated_id) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/supysonic/schema/postgres.sql b/supysonic/schema/postgres.sql index 95c9e94..f763148 100644 --- a/supysonic/schema/postgres.sql +++ b/supysonic/schema/postgres.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS folder ( - id UUID PRIMARY KEY, + id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, root BOOLEAN NOT NULL, name CITEXT NOT NULL, path VARCHAR(4096) NOT NULL, @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder ( created TIMESTAMP NOT NULL, cover_art VARCHAR(256), last_scan INTEGER NOT NULL, - parent_id UUID REFERENCES folder + parent_id INTEGER REFERENCES folder ); CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); @@ -41,8 +41,8 @@ CREATE TABLE IF NOT EXISTS track ( last_modification INTEGER NOT NULL, play_count INTEGER NOT NULL, last_play TIMESTAMP, - root_folder_id UUID NOT NULL REFERENCES folder, - folder_id UUID NOT NULL REFERENCES folder + root_folder_id INTEGER NOT NULL REFERENCES folder, + folder_id INTEGER NOT NULL REFERENCES folder ); CREATE INDEX IF NOT EXISTS index_track_album_id_fk ON track(album_id); CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_id); @@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS client_prefs ( CREATE TABLE IF NOT EXISTS starred_folder ( user_id UUID NOT NULL REFERENCES "user", - starred_id UUID NOT NULL REFERENCES folder, + starred_id INTEGER NOT NULL REFERENCES folder, date TIMESTAMP NOT NULL, PRIMARY KEY (user_id, starred_id) ); @@ -109,7 +109,7 @@ CREATE INDEX IF NOT EXISTS index_starred_track_starred_id_fk ON starred_track(st CREATE TABLE IF NOT EXISTS rating_folder ( user_id UUID NOT NULL REFERENCES "user", - rated_id UUID NOT NULL REFERENCES folder, + rated_id INTEGER NOT NULL REFERENCES folder, rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5), PRIMARY KEY (user_id, rated_id) ); diff --git a/supysonic/utils.py b/supysonic/utils.py index eb0fbbe..f621ca0 100644 --- a/supysonic/utils.py +++ b/supysonic/utils.py @@ -6,12 +6,13 @@ # Copyright (C) 2019 Alban 'spl0k' FĂ©ron # # Distributed under terms of the GNU AGPLv3 license. +import uuid from base64 import b64encode, b64decode from os import urandom from pony.orm import db_session, commit, ObjectNotFound -from supysonic.db import Meta +from supysonic.db import Folder, Meta @db_session From 81192bfeca83073462243934b8c12c544a113d51 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 15 Sep 2019 13:48:25 +0200 Subject: [PATCH 4/9] Fix Folder ID bugs and sqlite migration. --- supysonic/api/__init__.py | 7 +++++-- supysonic/api/media.py | 10 +++++++--- supysonic/db.py | 2 +- supysonic/schema/migration/sqlite/20190915.sql | 11 +++++++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 7d8e485..93c70f5 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -97,8 +97,11 @@ def get_entity_id(cls, eid): try: return int(eid) except ValueError: - raise ValueError("Invalid Folder ID: %s", eid) - return uuid.UUID(eid) + return None + try: + return uuid.UUID(eid) + except ValueError: + return None from .errors import * diff --git a/supysonic/api/media.py b/supysonic/api/media.py index c26763c..45a1639 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -35,7 +35,7 @@ from ..covers import get_embedded_cover from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now from ..py23 import dict -from . import api, get_entity +from . import api, get_entity, get_entity_id from .exceptions import ( GenericError, MissingParameter, @@ -215,15 +215,19 @@ def download_media(): @api.route("/getCoverArt.view", methods=["GET", "POST"]) def cover_art(): cache = current_app.cache + eid = request.values["id"] - if Folder.exists(id=eid): + fid = get_entity_id(Folder, eid) + tid = get_entity_id(Track, eid) + + if fid and Folder.exists(id=eid): res = get_entity(Folder) if not res.cover_art or not os.path.isfile( os.path.join(res.path, res.cover_art) ): raise NotFound("Cover art") cover_path = os.path.join(res.path, res.cover_art) - elif Track.exists(id=eid): + elif tid and Track.exists(id=eid): cache_key = "{}-cover".format(eid) try: cover_path = cache.get(cache_key) diff --git a/supysonic/db.py b/supysonic/db.py index f079dfb..2da1152 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -29,7 +29,7 @@ try: except ImportError: from urlparse import urlparse, parse_qsl -SCHEMA_VERSION = "20190518" +SCHEMA_VERSION = "20190915" def now(): diff --git a/supysonic/schema/migration/sqlite/20190915.sql b/supysonic/schema/migration/sqlite/20190915.sql index 4823e2e..2e0062e 100644 --- a/supysonic/schema/migration/sqlite/20190915.sql +++ b/supysonic/schema/migration/sqlite/20190915.sql @@ -26,11 +26,18 @@ CREATE TABLE IF NOT EXISTS folder_new ( CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder_new(parent_id); CREATE UNIQUE INDEX IF NOT EXISTS index_folder_path ON folder_new(path_hash); +INSERT INTO folder_new(id, root, name, path, path_hash, created, cover_art, last_scan, parent_id) +SELECT id_int.id, root, name, path, path_hash, created, cover_art, last_scan, NULL +FROM folder +JOIN folder_id_to_int id_int ON folder.id == id_int.uuid +WHERE folder.parent_id IS NULL; + INSERT INTO folder_new(id, root, name, path, path_hash, created, cover_art, last_scan, parent_id) SELECT id_int.id, root, name, path, path_hash, created, cover_art, last_scan, parent_id_int.id FROM folder JOIN folder_id_to_int id_int ON folder.id == id_int.uuid -JOIN folder_id_to_int parent_id_int ON folder.parent_id == parent_id_int.uuid; +JOIN folder_id_to_int parent_id_int ON folder.parent_id == parent_id_int.uuid +WHERE folder.parent_id IS NOT NULL; DROP TABLE folder; ALTER TABLE folder_new RENAME TO folder; @@ -94,7 +101,7 @@ CREATE INDEX IF NOT EXISTS index_starred_folder_starred_id_fk ON starred_folder_ INSERT INTO starred_folder_new(user_id, starred_id, date) SELECT user_id, id_int.id, date FROM starred_folder -JOIN folder_id_to_int id_int ON starred_folder_new.starred_id == id_int.uuid; +JOIN folder_id_to_int id_int ON starred_folder.starred_id == id_int.uuid; DROP TABLE starred_folder; ALTER TABLE starred_folder_new RENAME TO starred_folder; From 2df35026db90b4afe416d43e5731e3685acf8a9f Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 15 Sep 2019 14:35:58 +0200 Subject: [PATCH 5/9] Fix mysql migration --- supysonic/schema/migration/mysql/20190915.sql | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/supysonic/schema/migration/mysql/20190915.sql b/supysonic/schema/migration/mysql/20190915.sql index 6b258e4..664fd3d 100644 --- a/supysonic/schema/migration/mysql/20190915.sql +++ b/supysonic/schema/migration/mysql/20190915.sql @@ -15,47 +15,46 @@ DROP INDEX index_rating_folder_rated_id_fk ON rating_folder; ALTER TABLE folder - ADD int_id INTEGER AFTER id, + ADD int_id INTEGER REFERENCES folder AFTER id, ADD int_parent_id INTEGER REFERENCES folder AFTER parent_id; -UPDATE folder SET int_id = (SELECT id FROM folder_id_to_int WHERE uuid == folder.id); -UPDATE folder SET int_parent_id = (SELECT id FROM folder_id_to_int WHERE uuid == folder.parent_id); +UPDATE folder SET int_id = (SELECT id FROM folder_id_to_int WHERE uuid = folder.id); +UPDATE folder SET int_parent_id = (SELECT id FROM folder_id_to_int WHERE uuid = folder.parent_id); ALTER TABLE folder DROP PRIMARY KEY, DROP COLUMN id, DROP COLUMN parent_id, - RENAME COLUMN int_id TO id, - RENAME COLUMN int_parent_id TO parent_id, - MODIFY id INTEGER AUTO_INCREMENT, + CHANGE COLUMN int_id id INTEGER AUTO_INCREMENT, + CHANGE COLUMN int_parent_id parent_id INTEGER, ADD PRIMARY KEY (id); ALTER TABLE track ADD int_root_folder_id INTEGER NOT NULL REFERENCES folder AFTER root_folder_id, ADD int_folder_id INTEGER NOT NULL REFERENCES folder AFTER folder_id; -UPDATE track SET int_root_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid == track.root_folder_id); -UPDATE track SET int_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid == track.folder_id); +UPDATE track SET int_root_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid = track.root_folder_id); +UPDATE track SET int_folder_id = (SELECT id FROM folder_id_to_int WHERE uuid = track.folder_id); ALTER TABLE track DROP COLUMN root_folder_id, DROP COLUMN folder_id, - RENAME COLUMN int_root_folder_id TO root_folder_id, - RENAME COLUMN int_folder_id TO folder_id, + CHANGE COLUMN int_root_folder_id root_folder_id INTEGER NOT NULL, + CHANGE COLUMN int_folder_id folder_id INTEGER NOT NULL; ALTER TABLE starred_folder ADD int_starred_id INTEGER NOT NULL REFERENCES folder AFTER starred_id; -UPDATE starred_folder SET int_starred_id = (SELECT id FROM folder_id_to_int WHERE uuid == starred_folder.starred_id); +UPDATE starred_folder SET int_starred_id = (SELECT id FROM folder_id_to_int WHERE uuid = starred_folder.starred_id); ALTER TABLE starred_folder DROP PRIMARY KEY, DROP COLUMN starred_id, - RENAME COLUMN int_starred_id TO starred_id, + CHANGE COLUMN int_starred_id starred_id INTEGER NOT NULL, ADD PRIMARY KEY (user_id, starred_id); ALTER TABLE rating_folder ADD int_rated_id INTEGER NOT NULL REFERENCES folder AFTER rated_id; -UPDATE rating_folder SET int_rated_id = (SELECT id FROM folder_id_to_int WHERE uuid == rating_folder.rated_id); +UPDATE rating_folder SET int_rated_id = (SELECT id FROM folder_id_to_int WHERE uuid = rating_folder.rated_id); ALTER TABLE rating_folder DROP PRIMARY KEY, DROP COLUMN rated_id, - RENAME COLUMN int_rated_id TO rated_id, + CHANGE COLUMN int_rated_id rated_id INTEGER NOT NULL, ADD PRIMARY KEY (user_id, rated_id); From 1f8f3326e828401d79681ed1a83d7f9229fe8812 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Sun, 15 Sep 2019 15:38:02 +0200 Subject: [PATCH 6/9] Small fixes, postgres migration --- supysonic/api/__init__.py | 8 +- supysonic/schema/migration/mysql/20190915.sql | 2 +- .../schema/migration/postgres/20190915.sql | 83 +++++++++++++++++++ 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 supysonic/schema/migration/postgres/20190915.sql diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 93c70f5..972ccf3 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -82,11 +82,9 @@ def get_client_prefs(): def get_entity(cls, param="id"): - eid = request.values[param] - if cls == Folder: - eid = int(eid) - else: - eid = uuid.UUID(eid) + eid = get_entity_id(request.values[param]) + if eid is None: + return entity = cls[eid] return entity diff --git a/supysonic/schema/migration/mysql/20190915.sql b/supysonic/schema/migration/mysql/20190915.sql index 664fd3d..05e915b 100644 --- a/supysonic/schema/migration/mysql/20190915.sql +++ b/supysonic/schema/migration/mysql/20190915.sql @@ -15,7 +15,7 @@ DROP INDEX index_rating_folder_rated_id_fk ON rating_folder; ALTER TABLE folder - ADD int_id INTEGER REFERENCES folder AFTER id, + ADD int_id INTEGER AFTER id, ADD int_parent_id INTEGER REFERENCES folder AFTER parent_id; UPDATE folder SET int_id = (SELECT id FROM folder_id_to_int WHERE uuid = folder.id); UPDATE folder SET int_parent_id = (SELECT id FROM folder_id_to_int WHERE uuid = folder.parent_id); diff --git a/supysonic/schema/migration/postgres/20190915.sql b/supysonic/schema/migration/postgres/20190915.sql new file mode 100644 index 0000000..39d0678 --- /dev/null +++ b/supysonic/schema/migration/postgres/20190915.sql @@ -0,0 +1,83 @@ +START TRANSACTION; + +CREATE TEMPORARY TABLE IF NOT EXISTS folder_id_to_int ( + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + uid UUID NOT NULL +); + +INSERT INTO folder_id_to_int(uid) SELECT id FROM folder; + +ALTER TABLE folder DROP CONSTRAINT folder_parent_id_fkey; +ALTER TABLE rating_folder DROP CONSTRAINT rating_folder_rated_id_fkey; +ALTER TABLE starred_folder DROP CONSTRAINT starred_folder_starred_id_fkey; +ALTER TABLE track DROP CONSTRAINT track_folder_id_fkey; +ALTER TABLE track DROP CONSTRAINT track_root_folder_id_fkey; + + +ALTER TABLE folder + ADD int_id INTEGER, + ADD int_parent_id INTEGER; +UPDATE folder SET int_id = (SELECT id FROM folder_id_to_int WHERE uid = folder.id); +UPDATE folder SET int_parent_id = (SELECT id FROM folder_id_to_int WHERE uid = folder.parent_id); +CREATE SEQUENCE folder_id_seq AS INTEGER; +SELECT setval('folder_id_seq', coalesce(max(int_id), 0) + 1, false) FROM folder; +ALTER TABLE folder + DROP CONSTRAINT folder_pkey, + DROP COLUMN id, + DROP COLUMN parent_id, + ALTER COLUMN int_id SET DEFAULT nextval('folder_id_seq'), + ADD PRIMARY KEY (int_id); +ALTER TABLE folder RENAME COLUMN int_id TO id; +ALTER TABLE folder RENAME COLUMN int_parent_id TO parent_id; +ALTER TABLE folder ADD CONSTRAINT folder_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES folder(id); + + +ALTER TABLE track + ADD int_root_folder_id INTEGER, + ADD int_folder_id INTEGER; +UPDATE track SET int_root_folder_id = (SELECT id FROM folder_id_to_int WHERE uid = track.root_folder_id); +UPDATE track SET int_folder_id = (SELECT id FROM folder_id_to_int WHERE uid = track.folder_id); +ALTER TABLE track + DROP COLUMN root_folder_id, + DROP COLUMN folder_id, + ALTER COLUMN int_root_folder_id SET NOT NULL, + ALTER COLUMN int_folder_id SET NOT NULL; +ALTER TABLE track RENAME COLUMN int_root_folder_id TO root_folder_id; +ALTER TABLE track RENAME COLUMN int_folder_id TO folder_id; +ALTER TABLE track ADD CONSTRAINT track_folder_id_fkey FOREIGN KEY (folder_id) REFERENCES folder(id); +ALTER TABLE track ADD CONSTRAINT track_root_folder_id_fkey FOREIGN KEY (root_folder_id) REFERENCES folder(id); + + +ALTER TABLE starred_folder ADD int_starred_id INTEGER; +UPDATE starred_folder SET int_starred_id = (SELECT id FROM folder_id_to_int WHERE uid = starred_folder.starred_id); +ALTER TABLE starred_folder + DROP CONSTRAINT starred_folder_pkey, + DROP COLUMN starred_id, + ALTER COLUMN int_starred_id SET NOT NULL, + ADD PRIMARY KEY (user_id, int_starred_id); +ALTER TABLE starred_folder RENAME COLUMN int_starred_id TO starred_id; +ALTER TABLE starred_folder ADD CONSTRAINT starred_folder_starred_id_fkey FOREIGN KEY (starred_id) REFERENCES folder(id); + + +ALTER TABLE rating_folder ADD int_rated_id INTEGER; +UPDATE rating_folder SET int_rated_id = (SELECT id FROM folder_id_to_int WHERE uid = rating_folder.rated_id); +ALTER TABLE rating_folder + DROP CONSTRAINT rating_folder_pkey, + DROP COLUMN rated_id, + ALTER COLUMN int_rated_id SET NOT NULL, + ADD PRIMARY KEY (user_id, int_rated_id); +ALTER TABLE rating_folder RENAME COLUMN int_rated_id TO rated_id; +ALTER TABLE rating_folder ADD CONSTRAINT rating_folder_rated_id_fkey FOREIGN KEY (rated_id) REFERENCES folder(id); + + +CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); +CREATE INDEX IF NOT EXISTS index_track_folder_id_fk ON track(folder_id); +CREATE INDEX IF NOT EXISTS index_track_root_folder_id_fk ON track(root_folder_id); +CREATE INDEX IF NOT EXISTS index_starred_folder_starred_id_fk ON starred_folder(starred_id); +CREATE INDEX IF NOT EXISTS index_rating_folder_rated_id_fk ON rating_folder(rated_id); + + + +DROP TABLE folder_id_to_int; + +COMMIT; From 62fa440cfaecd7d38a51744c452225f9e75f56e9 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Tue, 1 Oct 2019 11:15:25 +0200 Subject: [PATCH 7/9] fix get_entity() --- supysonic/api/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 972ccf3..ecc419c 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -82,7 +82,7 @@ def get_client_prefs(): def get_entity(cls, param="id"): - eid = get_entity_id(request.values[param]) + eid = get_entity_id(cls, request.values[param]) if eid is None: return entity = cls[eid] From cc838f14d3376506d054623b260d3693300ecc00 Mon Sep 17 00:00:00 2001 From: mvn23 Date: Tue, 1 Oct 2019 16:55:23 +0200 Subject: [PATCH 8/9] Fix tests and some small bugs --- supysonic/api/__init__.py | 15 ++++---- supysonic/api/annotation.py | 49 ++++++++++++++++---------- supysonic/api/media.py | 50 +++++++++++++++++++-------- tests/api/test_album_songs.py | 2 +- tests/api/test_annotation.py | 10 ++++-- tests/api/test_browse.py | 4 +-- tests/api/test_media.py | 3 +- tests/base/test_db.py | 9 +++-- tests/frontend/test_folder.py | 8 ++--- tests/managers/test_manager_folder.py | 6 ++-- 10 files changed, 100 insertions(+), 56 deletions(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index ecc419c..0b35035 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -17,7 +17,6 @@ from flask import Blueprint from pony.orm import ObjectNotFound from pony.orm import commit -from ..db import Folder from ..managers.user import UserManager from ..py23 import dict @@ -82,9 +81,11 @@ def get_client_prefs(): def get_entity(cls, param="id"): - eid = get_entity_id(cls, request.values[param]) - if eid is None: - return + eid = request.values[param] + if cls == Folder: + eid = int(eid) + else: + eid = uuid.UUID(eid) entity = cls[eid] return entity @@ -95,11 +96,11 @@ def get_entity_id(cls, eid): try: return int(eid) except ValueError: - return None + raise GenericError("Invalid ID") try: return uuid.UUID(eid) - except ValueError: - return None + except (AttributeError, ValueError): + raise GenericError("Invalid ID") from .errors import * diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 27d777f..457383a 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -77,6 +77,11 @@ def handle_star_request(func): terr = e try: func(Folder, eid) + except GenericError as e: + if e.message == "Invalid ID" and isinstance(terr, ObjectNotFound): + ferr = NotFound("Folder not in database") + else: + ferr = e except Exception as e: ferr = e @@ -115,35 +120,43 @@ def rate(): id = request.values["id"] rating = request.values["rating"] - tid = get_entity_id(Track, id) - fid = get_entity_id(Folder, id) + try: + tid = get_entity_id(Track, id) + except GenericError: + tid = None + try: + fid = get_entity_id(Folder, id) + except GenericError: + fid = None uid = None rating = int(rating) + if tid is None and fid is None: + raise GenericError("Invalid ID") + if not 0 <= rating <= 5: raise GenericError("rating must be between 0 and 5 (inclusive)") if rating == 0: - delete( - r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == tid - ) - delete( - r - for r in RatingFolder - if r.user.id == request.user.id and r.rated.id == fid - ) + if tid is not None: + delete( + r for r in RatingTrack if r.user.id == request.user.id and r.rated.id == tid + ) + else: + delete( + r + for r in RatingFolder + if r.user.id == request.user.id and r.rated.id == fid + ) else: - try: + if tid is not None: rated = Track[tid] rating_cls = RatingTrack uid = tid - except ObjectNotFound: - try: - rated = Folder[fid] - rating_cls = RatingFolder - uid = fid - except ObjectNotFound: - raise NotFound("Track or Folder") + else: + rated = Folder[fid] + rating_cls = RatingFolder + uid = fid try: rating_info = rating_cls[request.user, uid] diff --git a/supysonic/api/media.py b/supysonic/api/media.py index 45a1639..26792ab 100644 --- a/supysonic/api/media.py +++ b/supysonic/api/media.py @@ -187,22 +187,33 @@ def stream_media(): @api.route("/download.view", methods=["GET", "POST"]) def download_media(): id = request.values["id"] - uid = get_entity_id(Track, id) - fid = get_entity_id(Folder, id) - try: # Track -> direct download - rv = Track[uid] - return send_file(rv.path, mimetype=rv.mimetype, conditional=True) - except ObjectNotFound: - pass + try: + uid = get_entity_id(Track, id) + except GenericError: + uid = None + try: + fid = get_entity_id(Folder, id) + except GenericError: + fid = None - try: # Folder -> stream zipped tracks, non recursive - rv = Folder[fid] - except ObjectNotFound: - try: # Album -> stream zipped tracks - rv = Album[uid] + if uid is None and fid is None: + raise GenericError("Invalid ID") + + if uid is not None: + try: + rv = Track[uid] + return send_file(rv.path, mimetype=rv.mimetype, conditional=True) except ObjectNotFound: - raise NotFound("Track, Folder or Album") + try: # Album -> stream zipped tracks + rv = Album[uid] + except ObjectNotFound: + raise NotFound("Track or Album") + else: + try: # Folder -> stream zipped tracks, non recursive + rv = Folder[fid] + except ObjectNotFound: + raise NotFound("Folder") z = ZipFile(compression=ZIP_DEFLATED) for track in rv.tracks: @@ -217,8 +228,17 @@ def cover_art(): cache = current_app.cache eid = request.values["id"] - fid = get_entity_id(Folder, eid) - tid = get_entity_id(Track, eid) + try: + fid = get_entity_id(Folder, eid) + except GenericError: + fid = None + try: + tid = get_entity_id(Track, eid) + except GenericError: + tid = None + + if not fid and not tid: + raise GenericError("Invalid ID") if fid and Folder.exists(id=eid): res = get_entity(Folder) diff --git a/tests/api/test_album_songs.py b/tests/api/test_album_songs.py index 10fefbc..3d86c81 100644 --- a/tests/api/test_album_songs.py +++ b/tests/api/test_album_songs.py @@ -118,7 +118,7 @@ class AlbumSongsTestCase(ApiTestBase): self._make_request("getRandomSongs", {"fromYear": "year"}, error=0) self._make_request("getRandomSongs", {"toYear": "year"}, error=0) self._make_request("getRandomSongs", {"musicFolderId": "idid"}, error=0) - self._make_request("getRandomSongs", {"musicFolderId": uuid.uuid4()}, error=70) + self._make_request("getRandomSongs", {"musicFolderId": 1234567890}, error=70) rv, child = self._make_request( "getRandomSongs", tag="randomSongs", skip_post=True diff --git a/tests/api/test_annotation.py b/tests/api/test_annotation.py index ef2b732..da4bc74 100644 --- a/tests/api/test_annotation.py +++ b/tests/api/test_annotation.py @@ -27,6 +27,10 @@ class AnnotationTestCase(ApiTestBase): artist = Artist(name="Artist") album = Album(name="Album", artist=artist) + # Populate folder ids + root = Folder.get(name="Root") + folder = Folder.get(name="Folder") + track = Track( title="Track", album=album, @@ -73,7 +77,7 @@ class AnnotationTestCase(ApiTestBase): 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.folderid)}, error=70) + self._make_request("star", {"albumId": str(self.folderid)}, error=0) 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) @@ -81,7 +85,7 @@ class AnnotationTestCase(ApiTestBase): 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.folderid)}, error=70) + self._make_request("star", {"artistId": str(self.folderid)}, error=0) 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) @@ -213,7 +217,7 @@ class AnnotationTestCase(ApiTestBase): 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.folderid)}, error=70) + self._make_request("scrobble", {"id": str(self.folderid)}, error=0) self._make_request("scrobble", {"id": str(self.trackid)}) self._make_request("scrobble", {"id": str(self.trackid), "submission": True}) diff --git a/tests/api/test_browse.py b/tests/api/test_browse.py index 6d40bd8..395ead1 100644 --- a/tests/api/test_browse.py +++ b/tests/api/test_browse.py @@ -82,7 +82,7 @@ class BrowseTestCase(ApiTestBase): def test_get_indexes(self): self._make_request("getIndexes", {"musicFolderId": "abcdef"}, error=0) - self._make_request("getIndexes", {"musicFolderId": str(uuid.uuid4())}, error=70) + self._make_request("getIndexes", {"musicFolderId": 1234567890}, error=70) self._make_request("getIndexes", {"ifModifiedSince": "quoi"}, error=0) rv, child = self._make_request( @@ -109,7 +109,7 @@ class BrowseTestCase(ApiTestBase): def test_get_music_directory(self): self._make_request("getMusicDirectory", error=10) self._make_request("getMusicDirectory", {"id": "id"}, error=0) - self._make_request("getMusicDirectory", {"id": str(uuid.uuid4())}, error=70) + self._make_request("getMusicDirectory", {"id": 1234567890}, error=70) # should test with folders with both children folders and tracks. this code would break in that case with db_session: diff --git a/tests/api/test_media.py b/tests/api/test_media.py index 42c14e3..b24f6ad 100644 --- a/tests/api/test_media.py +++ b/tests/api/test_media.py @@ -31,6 +31,7 @@ class MediaTestCase(ApiTestBase): root=True, cover_art="cover.jpg", ) + folder = Folder.get(name="Root") self.folderid = folder.id artist = Artist(name="Artist") @@ -74,7 +75,7 @@ class MediaTestCase(ApiTestBase): 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.folderid)}, error=70) + self._make_request("stream", {"id": str(self.folderid)}, error=0) self._make_request( "stream", {"id": str(self.trackid), "maxBitRate": "string"}, error=0 ) diff --git a/tests/base/test_db.py b/tests/base/test_db.py index 5251081..54768da 100644 --- a/tests/base/test_db.py +++ b/tests/base/test_db.py @@ -50,7 +50,12 @@ class DbTestCase(unittest.TestCase): parent=root_folder, ) - return root_folder, child_folder, child_2 + # Folder IDs don't get populated until we query the db. + return ( + db.Folder.get(name="Root folder"), + db.Folder.get(name="Child folder"), + db.Folder.get(name="Child Folder (No Art)") + ) def create_some_tracks(self, artist=None, album=None): root, child, child_2 = self.create_some_folders() @@ -209,7 +214,7 @@ class DbTestCase(unittest.TestCase): root_folder, folder_art, folder_noart = self.create_some_folders() track1 = self.create_track_in( - root_folder, folder_noart, artist=artist, album=album + folder_noart, root_folder, artist=artist, album=album ) album_dict = album.as_subsonic_album(user) diff --git a/tests/frontend/test_folder.py b/tests/frontend/test_folder.py index c9f3221..c0ca1ea 100644 --- a/tests/frontend/test_folder.py +++ b/tests/frontend/test_folder.py @@ -72,8 +72,8 @@ class FolderTestCase(FrontendTestBase): self._login("alice", "Alic3") rv = self.client.get("/folder/del/string", follow_redirects=True) - self.assertIn("badly formed", rv.data) - rv = self.client.get("/folder/del/" + str(uuid.uuid4()), follow_redirects=True) + self.assertIn("Invalid folder id", rv.data) + rv = self.client.get("/folder/del/1234567890", follow_redirects=True) self.assertIn("No such folder", rv.data) rv = self.client.get("/folder/del/" + str(folder.id), follow_redirects=True) self.assertIn("Music folders", rv.data) @@ -87,8 +87,8 @@ class FolderTestCase(FrontendTestBase): self._login("alice", "Alic3") rv = self.client.get("/folder/scan/string", follow_redirects=True) - self.assertIn("badly formed", rv.data) - rv = self.client.get("/folder/scan/" + str(uuid.uuid4()), follow_redirects=True) + self.assertIn("Invalid folder id", rv.data) + rv = self.client.get("/folder/scan/1234567890", follow_redirects=True) self.assertIn("No such folder", rv.data) rv = self.client.get("/folder/scan/" + str(folder.id), follow_redirects=True) self.assertIn("start", rv.data) diff --git a/tests/managers/test_manager_folder.py b/tests/managers/test_manager_folder.py index 84e9dd1..27a3b2e 100644 --- a/tests/managers/test_manager_folder.py +++ b/tests/managers/test_manager_folder.py @@ -76,7 +76,7 @@ class FolderManagerTestCase(unittest.TestCase): self.assertRaises(ValueError, FolderManager.get, 0xDEADBEEF) # Non-existent folder - self.assertRaises(ObjectNotFound, FolderManager.get, uuid.uuid4()) + self.assertRaises(ObjectNotFound, FolderManager.get, 1234567890) @db_session def test_add_folder(self): @@ -114,12 +114,12 @@ class FolderManagerTestCase(unittest.TestCase): self.create_folders() with db_session: - # Delete invalid UUID + # Delete invalid Folder ID self.assertRaises(ValueError, FolderManager.delete, "invalid-uuid") self.assertEqual(db.Folder.select().count(), 3) # Delete non-existent folder - self.assertRaises(ObjectNotFound, FolderManager.delete, uuid.uuid4()) + self.assertRaises(ObjectNotFound, FolderManager.delete, 1234567890) self.assertEqual(db.Folder.select().count(), 3) # Delete non-root folder From 67670aace88f57c0be4f0130b60019801860c24f Mon Sep 17 00:00:00 2001 From: mvn23 Date: Wed, 9 Oct 2019 18:57:42 +0200 Subject: [PATCH 9/9] Improve star/unstar handling --- supysonic/api/__init__.py | 2 ++ supysonic/api/annotation.py | 56 ++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/supysonic/api/__init__.py b/supysonic/api/__init__.py index 0b35035..e14d7b7 100644 --- a/supysonic/api/__init__.py +++ b/supysonic/api/__init__.py @@ -93,6 +93,8 @@ def get_entity(cls, param="id"): def get_entity_id(cls, eid): """Return the entity ID as its proper type.""" if cls == Folder: + if isinstance(eid, uuid.UUID): + raise GenericError("Invalid ID") try: return int(eid) except ValueError: diff --git a/supysonic/api/annotation.py b/supysonic/api/annotation.py index 457383a..2b8b64c 100644 --- a/supysonic/api/annotation.py +++ b/supysonic/api/annotation.py @@ -32,12 +32,14 @@ def star_single(cls, eid): :param eid: id of the entity to star """ - uid = get_entity_id(cls, eid) - e = cls[uid] + try: + e = cls[eid] + except ObjectNotFound: + raise NotFound("{} {}".format(cls.__name__, eid)) starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) try: - starred_cls[request.user, uid] + starred_cls[request.user, eid] raise GenericError("{} {} already starred".format(cls.__name__, eid)) except ObjectNotFound: pass @@ -52,10 +54,9 @@ def unstar_single(cls, eid): :param eid: id of the entity to unstar """ - uid = get_entity_id(cls, eid) starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) delete( - s for s in starred_cls if s.user.id == request.user.id and s.starred.id == uid + s for s in starred_cls if s.user.id == request.user.id and s.starred.id == eid ) return None @@ -68,35 +69,44 @@ def handle_star_request(func): errors = [] for eid in id: - terr = None - ferr = None - try: - func(Track, eid) - except Exception as e: - terr = e + tid = get_entity_id(Track, eid) + except GenericError: + tid = None try: - func(Folder, eid) - except GenericError as e: - if e.message == "Invalid ID" and isinstance(terr, ObjectNotFound): - ferr = NotFound("Folder not in database") - else: - ferr = e - except Exception as e: - ferr = e + fid = get_entity_id(Folder, eid) + except GenericError: + fid = None + err = None - if terr and ferr: - errors += [terr, ferr] + if tid is None and fid is None: + raise GenericError("Invalid ID") + + if tid is not None: + try: + func(Track, tid) + except Exception as e: + err = e + else: + try: + func(Folder, fid) + except Exception as e: + err = e + + if err: + errors.append(err) for alId in albumId: + alb_id = get_entity_id(Album, alId) try: - func(Album, alId) + func(Album, alb_id) except Exception as e: errors.append(e) for arId in artistId: + art_id = get_entity_id(Artist, arId) try: - func(Artist, arId) + func(Artist, art_id) except Exception as e: errors.append(e)