1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-11-09 19:52:16 +00:00

Port supysonic.scanner

This commit is contained in:
Alban Féron 2022-12-10 18:04:09 +01:00
parent e589247458
commit 83ba85aaf1
No known key found for this signature in database
GPG Key ID: 8CE0313646D16165
3 changed files with 50 additions and 77 deletions

View File

@ -156,14 +156,14 @@ class Folder(PathMixin, db.Model):
@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
query = cls.delete().where(
~cls.root,
cls.id.not_in(Track.select(Track.folder)),
cls.id.not_in(cls.select(cls.parent)),
)
total = 0
while True:
count = query.delete()
count = query.execute()
total += count
if not count:
return total
@ -191,7 +191,7 @@ class Artist(db.Model):
@classmethod
def prune(cls):
cls.delete().where(
return cls.delete().where(
cls.id.not_in(Album.select(Album.artist)),
cls.id.not_in(Track.select(Track.artist)),
).execute()
@ -254,7 +254,7 @@ class Album(db.Model):
@classmethod
def prune(cls):
cls.delete().where(cls.id.not_in(Track.select(Track.album))).execute()
return cls.delete().where(cls.id.not_in(Track.select(Track.album))).execute()
class Track(PathMixin, db.Model):

View File

@ -101,10 +101,10 @@ class Scanner(Thread):
except QueueEmpty:
break
with db_session:
try:
folder = Folder.get(name=folder_name, root=True)
if folder is None:
continue
except Folder.DoesNotExist:
continue
self.__scan_folder(folder)
@ -144,32 +144,27 @@ class Scanner(Thread):
# Remove files that have been deleted
# Could be more efficient if done above
if not self.__stopped.is_set():
with db_session:
for track in Track.select(lambda t: t.root_folder == folder):
if not os.path.exists(track.path) or not self.__check_extension(
track.path
):
self.remove_file(track.path)
for track in Track.select().where(Track.root_folder == folder):
if not os.path.exists(track.path) or not self.__check_extension(
track.path
):
self.remove_file(track.path)
# Remove deleted/moved folders and update cover art info
folders = [folder]
while not self.__stopped.is_set() and folders:
f = folders.pop()
with db_session:
# f has been fetched from another session, refetch or Pony will complain
f = Folder[f.id]
if not f.root and not os.path.isdir(f.path):
f.delete_instance(recursive=True)
continue
if not f.root and not os.path.isdir(f.path):
f.delete() # Pony will cascade
continue
self.find_cover(f.path)
folders += f.children
self.find_cover(f.path)
folders += f.children[:]
if not self.__stopped.is_set():
with db_session:
Folder[folder.id].last_scan = int(time.time())
folder.last_scan = int(time.time())
folder.save()
if self.__on_folder_end is not None:
self.__on_folder_end(folder)
@ -178,10 +173,9 @@ class Scanner(Thread):
if self.__stopped.is_set():
return
with db_session:
self.__stats.deleted.albums = Album.prune()
self.__stats.deleted.artists = Artist.prune()
Folder.prune()
self.__stats.deleted.albums = Album.prune()
self.__stats.deleted.artists = Artist.prune()
Folder.prune()
def __check_extension(self, path):
if not self.__extensions:
@ -210,7 +204,7 @@ class Scanner(Thread):
mtime = int(stat.st_mtime)
tr = Track.get(path=path)
tr = Track.get_or_none(path=path)
if tr is not None:
if not self.__force and not mtime > tr.last_modification:
return
@ -253,7 +247,7 @@ class Scanner(Thread):
trdict["created"] = datetime.fromtimestamp(mtime)
try:
Track(**trdict)
Track.create(**trdict)
self.__stats.added.tracks += 1
except ValueError:
# Field validation error
@ -266,7 +260,9 @@ class Scanner(Thread):
trdict["artist"] = trartist
try:
tr.set(**trdict)
for attr, value in trdict.items():
setattr(tr, attr, value)
tr.save()
except ValueError:
# Field validation error
self.__stats.errors.append(path)
@ -275,12 +271,11 @@ class Scanner(Thread):
if not isinstance(path, str):
raise TypeError("Expecting string, got " + str(type(path)))
tr = Track.get(path=path)
if not tr:
return
self.__stats.deleted.tracks += 1
tr.delete()
try:
Track.get(path=path).delete_instance()
self.__stats.deleted.tracks += 1
except Track.DoesNotExist:
pass
def move_file(self, src_path, dst_path):
if not isinstance(src_path, str):
@ -291,8 +286,9 @@ class Scanner(Thread):
if src_path == dst_path:
return
tr = Track.get(path=src_path)
if tr is None:
try:
tr = Track.get(path=src_path)
except Track.DoesNotExist:
return
tr_dst = Track.get(path=dst_path)
@ -352,28 +348,23 @@ class Scanner(Thread):
def __find_album(self, artist, album):
ar = self.__find_artist(artist)
al = ar.albums.select(lambda a: a.name == album).first()
al = ar.albums.where(Album.name == album).first()
if al:
return al
al = Album(name=album, artist=ar)
self.__stats.added.albums += 1
return al
return Album.create(name=album, artist=ar)
def __find_artist(self, artist):
ar = Artist.get(name=artist)
if ar:
return ar
ar = Artist(name=artist)
self.__stats.added.artists += 1
return ar
try:
return Artist.get(name=artist)
except Artist.DoesNotExist:
self.__stats.added.artists += 1
return Artist.create(name=artist)
def __find_root_folder(self, path):
path = os.path.dirname(path)
for folder in Folder.select(lambda f: f.root):
for folder in Folder.select().where(Folder.root):
if path.startswith(folder.path):
return folder

View File

@ -1,7 +1,7 @@
# This file is part of Supysonic.
# Supysonic is a Python implementation of the Subsonic server API.
#
# Copyright (C) 2017-2020 Alban 'spl0k' Féron
# Copyright (C) 2017-2022 Alban 'spl0k' Féron
#
# Distributed under terms of the GNU AGPLv3 license.
@ -12,7 +12,6 @@ import tempfile
import unittest
from contextlib import contextmanager
from pony.orm import db_session, commit
from supysonic import db
from supysonic.managers.folder import FolderManager
@ -23,9 +22,8 @@ class ScannerTestCase(unittest.TestCase):
def setUp(self):
db.init_database("sqlite:")
with db_session:
folder = FolderManager.add("folder", os.path.abspath("tests/assets/folder"))
self.assertIsNotNone(folder)
folder = FolderManager.add("folder", os.path.abspath("tests/assets/folder"))
self.assertIsNotNone(folder)
self.folderid = folder.id
self.__scan()
@ -50,9 +48,7 @@ class ScannerTestCase(unittest.TestCase):
self.scanner = Scanner(force=force)
self.scanner.queue_folder("folder")
self.scanner.run()
commit()
@db_session
def test_scan(self):
self.assertEqual(db.Track.select().count(), 1)
@ -61,40 +57,32 @@ class ScannerTestCase(unittest.TestCase):
TypeError, self.scanner.queue_folder, db.Folder[self.folderid]
)
@db_session
def test_rescan(self):
self.__scan()
self.assertEqual(db.Track.select().count(), 1)
@db_session
def test_force_rescan(self):
self.__scan(True)
self.assertEqual(db.Track.select().count(), 1)
@db_session
def test_scan_file(self):
self.scanner.scan_file("/some/inexistent/path")
commit()
self.assertEqual(db.Track.select().count(), 1)
@db_session
def test_remove_file(self):
track = db.Track.select().first()
self.assertRaises(TypeError, self.scanner.remove_file, None)
self.assertRaises(TypeError, self.scanner.remove_file, track)
self.scanner.remove_file("/some/inexistent/path")
commit()
self.assertEqual(db.Track.select().count(), 1)
self.scanner.remove_file(track.path)
self.scanner.prune()
commit()
self.assertEqual(db.Track.select().count(), 0)
self.assertEqual(db.Album.select().count(), 0)
self.assertEqual(db.Artist.select().count(), 0)
@db_session
def test_move_file(self):
track = db.Track.select().first()
self.assertRaises(TypeError, self.scanner.move_file, None, "string")
@ -103,11 +91,9 @@ class ScannerTestCase(unittest.TestCase):
self.assertRaises(TypeError, self.scanner.move_file, "string", track)
self.scanner.move_file("/some/inexistent/path", track.path)
commit()
self.assertEqual(db.Track.select().count(), 1)
self.scanner.move_file(track.path, track.path)
commit()
self.assertEqual(db.Track.select().count(), 1)
self.assertRaises(
@ -118,17 +104,14 @@ class ScannerTestCase(unittest.TestCase):
self.__scan()
self.assertEqual(db.Track.select().count(), 2)
self.scanner.move_file(tf, track.path)
commit()
self.assertEqual(db.Track.select().count(), 1)
track = db.Track.select().first()
new_path = track.path.replace("silence", "silence_moved")
self.scanner.move_file(track.path, new_path)
commit()
self.assertEqual(db.Track.select().count(), 1)
self.assertEqual(track.path, new_path)
@db_session
def test_rescan_corrupt_file(self):
with self.__temporary_track_copy() as tf:
self.__scan()
@ -142,7 +125,6 @@ class ScannerTestCase(unittest.TestCase):
self.__scan(True)
self.assertEqual(db.Track.select().count(), 1)
@db_session
def test_rescan_removed_file(self):
with self.__temporary_track_copy():
self.__scan()
@ -151,7 +133,6 @@ class ScannerTestCase(unittest.TestCase):
self.__scan()
self.assertEqual(db.Track.select().count(), 1)
@db_session
def test_scan_tag_change(self):
with self.__temporary_track_copy() as tf:
self.__scan()
@ -165,6 +146,7 @@ class ScannerTestCase(unittest.TestCase):
tags.save()
self.__scan(True)
copy = db.Track.get(path=tf)
self.assertEqual(copy.artist.name, "Renamed artist")
self.assertEqual(copy.album.name, "Crappy album")
self.assertIsNotNone(db.Artist.get(name="Some artist"))