1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-13 21:52:18 +00:00

Merge remote-tracking branch 'mvn23/api_fix_ids'

This commit is contained in:
Alban Féron 2019-10-26 16:38:50 +02:00
commit 2f94089e9c
No known key found for this signature in database
GPG Key ID: 8CE0313646D16165
22 changed files with 479 additions and 104 deletions

View File

@ -82,11 +82,29 @@ def get_client_prefs():
def get_entity(cls, param="id"): def get_entity(cls, param="id"):
eid = request.values[param] eid = request.values[param]
if cls == Folder:
eid = int(eid)
else:
eid = uuid.UUID(eid) eid = uuid.UUID(eid)
entity = cls[eid] entity = cls[eid]
return entity return entity
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:
raise GenericError("Invalid ID")
try:
return uuid.UUID(eid)
except (AttributeError, ValueError):
raise GenericError("Invalid ID")
from .errors import * from .errors import *
from .system import * from .system import *

View File

@ -7,8 +7,6 @@
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import uuid
from datetime import timedelta from datetime import timedelta
from flask import request from flask import request
from pony.orm import select, desc, avg, max, min, count from pony.orm import select, desc, avg, max, min, count
@ -42,7 +40,12 @@ def rand_songs():
size = int(size) if size else 10 size = int(size) if size else 10
fromYear = int(fromYear) if fromYear else None fromYear = int(fromYear) if fromYear else None
toYear = int(toYear) if toYear else None toYear = int(toYear) if toYear else None
fid = uuid.UUID(musicFolderId) if musicFolderId else None fid = None
if musicFolderId:
try:
fid = int(musicFolderId)
except ValueError:
raise ValueError("Invalid folder ID")
query = Track.select() query = Track.select()
if fromYear: if fromYear:

View File

@ -21,7 +21,7 @@ from ..db import RatingTrack, RatingFolder
from ..lastfm import LastFm from ..lastfm import LastFm
from ..py23 import dict from ..py23 import dict
from . import api, get_entity from . import api, get_entity, get_entity_id
from .exceptions import AggregateException, GenericError, MissingParameter, NotFound from .exceptions import AggregateException, GenericError, MissingParameter, NotFound
@ -32,12 +32,14 @@ def star_single(cls, eid):
:param eid: id of the entity to star :param eid: id of the entity to star
""" """
uid = uuid.UUID(eid) try:
e = cls[uid] e = cls[eid]
except ObjectNotFound:
raise NotFound("{} {}".format(cls.__name__, eid))
starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__)
try: try:
starred_cls[request.user, uid] starred_cls[request.user, eid]
raise GenericError("{} {} already starred".format(cls.__name__, eid)) raise GenericError("{} {} already starred".format(cls.__name__, eid))
except ObjectNotFound: except ObjectNotFound:
pass pass
@ -52,10 +54,9 @@ def unstar_single(cls, eid):
:param eid: id of the entity to unstar :param eid: id of the entity to unstar
""" """
uid = uuid.UUID(eid)
starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__) starred_cls = getattr(sys.modules[__name__], "Starred" + cls.__name__)
delete( 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 return None
@ -68,30 +69,44 @@ def handle_star_request(func):
errors = [] errors = []
for eid in id: for eid in id:
terr = None
ferr = None
try: try:
func(Track, eid) tid = get_entity_id(Track, eid)
except Exception as e: except GenericError:
terr = e tid = None
try: try:
func(Folder, eid) fid = get_entity_id(Folder, eid)
except Exception as e: except GenericError:
ferr = e fid = None
err = None
if terr and ferr: if tid is None and fid is None:
errors += [terr, ferr] 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: for alId in albumId:
alb_id = get_entity_id(Album, alId)
try: try:
func(Album, alId) func(Album, alb_id)
except Exception as e: except Exception as e:
errors.append(e) errors.append(e)
for arId in artistId: for arId in artistId:
art_id = get_entity_id(Artist, arId)
try: try:
func(Artist, arId) func(Artist, art_id)
except Exception as e: except Exception as e:
errors.append(e) errors.append(e)
@ -115,31 +130,43 @@ def rate():
id = request.values["id"] id = request.values["id"]
rating = request.values["rating"] rating = request.values["rating"]
uid = uuid.UUID(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) rating = int(rating)
if tid is None and fid is None:
raise GenericError("Invalid ID")
if not 0 <= rating <= 5: if not 0 <= rating <= 5:
raise GenericError("rating must be between 0 and 5 (inclusive)") raise GenericError("rating must be between 0 and 5 (inclusive)")
if rating == 0: if rating == 0:
if tid is not None:
delete( 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
) )
else:
delete( delete(
r r
for r in RatingFolder 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: else:
try: if tid is not None:
rated = Track[uid] rated = Track[tid]
rating_cls = RatingTrack rating_cls = RatingTrack
except ObjectNotFound: uid = tid
try: else:
rated = Folder[uid] rated = Folder[fid]
rating_cls = RatingFolder rating_cls = RatingFolder
except ObjectNotFound: uid = fid
raise NotFound("Track or Folder")
try: try:
rating_info = rating_cls[request.user, uid] rating_info = rating_cls[request.user, uid]

View File

@ -16,7 +16,7 @@ from pony.orm import ObjectNotFound, select
from ..db import Folder, Artist, Album, Track from ..db import Folder, Artist, Album, Track
from ..py23 import dict from ..py23 import dict
from . import api, get_entity from . import api, get_entity, get_entity_id
@api.route("/getMusicFolders.view", methods=["GET", "POST"]) @api.route("/getMusicFolders.view", methods=["GET", "POST"])
@ -42,7 +42,7 @@ def list_indexes():
if musicFolderId is None: if musicFolderId is None:
folders = Folder.select(lambda f: f.root)[:] folders = Folder.select(lambda f: f.root)[:]
else: else:
mfid = uuid.UUID(musicFolderId) mfid = get_entity_id(Folder, musicFolderId)
folder = Folder[mfid] folder = Folder[mfid]
if not folder.root: if not folder.root:
raise ObjectNotFound(Folder, mfid) raise ObjectNotFound(Folder, mfid)

View File

@ -35,7 +35,7 @@ from ..covers import get_embedded_cover
from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now from ..db import Track, Album, Artist, Folder, User, ClientPrefs, now
from ..py23 import dict from ..py23 import dict
from . import api, get_entity from . import api, get_entity, get_entity_id
from .exceptions import ( from .exceptions import (
GenericError, GenericError,
MissingParameter, MissingParameter,
@ -190,21 +190,33 @@ def stream_media():
@api.route("/download.view", methods=["GET", "POST"]) @api.route("/download.view", methods=["GET", "POST"])
def download_media(): def download_media():
id = request.values["id"] id = request.values["id"]
uid = uuid.UUID(id)
try: # Track -> direct download try:
uid = get_entity_id(Track, id)
except GenericError:
uid = None
try:
fid = get_entity_id(Folder, id)
except GenericError:
fid = None
if uid is None and fid is None:
raise GenericError("Invalid ID")
if uid is not None:
try:
rv = Track[uid] rv = Track[uid]
return send_file(rv.path, mimetype=rv.mimetype, conditional=True) return send_file(rv.path, mimetype=rv.mimetype, conditional=True)
except ObjectNotFound:
pass
try: # Folder -> stream zipped tracks, non recursive
rv = Folder[uid]
except ObjectNotFound: except ObjectNotFound:
try: # Album -> stream zipped tracks try: # Album -> stream zipped tracks
rv = Album[uid] rv = Album[uid]
except ObjectNotFound: except ObjectNotFound:
raise NotFound("Track, Folder or Album") 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) z = ZipFile(compression=ZIP_DEFLATED)
for track in rv.tracks: for track in rv.tracks:
@ -217,15 +229,28 @@ def download_media():
@api.route("/getCoverArt.view", methods=["GET", "POST"]) @api.route("/getCoverArt.view", methods=["GET", "POST"])
def cover_art(): def cover_art():
cache = current_app.cache cache = current_app.cache
eid = request.values["id"] eid = request.values["id"]
if Folder.exists(id=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) res = get_entity(Folder)
if not res.cover_art or not os.path.isfile( if not res.cover_art or not os.path.isfile(
os.path.join(res.path, res.cover_art) os.path.join(res.path, res.cover_art)
): ):
raise NotFound("Cover art") raise NotFound("Cover art")
cover_path = os.path.join(res.path, res.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) cache_key = "{}-cover".format(eid)
try: try:
cover_path = cache.get(cache_key) cover_path = cache.get(cache_key)

View File

@ -90,8 +90,13 @@ class IniConfig(DefaultConfig):
lv = value.lower() lv = value.lower()
if lv in ("yes", "true", "on"): if lv in ("yes", "true", "on"):
return True return True
elif lv in ("no", "false", "off"): if lv in ("no", "false", "off"):
return False return False
try:
if isinstance(value, unicode):
return str(value)
except NameError:
pass
return value return value
@classmethod @classmethod

View File

@ -29,7 +29,7 @@ try:
except ImportError: except ImportError:
from urlparse import urlparse, parse_qsl from urlparse import urlparse, parse_qsl
SCHEMA_VERSION = "20190518" SCHEMA_VERSION = "20190915"
def now(): def now():
@ -79,7 +79,7 @@ class PathMixin(object):
class Folder(PathMixin, db.Entity): class Folder(PathMixin, db.Entity):
_table_ = "folder" _table_ = "folder"
id = PrimaryKey(UUID, default=uuid4) id = PrimaryKey(int, auto=True)
root = Required(bool, default=False) root = Required(bool, default=False)
name = Required(str, autostrip=False) name = Required(str, autostrip=False)
path = Required(str, 4096, autostrip=False) # unique path = Required(str, 4096, autostrip=False) # unique

View File

@ -21,15 +21,13 @@ from ..py23 import strtype
class FolderManager: class FolderManager:
@staticmethod @staticmethod
def get(uid): def get(id):
if isinstance(uid, strtype): try:
uid = uuid.UUID(uid) id = int(id)
elif isinstance(uid, uuid.UUID): except ValueError:
pass
else:
raise ValueError("Invalid folder id") raise ValueError("Invalid folder id")
return Folder[uid] return Folder[id]
@staticmethod @staticmethod
def add(name, path): def add(name, path):
@ -55,8 +53,8 @@ class FolderManager:
return folder return folder
@staticmethod @staticmethod
def delete(uid): def delete(id):
folder = FolderManager.get(uid) folder = FolderManager.get(id)
if not folder.root: if not folder.root:
raise ObjectNotFound(Folder) raise ObjectNotFound(Folder)

View File

@ -0,0 +1,69 @@
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,
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);
ALTER TABLE track
DROP COLUMN root_folder_id,
DROP COLUMN 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);
ALTER TABLE starred_folder
DROP PRIMARY KEY,
DROP COLUMN 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);
ALTER TABLE rating_folder
DROP PRIMARY KEY,
DROP COLUMN rated_id,
CHANGE COLUMN int_rated_id rated_id INTEGER NOT NULL,
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;

View File

@ -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;

View File

@ -0,0 +1,136 @@
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, 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
WHERE folder.parent_id IS NOT NULL;
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.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;

View File

@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS folder ( CREATE TABLE IF NOT EXISTS folder (
id BINARY(16) PRIMARY KEY, id INTEGER PRIMARY KEY AUTO_INCREMENT,
root BOOLEAN NOT NULL, root BOOLEAN NOT NULL,
name VARCHAR(256) NOT NULL, name VARCHAR(256) NOT NULL,
path VARCHAR(4096) NOT NULL, path VARCHAR(4096) NOT NULL,
@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder (
created DATETIME NOT NULL, created DATETIME NOT NULL,
cover_art VARCHAR(256), cover_art VARCHAR(256),
last_scan INTEGER NOT NULL, last_scan INTEGER NOT NULL,
parent_id BINARY(16) REFERENCES folder parent_id INTEGER REFERENCES folder
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE INDEX IF NOT EXISTS index_folder_parent_id_fk ON folder(parent_id); 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, last_modification INTEGER NOT NULL,
play_count INTEGER NOT NULL, play_count INTEGER NOT NULL,
last_play DATETIME, last_play DATETIME,
root_folder_id BINARY(16) NOT NULL REFERENCES folder, root_folder_id INTEGER NOT NULL REFERENCES folder,
folder_id BINARY(16) NOT NULL REFERENCES folder folder_id INTEGER NOT NULL REFERENCES folder
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) 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_album_id_fk ON track(album_id);
CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_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 ( CREATE TABLE IF NOT EXISTS starred_folder (
user_id BINARY(16) NOT NULL REFERENCES user, 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, date DATETIME NOT NULL,
PRIMARY KEY (user_id, starred_id) PRIMARY KEY (user_id, starred_id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) 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 ( CREATE TABLE IF NOT EXISTS rating_folder (
user_id BINARY(16) NOT NULL REFERENCES user, 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), rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5),
PRIMARY KEY (user_id, rated_id) PRIMARY KEY (user_id, rated_id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View File

@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS folder ( CREATE TABLE IF NOT EXISTS folder (
id UUID PRIMARY KEY, id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
root BOOLEAN NOT NULL, root BOOLEAN NOT NULL,
name CITEXT NOT NULL, name CITEXT NOT NULL,
path VARCHAR(4096) NOT NULL, path VARCHAR(4096) NOT NULL,
@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder (
created TIMESTAMP NOT NULL, created TIMESTAMP NOT NULL,
cover_art VARCHAR(256), cover_art VARCHAR(256),
last_scan INTEGER NOT NULL, 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); 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, last_modification INTEGER NOT NULL,
play_count INTEGER NOT NULL, play_count INTEGER NOT NULL,
last_play TIMESTAMP, last_play TIMESTAMP,
root_folder_id UUID NOT NULL REFERENCES folder, root_folder_id INTEGER NOT NULL REFERENCES folder,
folder_id UUID 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_album_id_fk ON track(album_id);
CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_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 ( CREATE TABLE IF NOT EXISTS starred_folder (
user_id UUID NOT NULL REFERENCES "user", 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, date TIMESTAMP NOT NULL,
PRIMARY KEY (user_id, starred_id) 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 ( CREATE TABLE IF NOT EXISTS rating_folder (
user_id UUID NOT NULL REFERENCES "user", 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), rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5),
PRIMARY KEY (user_id, rated_id) PRIMARY KEY (user_id, rated_id)
); );

View File

@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS folder ( CREATE TABLE IF NOT EXISTS folder (
id CHAR(36) PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
root BOOLEAN NOT NULL, root BOOLEAN NOT NULL,
name VARCHAR(256) NOT NULL COLLATE NOCASE, name VARCHAR(256) NOT NULL COLLATE NOCASE,
path VARCHAR(4096) NOT NULL, path VARCHAR(4096) NOT NULL,
@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS folder (
created DATETIME NOT NULL, created DATETIME NOT NULL,
cover_art VARCHAR(256), cover_art VARCHAR(256),
last_scan INTEGER NOT NULL, 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 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); 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, last_modification INTEGER NOT NULL,
play_count INTEGER NOT NULL, play_count INTEGER NOT NULL,
last_play DATETIME, last_play DATETIME,
root_folder_id CHAR(36) NOT NULL REFERENCES folder, root_folder_id INTEGER NOT NULL REFERENCES folder,
folder_id CHAR(36) 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_album_id_fk ON track(album_id);
CREATE INDEX IF NOT EXISTS index_track_artist_id_fk ON track(artist_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 ( CREATE TABLE IF NOT EXISTS starred_folder (
user_id CHAR(36) NOT NULL REFERENCES user, 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, date DATETIME NOT NULL,
PRIMARY KEY (user_id, starred_id) 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 ( CREATE TABLE IF NOT EXISTS rating_folder (
user_id CHAR(36) NOT NULL REFERENCES user, 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), rating INTEGER NOT NULL CHECK(rating BETWEEN 1 AND 5),
PRIMARY KEY (user_id, rated_id) PRIMARY KEY (user_id, rated_id)
); );

View File

@ -6,12 +6,13 @@
# Copyright (C) 2019 Alban 'spl0k' Féron # Copyright (C) 2019 Alban 'spl0k' Féron
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import uuid
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from os import urandom from os import urandom
from pony.orm import db_session, commit, ObjectNotFound from pony.orm import db_session, commit, ObjectNotFound
from supysonic.db import Meta from supysonic.db import Folder, Meta
@db_session @db_session

View File

@ -118,7 +118,7 @@ class AlbumSongsTestCase(ApiTestBase):
self._make_request("getRandomSongs", {"fromYear": "year"}, error=0) self._make_request("getRandomSongs", {"fromYear": "year"}, error=0)
self._make_request("getRandomSongs", {"toYear": "year"}, error=0) self._make_request("getRandomSongs", {"toYear": "year"}, error=0)
self._make_request("getRandomSongs", {"musicFolderId": "idid"}, 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( rv, child = self._make_request(
"getRandomSongs", tag="randomSongs", skip_post=True "getRandomSongs", tag="randomSongs", skip_post=True

View File

@ -27,6 +27,10 @@ class AnnotationTestCase(ApiTestBase):
artist = Artist(name="Artist") artist = Artist(name="Artist")
album = Album(name="Album", artist=artist) album = Album(name="Album", artist=artist)
# Populate folder ids
root = Folder.get(name="Root")
folder = Folder.get(name="Folder")
track = Track( track = Track(
title="Track", title="Track",
album=album, album=album,
@ -73,7 +77,7 @@ class AnnotationTestCase(ApiTestBase):
self.assertIn("starred", Folder[self.folderid].as_subsonic_child(self.user)) 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", {"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.artistid)}, error=70)
self._make_request("star", {"albumId": str(self.trackid)}, error=70) self._make_request("star", {"albumId": str(self.trackid)}, error=70)
self._make_request("star", {"albumId": str(self.albumid)}, skip_post=True) 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.assertIn("starred", Album[self.albumid].as_subsonic_album(self.user))
self._make_request("star", {"albumId": str(self.albumid)}, error=0) 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.albumid)}, error=70)
self._make_request("star", {"artistId": str(self.trackid)}, error=70) self._make_request("star", {"artistId": str(self.trackid)}, error=70)
self._make_request("star", {"artistId": str(self.artistid)}, skip_post=True) 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", error=10)
self._make_request("scrobble", {"id": "song"}, error=0) self._make_request("scrobble", {"id": "song"}, error=0)
self._make_request("scrobble", {"id": str(uuid.uuid4())}, error=70) 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)})
self._make_request("scrobble", {"id": str(self.trackid), "submission": True}) self._make_request("scrobble", {"id": str(self.trackid), "submission": True})

View File

@ -82,7 +82,7 @@ class BrowseTestCase(ApiTestBase):
def test_get_indexes(self): def test_get_indexes(self):
self._make_request("getIndexes", {"musicFolderId": "abcdef"}, error=0) 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) self._make_request("getIndexes", {"ifModifiedSince": "quoi"}, error=0)
rv, child = self._make_request( rv, child = self._make_request(
@ -109,7 +109,7 @@ class BrowseTestCase(ApiTestBase):
def test_get_music_directory(self): def test_get_music_directory(self):
self._make_request("getMusicDirectory", error=10) self._make_request("getMusicDirectory", error=10)
self._make_request("getMusicDirectory", {"id": "id"}, error=0) 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 # should test with folders with both children folders and tracks. this code would break in that case
with db_session: with db_session:

View File

@ -31,6 +31,7 @@ class MediaTestCase(ApiTestBase):
root=True, root=True,
cover_art="cover.jpg", cover_art="cover.jpg",
) )
folder = Folder.get(name="Root")
self.folderid = folder.id self.folderid = folder.id
artist = Artist(name="Artist") artist = Artist(name="Artist")
@ -74,7 +75,7 @@ class MediaTestCase(ApiTestBase):
self._make_request("stream", error=10) self._make_request("stream", error=10)
self._make_request("stream", {"id": "string"}, error=0) self._make_request("stream", {"id": "string"}, error=0)
self._make_request("stream", {"id": str(uuid.uuid4())}, error=70) 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( self._make_request(
"stream", {"id": str(self.trackid), "maxBitRate": "string"}, error=0 "stream", {"id": str(self.trackid), "maxBitRate": "string"}, error=0
) )

View File

@ -50,7 +50,12 @@ class DbTestCase(unittest.TestCase):
parent=root_folder, 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): def create_some_tracks(self, artist=None, album=None):
root, child, child_2 = self.create_some_folders() 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() root_folder, folder_art, folder_noart = self.create_some_folders()
track1 = self.create_track_in( 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) album_dict = album.as_subsonic_album(user)

View File

@ -72,8 +72,8 @@ class FolderTestCase(FrontendTestBase):
self._login("alice", "Alic3") self._login("alice", "Alic3")
rv = self.client.get("/folder/del/string", follow_redirects=True) rv = self.client.get("/folder/del/string", follow_redirects=True)
self.assertIn("badly formed", rv.data) self.assertIn("Invalid folder id", rv.data)
rv = self.client.get("/folder/del/" + str(uuid.uuid4()), follow_redirects=True) rv = self.client.get("/folder/del/1234567890", follow_redirects=True)
self.assertIn("No such folder", rv.data) self.assertIn("No such folder", rv.data)
rv = self.client.get("/folder/del/" + str(folder.id), follow_redirects=True) rv = self.client.get("/folder/del/" + str(folder.id), follow_redirects=True)
self.assertIn("Music folders", rv.data) self.assertIn("Music folders", rv.data)
@ -87,8 +87,8 @@ class FolderTestCase(FrontendTestBase):
self._login("alice", "Alic3") self._login("alice", "Alic3")
rv = self.client.get("/folder/scan/string", follow_redirects=True) rv = self.client.get("/folder/scan/string", follow_redirects=True)
self.assertIn("badly formed", rv.data) self.assertIn("Invalid folder id", rv.data)
rv = self.client.get("/folder/scan/" + str(uuid.uuid4()), follow_redirects=True) rv = self.client.get("/folder/scan/1234567890", follow_redirects=True)
self.assertIn("No such folder", rv.data) self.assertIn("No such folder", rv.data)
rv = self.client.get("/folder/scan/" + str(folder.id), follow_redirects=True) rv = self.client.get("/folder/scan/" + str(folder.id), follow_redirects=True)
self.assertIn("start", rv.data) self.assertIn("start", rv.data)

View File

@ -76,7 +76,7 @@ class FolderManagerTestCase(unittest.TestCase):
self.assertRaises(ValueError, FolderManager.get, 0xDEADBEEF) self.assertRaises(ValueError, FolderManager.get, 0xDEADBEEF)
# Non-existent folder # Non-existent folder
self.assertRaises(ObjectNotFound, FolderManager.get, uuid.uuid4()) self.assertRaises(ObjectNotFound, FolderManager.get, 1234567890)
@db_session @db_session
def test_add_folder(self): def test_add_folder(self):
@ -114,12 +114,12 @@ class FolderManagerTestCase(unittest.TestCase):
self.create_folders() self.create_folders()
with db_session: with db_session:
# Delete invalid UUID # Delete invalid Folder ID
self.assertRaises(ValueError, FolderManager.delete, "invalid-uuid") self.assertRaises(ValueError, FolderManager.delete, "invalid-uuid")
self.assertEqual(db.Folder.select().count(), 3) self.assertEqual(db.Folder.select().count(), 3)
# Delete non-existent folder # Delete non-existent folder
self.assertRaises(ObjectNotFound, FolderManager.delete, uuid.uuid4()) self.assertRaises(ObjectNotFound, FolderManager.delete, 1234567890)
self.assertEqual(db.Folder.select().count(), 3) self.assertEqual(db.Folder.select().count(), 3)
# Delete non-root folder # Delete non-root folder