1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-22 17:06:17 +00:00

Fix integrity errors when deleting a root folder

This commit is contained in:
Alban Féron 2023-01-14 16:47:57 +01:00
parent b57b086e04
commit 09a5fb12ed
No known key found for this signature in database
GPG Key ID: 8CE0313646D16165
3 changed files with 90 additions and 23 deletions

View File

@ -202,11 +202,19 @@ class Artist(_Model):
@classmethod @classmethod
def prune(cls): def prune(cls):
album_artists = Album.select(Album.artist)
track_artists = Track.select(Track.artist)
StarredArtist.delete().where(
StarredArtist.starred.not_in(album_artists),
StarredArtist.starred.not_in(track_artists),
).execute()
return ( return (
cls.delete() cls.delete()
.where( .where(
cls.id.not_in(Album.select(Album.artist)), cls.id.not_in(album_artists),
cls.id.not_in(Track.select(Track.artist)), cls.id.not_in(track_artists),
) )
.execute() .execute()
) )
@ -269,7 +277,9 @@ class Album(_Model):
@classmethod @classmethod
def prune(cls): def prune(cls):
return cls.delete().where(cls.id.not_in(Track.select(Track.album))).execute() albums = Track.select(Track.album)
StarredAlbum.delete().where(StarredAlbum.starred.not_in(albums)).execute()
return cls.delete().where(cls.id.not_in(albums)).execute()
class Track(PathMixin, _Model): class Track(PathMixin, _Model):

View File

@ -7,9 +7,21 @@
import os.path import os.path
from peewee import IntegrityError
from ..daemon.client import DaemonClient from ..daemon.client import DaemonClient
from ..daemon.exceptions import DaemonUnavailableError from ..daemon.exceptions import DaemonUnavailableError
from ..db import Folder, Track, Artist, Album, User, RatingTrack, StarredTrack from ..db import (
Folder,
Track,
Artist,
Album,
User,
RatingFolder,
RatingTrack,
StarredFolder,
StarredTrack,
)
class FolderManager: class FolderManager:
@ -67,21 +79,29 @@ class FolderManager:
except DaemonUnavailableError: except DaemonUnavailableError:
pass pass
users = User.select(User.id).join(Track).where(Track.root_folder == folder) root_cond = Track.root_folder == folder
users = User.select(User.id).join(Track).where(root_cond)
User.update(last_play=None).where(User.id.in_(users)).execute() User.update(last_play=None).where(User.id.in_(users)).execute()
deleted_tracks_query = Track.select(Track.id).where(Track.root_folder == folder) tracks = Track.select(Track.id).where(root_cond)
RatingTrack.delete().where( RatingTrack.delete().where(RatingTrack.rated.in_(tracks)).execute()
RatingTrack.rated.in_(deleted_tracks_query) StarredTrack.delete().where(StarredTrack.starred.in_(tracks)).execute()
).execute()
StarredTrack.delete().where(
StarredTrack.starred.in_(deleted_tracks_query)
).execute()
Track.delete().where(Track.root_folder == folder).execute() path_cond = Folder.path.startswith(folder.path)
folders = Folder.select(Folder.id).where(path_cond)
RatingFolder.delete().where(RatingFolder.rated.in_(folders)).execute()
StarredFolder.delete().where(StarredFolder.starred.in_(folders)).execute()
Track.delete().where(root_cond).execute()
Album.prune() Album.prune()
Artist.prune() Artist.prune()
Folder.delete().where(Folder.path.startswith(folder.path)).execute() query = Folder.delete().where(path_cond)
try:
query.execute()
except IntegrityError:
# Integrity error most likely due to MySQL poor handling of delete order
query = query.order_by(Folder.path.desc())
query.execute()
@staticmethod @staticmethod
def delete_by_name(name): def delete_by_name(name):

View File

@ -1,12 +1,26 @@
# This file is part of Supysonic. # This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API. # Supysonic is a Python implementation of the Subsonic server API.
# #
# Copyright (C) 2017-2022 Alban 'spl0k' Féron # Copyright (C) 2017-2023 Alban 'spl0k' Féron
# 2017 Óscar García Amor # 2017 Óscar García Amor
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
from supysonic.db import Folder, Album, Artist, Track, init_database, release_database from supysonic.db import (
Folder,
Album,
Artist,
RatingFolder,
RatingTrack,
StarredAlbum,
StarredArtist,
StarredFolder,
StarredTrack,
Track,
User,
init_database,
release_database,
)
from supysonic.managers.folder import FolderManager from supysonic.managers.folder import FolderManager
import os import os
@ -31,31 +45,48 @@ class FolderManagerTestCase(unittest.TestCase):
def create_folders(self): def create_folders(self):
# Add test folders # Add test folders
self.assertIsNotNone(FolderManager.add("media", self.media_dir)) media = FolderManager.add("media", self.media_dir)
self.assertIsNotNone(FolderManager.add("music", self.music_dir)) music = FolderManager.add("music", self.music_dir)
self.assertIsNotNone(media)
self.assertIsNotNone(music)
Folder.create( Folder.create(
root=False, name="non-root", path=os.path.join(self.music_dir, "subfolder") root=False,
parent=music,
name="non-root",
path=os.path.join(self.music_dir, "subfolder"),
) )
artist = Artist.create(name="Artist") artist = Artist.create(name="Artist")
album = Album.create(name="Album", artist=artist) album = Album.create(name="Album", artist=artist)
root = Folder.get(name="media") Track.create(
Track(
title="Track", title="Track",
artist=artist, artist=artist,
album=album, album=album,
disc=1, disc=1,
number=1, number=1,
path=os.path.join(self.media_dir, "somefile"), path=os.path.join(self.media_dir, "somefile"),
folder=root, folder=media,
root_folder=root, root_folder=media,
duration=2, duration=2,
bitrate=320, bitrate=320,
last_modification=0, last_modification=0,
) )
def create_annotations(self):
track = Track.select().first()
user = User.create(name="user", password="secret", salt="ABC+", last_play=track)
folder = Folder.get(name="media")
RatingFolder.create(user=user, rated=folder, rating=3)
RatingTrack.create(user=user, rated=track, rating=3)
StarredFolder.create(user=user, starred=folder)
StarredArtist.create(user=user, starred=track.artist_id)
StarredAlbum.create(user=user, starred=track.album_id)
StarredTrack.create(user=user, starred=track)
def test_get_folder(self): def test_get_folder(self):
self.create_folders() self.create_folders()
@ -116,6 +147,9 @@ class FolderManagerTestCase(unittest.TestCase):
self.assertRaises(Folder.DoesNotExist, FolderManager.delete, folder.id) self.assertRaises(Folder.DoesNotExist, FolderManager.delete, folder.id)
self.assertEqual(Folder.select().count(), 3) self.assertEqual(Folder.select().count(), 3)
# Create some annotation to ensure foreign keys are properly handled
self.create_annotations()
# Delete existing folders # Delete existing folders
for name in ["media", "music"]: for name in ["media", "music"]:
folder = Folder.get(name=name, root=True) folder = Folder.get(name=name, root=True)
@ -132,6 +166,9 @@ class FolderManagerTestCase(unittest.TestCase):
self.assertRaises(Folder.DoesNotExist, FolderManager.delete_by_name, "null") self.assertRaises(Folder.DoesNotExist, FolderManager.delete_by_name, "null")
self.assertEqual(Folder.select().count(), 3) self.assertEqual(Folder.select().count(), 3)
# Create some annotation to ensure foreign keys are properly handled
self.create_annotations()
# Delete existing folders # Delete existing folders
for name in ["media", "music"]: for name in ["media", "music"]:
FolderManager.delete_by_name(name) FolderManager.delete_by_name(name)