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:
parent
e589247458
commit
83ba85aaf1
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"))
|
||||
|
Loading…
Reference in New Issue
Block a user