mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 17:06:17 +00:00
Improved folder deletion
This commit is contained in:
parent
0fe96f05c3
commit
b72daea109
@ -14,7 +14,7 @@ import os.path
|
||||
from datetime import datetime
|
||||
from pony.orm import Database, Required, Optional, Set, PrimaryKey, LongStr
|
||||
from pony.orm import ObjectNotFound
|
||||
from pony.orm import min, max, avg, sum
|
||||
from pony.orm import min, max, avg, sum, exists
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from .py23 import dict, strtype
|
||||
@ -79,6 +79,17 @@ class Folder(db.Entity):
|
||||
|
||||
return info
|
||||
|
||||
@classmethod
|
||||
def prune(cls):
|
||||
query = cls.select(lambda self: not exists(t for t in Track if t.folder == self) and \
|
||||
not exists(f for f in Folder if f.parent == self) and not self.root)
|
||||
total = 0
|
||||
while True:
|
||||
count = query.delete(bulk = True)
|
||||
total += count
|
||||
if not count:
|
||||
return total
|
||||
|
||||
class Artist(db.Entity):
|
||||
_table_ = 'artist'
|
||||
|
||||
@ -104,6 +115,11 @@ class Artist(db.Entity):
|
||||
|
||||
return info
|
||||
|
||||
@classmethod
|
||||
def prune(cls):
|
||||
return cls.select(lambda self: not exists(a for a in Album if a.artist == self) and \
|
||||
not exists(t for t in Track if t.artist == self)).delete(bulk = True)
|
||||
|
||||
class Album(db.Entity):
|
||||
_table_ = 'album'
|
||||
|
||||
@ -140,6 +156,10 @@ class Album(db.Entity):
|
||||
year = min(map(lambda t: t.year if t.year else 9999, self.tracks))
|
||||
return '%i%s' % (year, self.name.lower())
|
||||
|
||||
@classmethod
|
||||
def prune(cls):
|
||||
return cls.select(lambda self: not exists(t for t in Track if t.album == self)).delete(bulk = True)
|
||||
|
||||
class Track(db.Entity):
|
||||
_table_ = 'track'
|
||||
|
||||
|
@ -13,9 +13,8 @@ import uuid
|
||||
from pony.orm import select
|
||||
from pony.orm import ObjectNotFound
|
||||
|
||||
from ..db import Folder, Track
|
||||
from ..db import Folder, Track, Artist, Album
|
||||
from ..py23 import strtype
|
||||
from ..scanner import Scanner
|
||||
|
||||
class FolderManager:
|
||||
@staticmethod
|
||||
@ -52,10 +51,10 @@ class FolderManager:
|
||||
if not folder.root:
|
||||
raise ObjectNotFound(Folder)
|
||||
|
||||
scanner = Scanner()
|
||||
for track in Track.select(lambda t: t.root_folder == folder):
|
||||
scanner.remove_file(track.path)
|
||||
scanner.finish()
|
||||
Track.select(lambda t: t.root_folder == folder).delete(bulk = True)
|
||||
Album.prune()
|
||||
Artist.prune()
|
||||
Folder.prune()
|
||||
|
||||
folder.delete()
|
||||
|
||||
|
@ -41,14 +41,6 @@ class Scanner:
|
||||
self.__stats = Stats()
|
||||
self.__extensions = extensions
|
||||
|
||||
self.__folders_to_check = set()
|
||||
self.__artists_to_check = set()
|
||||
self.__albums_to_check = set()
|
||||
|
||||
def __del__(self):
|
||||
if self.__folders_to_check or self.__artists_to_check or self.__albums_to_check:
|
||||
raise Exception("There's still something to check. Did you run Scanner.finish()?")
|
||||
|
||||
def scan(self, folder, progress_callback = None):
|
||||
if not isinstance(folder, Folder):
|
||||
raise TypeError('Expecting Folder instance, got ' + str(type(folder)))
|
||||
@ -99,31 +91,9 @@ class Scanner:
|
||||
|
||||
@db_session
|
||||
def finish(self):
|
||||
for album in Album.select(lambda a: a.id in self.__albums_to_check):
|
||||
if not album.tracks.is_empty():
|
||||
continue
|
||||
|
||||
self.__artists_to_check.add(album.artist.id)
|
||||
self.__stats.deleted.albums += 1
|
||||
album.delete()
|
||||
self.__albums_to_check.clear()
|
||||
|
||||
for artist in Artist.select(lambda a: a.id in self.__artists_to_check):
|
||||
if not artist.albums.is_empty() or not artist.tracks.is_empty():
|
||||
continue
|
||||
|
||||
self.__stats.deleted.artists += 1
|
||||
artist.delete()
|
||||
self.__artists_to_check.clear()
|
||||
|
||||
while self.__folders_to_check:
|
||||
folder = Folder[self.__folders_to_check.pop()]
|
||||
if folder.root:
|
||||
continue
|
||||
|
||||
if folder.tracks.is_empty() and folder.children.is_empty():
|
||||
self.__folders_to_check.add(folder.parent.id)
|
||||
folder.delete()
|
||||
self.__stats.deleted.albums = Album.prune()
|
||||
self.__stats.deleted.artists = Artist.prune()
|
||||
Folder.prune()
|
||||
|
||||
def __is_valid_path(self, path):
|
||||
if not os.path.exists(path):
|
||||
@ -185,11 +155,9 @@ class Scanner:
|
||||
self.__stats.added.tracks += 1
|
||||
else:
|
||||
if tr.album.id != tralbum.id:
|
||||
self.__albums_to_check.add(tr.album.id)
|
||||
trdict['album'] = tralbum
|
||||
|
||||
if tr.artist.id != trartist.id:
|
||||
self.__artists_to_check.add(tr.artist.id)
|
||||
trdict['artist'] = trartist
|
||||
|
||||
tr.set(**trdict)
|
||||
@ -203,9 +171,6 @@ class Scanner:
|
||||
if not tr:
|
||||
return
|
||||
|
||||
self.__folders_to_check.add(tr.folder.id)
|
||||
self.__albums_to_check.add(tr.album.id)
|
||||
self.__artists_to_check.add(tr.artist.id)
|
||||
self.__stats.deleted.tracks += 1
|
||||
tr.delete()
|
||||
|
||||
@ -223,7 +188,6 @@ class Scanner:
|
||||
if tr is None:
|
||||
return
|
||||
|
||||
self.__folders_to_check.add(tr.folder.id)
|
||||
tr_dst = Track.get(path = dst_path)
|
||||
if tr_dst is not None:
|
||||
root = tr_dst.root_folder
|
||||
|
@ -11,10 +11,8 @@
|
||||
|
||||
from supysonic import db
|
||||
from supysonic.managers.folder import FolderManager
|
||||
from supysonic.py23 import strtype
|
||||
|
||||
import os
|
||||
import io
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
@ -36,7 +34,6 @@ class FolderManagerTestCase(unittest.TestCase):
|
||||
shutil.rmtree(self.media_dir)
|
||||
shutil.rmtree(self.music_dir)
|
||||
|
||||
@db_session
|
||||
def create_folders(self):
|
||||
# Add test folders
|
||||
self.assertIsNotNone(FolderManager.add('media', self.media_dir))
|
||||
@ -112,42 +109,49 @@ class FolderManagerTestCase(unittest.TestCase):
|
||||
self.assertRaises(ValueError, FolderManager.add, 'parent', path)
|
||||
self.assertEqual(db.Folder.select().count(), 3)
|
||||
|
||||
@db_session
|
||||
def test_delete_folder(self):
|
||||
with db_session:
|
||||
self.create_folders()
|
||||
|
||||
with db_session:
|
||||
# Delete invalid UUID
|
||||
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.assertEqual(db.Folder.select().count(), 3)
|
||||
|
||||
# Delete non-root folder
|
||||
folder = db.Folder.get(name = 'non-root')
|
||||
self.assertRaises(ObjectNotFound, FolderManager.delete, folder.id)
|
||||
self.assertEqual(db.Folder.select().count(), 3)
|
||||
|
||||
with db_session:
|
||||
# Delete existing folders
|
||||
for name in ['media', 'music']:
|
||||
folder = db.Folder.get(name = name, root = True)
|
||||
FolderManager.delete(folder.id)
|
||||
self.assertRaises(ObjectNotFound, db.Folder.__getitem__, folder.id)
|
||||
|
||||
# Delete invalid UUID
|
||||
self.assertRaises(ValueError, FolderManager.delete, 'invalid-uuid')
|
||||
self.assertEqual(db.Folder.select().count(), 1) # 'non-root' remaining
|
||||
# Even if we have only 2 root folders, non-root should never exist and be cleaned anyway
|
||||
self.assertEqual(db.Folder.select().count(), 0)
|
||||
|
||||
# Delete non-existent folder
|
||||
self.assertRaises(ObjectNotFound, FolderManager.delete, uuid.uuid4())
|
||||
self.assertEqual(db.Folder.select().count(), 1) # 'non-root' remaining
|
||||
|
||||
# Delete non-root folder
|
||||
folder = db.Folder.get(name = 'non-root')
|
||||
self.assertRaises(ObjectNotFound, FolderManager.delete, folder.id)
|
||||
self.assertEqual(db.Folder.select().count(), 1) # 'non-root' remaining
|
||||
|
||||
@db_session
|
||||
def test_delete_by_name(self):
|
||||
with db_session:
|
||||
self.create_folders()
|
||||
|
||||
with db_session:
|
||||
# Delete non-existent folder
|
||||
self.assertRaises(ObjectNotFound, FolderManager.delete_by_name, 'null')
|
||||
self.assertEqual(db.Folder.select().count(), 3)
|
||||
|
||||
with db_session:
|
||||
# Delete existing folders
|
||||
for name in ['media', 'music']:
|
||||
FolderManager.delete_by_name(name)
|
||||
self.assertFalse(db.Folder.exists(name = name))
|
||||
|
||||
# Delete non-existent folder
|
||||
self.assertRaises(ObjectNotFound, FolderManager.delete_by_name, 'null')
|
||||
self.assertEqual(db.Folder.select().count(), 1) # 'non-root' remaining
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user