2017-11-25 21:21:58 +00:00
|
|
|
# This file is part of Supysonic.
|
|
|
|
# Supysonic is a Python implementation of the Subsonic server API.
|
|
|
|
#
|
2023-01-17 21:59:43 +00:00
|
|
|
# Copyright (C) 2017-2023 Alban 'spl0k' Féron
|
2017-11-25 21:21:58 +00:00
|
|
|
#
|
|
|
|
# Distributed under terms of the GNU AGPLv3 license.
|
|
|
|
|
|
|
|
import mutagen
|
2020-11-08 14:39:09 +00:00
|
|
|
import os
|
2017-11-25 21:21:58 +00:00
|
|
|
import os.path
|
2023-01-17 21:59:43 +00:00
|
|
|
import shutil
|
2017-11-25 21:21:58 +00:00
|
|
|
import tempfile
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
|
|
|
|
from supysonic import db
|
|
|
|
from supysonic.managers.folder import FolderManager
|
|
|
|
from supysonic.scanner import Scanner
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
|
2017-11-25 21:21:58 +00:00
|
|
|
class ScannerTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
db.init_database("sqlite:")
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2022-12-10 17:04:09 +00:00
|
|
|
folder = FolderManager.add("folder", os.path.abspath("tests/assets/folder"))
|
|
|
|
self.assertIsNotNone(folder)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2020-01-19 15:45:31 +00:00
|
|
|
self.folderid = folder.id
|
|
|
|
self.__scan()
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def tearDown(self):
|
2017-12-19 22:16:55 +00:00
|
|
|
db.release_database()
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def __temporary_track_copy(self):
|
2017-12-17 22:25:34 +00:00
|
|
|
track = db.Track.select().first()
|
2020-11-08 14:39:09 +00:00
|
|
|
with tempfile.NamedTemporaryFile(
|
|
|
|
dir=os.path.dirname(track.path), delete=False
|
|
|
|
) as tf:
|
2020-11-22 15:12:14 +00:00
|
|
|
with open(track.path, "rb") as f:
|
2017-11-25 21:21:58 +00:00
|
|
|
tf.write(f.read())
|
2020-11-08 14:39:09 +00:00
|
|
|
try:
|
|
|
|
yield tf.name
|
|
|
|
finally:
|
|
|
|
os.remove(tf.name)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
def __scan(self, force=False):
|
2019-07-06 15:04:08 +00:00
|
|
|
self.scanner = Scanner(force=force)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.scanner.queue_folder("folder")
|
2019-05-11 15:13:58 +00:00
|
|
|
self.scanner.run()
|
|
|
|
|
2017-11-25 21:21:58 +00:00
|
|
|
def test_scan(self):
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-05-11 15:13:58 +00:00
|
|
|
self.assertRaises(TypeError, self.scanner.queue_folder, None)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertRaises(
|
|
|
|
TypeError, self.scanner.queue_folder, db.Folder[self.folderid]
|
|
|
|
)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_rescan(self):
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_force_rescan(self):
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan(True)
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_scan_file(self):
|
2019-06-29 15:25:44 +00:00
|
|
|
self.scanner.scan_file("/some/inexistent/path")
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_remove_file(self):
|
2017-12-17 22:25:34 +00:00
|
|
|
track = db.Track.select().first()
|
2017-11-25 21:21:58 +00:00
|
|
|
self.assertRaises(TypeError, self.scanner.remove_file, None)
|
|
|
|
self.assertRaises(TypeError, self.scanner.remove_file, track)
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
self.scanner.remove_file("/some/inexistent/path")
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
self.scanner.remove_file(track.path)
|
2019-05-11 15:13:58 +00:00
|
|
|
self.scanner.prune()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 0)
|
|
|
|
self.assertEqual(db.Album.select().count(), 0)
|
|
|
|
self.assertEqual(db.Artist.select().count(), 0)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_move_file(self):
|
2017-12-17 22:25:34 +00:00
|
|
|
track = db.Track.select().first()
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertRaises(TypeError, self.scanner.move_file, None, "string")
|
|
|
|
self.assertRaises(TypeError, self.scanner.move_file, track, "string")
|
|
|
|
self.assertRaises(TypeError, self.scanner.move_file, "string", None)
|
|
|
|
self.assertRaises(TypeError, self.scanner.move_file, "string", track)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
self.scanner.move_file("/some/inexistent/path", track.path)
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
self.scanner.move_file(track.path, track.path)
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertRaises(
|
|
|
|
Exception, self.scanner.move_file, track.path, "/some/inexistent/path"
|
|
|
|
)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
with self.__temporary_track_copy() as tf:
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 2)
|
2020-11-08 14:39:09 +00:00
|
|
|
self.scanner.move_file(tf, track.path)
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2017-12-17 22:25:34 +00:00
|
|
|
track = db.Track.select().first()
|
2019-06-29 15:25:44 +00:00
|
|
|
new_path = track.path.replace("silence", "silence_moved")
|
2017-11-25 21:21:58 +00:00
|
|
|
self.scanner.move_file(track.path, new_path)
|
2022-12-11 14:40:23 +00:00
|
|
|
|
|
|
|
track = db.Track.select().first()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
self.assertEqual(track.path, new_path)
|
|
|
|
|
|
|
|
def test_rescan_corrupt_file(self):
|
|
|
|
with self.__temporary_track_copy() as tf:
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 2)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2020-11-08 14:39:09 +00:00
|
|
|
with open(tf, "wb") as f:
|
|
|
|
f.seek(0, 0)
|
|
|
|
f.write(b"\x00" * 4096)
|
|
|
|
f.truncate()
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan(True)
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_rescan_removed_file(self):
|
2020-11-08 14:39:09 +00:00
|
|
|
with self.__temporary_track_copy():
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 2)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2017-12-17 22:25:34 +00:00
|
|
|
self.assertEqual(db.Track.select().count(), 1)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_scan_tag_change(self):
|
|
|
|
with self.__temporary_track_copy() as tf:
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan()
|
2020-11-08 14:39:09 +00:00
|
|
|
copy = db.Track.get(path=tf)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertEqual(copy.artist.name, "Some artist")
|
|
|
|
self.assertEqual(copy.album.name, "Awesome album")
|
2017-11-25 21:21:58 +00:00
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
tags = mutagen.File(copy.path, easy=True)
|
|
|
|
tags["artist"] = "Renamed artist"
|
|
|
|
tags["album"] = "Crappy album"
|
2017-11-25 21:21:58 +00:00
|
|
|
tags.save()
|
|
|
|
|
2019-05-11 15:13:58 +00:00
|
|
|
self.__scan(True)
|
2022-12-10 17:04:09 +00:00
|
|
|
copy = db.Track.get(path=tf)
|
2019-06-29 15:25:44 +00:00
|
|
|
self.assertEqual(copy.artist.name, "Renamed artist")
|
|
|
|
self.assertEqual(copy.album.name, "Crappy album")
|
|
|
|
self.assertIsNotNone(db.Artist.get(name="Some artist"))
|
|
|
|
self.assertIsNotNone(db.Album.get(name="Awesome album"))
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
def test_stats(self):
|
2018-01-20 17:26:58 +00:00
|
|
|
stats = self.scanner.stats()
|
|
|
|
self.assertEqual(stats.added.artists, 1)
|
|
|
|
self.assertEqual(stats.added.albums, 1)
|
|
|
|
self.assertEqual(stats.added.tracks, 1)
|
|
|
|
self.assertEqual(stats.deleted.artists, 0)
|
|
|
|
self.assertEqual(stats.deleted.albums, 0)
|
|
|
|
self.assertEqual(stats.deleted.tracks, 0)
|
2017-11-25 21:21:58 +00:00
|
|
|
|
|
|
|
|
2023-01-17 21:59:43 +00:00
|
|
|
class ScannerDeletionsTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
self.__dir = tempfile.mkdtemp()
|
|
|
|
db.init_database("sqlite:")
|
|
|
|
FolderManager.add("folder", self.__dir)
|
|
|
|
|
|
|
|
# Create folder hierarchy
|
|
|
|
self._firstsubdir = tempfile.mkdtemp(dir=self.__dir)
|
|
|
|
subdir = self._firstsubdir
|
|
|
|
for _ in range(4):
|
|
|
|
subdir = tempfile.mkdtemp(dir=subdir)
|
|
|
|
|
|
|
|
# Put a file in the deepest folder
|
|
|
|
self._trackpath = os.path.join(subdir, "silence.mp3")
|
|
|
|
shutil.copyfile("tests/assets/folder/silence.mp3", self._trackpath)
|
|
|
|
|
|
|
|
self._scan()
|
|
|
|
|
|
|
|
# Create annotation data
|
|
|
|
track = db.Track.get()
|
|
|
|
firstdir = db.Folder.get(path=self._firstsubdir)
|
|
|
|
user = db.User.create(
|
|
|
|
name="user", password="password", salt="salt", last_play=track
|
|
|
|
)
|
|
|
|
db.StarredFolder.create(user=user, starred=track.folder_id)
|
|
|
|
db.StarredFolder.create(user=user, starred=firstdir)
|
|
|
|
db.StarredArtist.create(user=user, starred=track.artist_id)
|
|
|
|
db.StarredAlbum.create(user=user, starred=track.album_id)
|
|
|
|
db.StarredTrack.create(user=user, starred=track)
|
|
|
|
db.RatingFolder.create(user=user, rated=track.folder_id, rating=2)
|
|
|
|
db.RatingFolder.create(user=user, rated=firstdir, rating=2)
|
|
|
|
db.RatingTrack.create(user=user, rated=track, rating=2)
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
db.release_database()
|
|
|
|
shutil.rmtree(self.__dir)
|
|
|
|
|
|
|
|
def _scan(self):
|
|
|
|
scanner = Scanner()
|
|
|
|
scanner.queue_folder("folder")
|
|
|
|
scanner.run()
|
|
|
|
|
|
|
|
return scanner.stats()
|
|
|
|
|
|
|
|
def _check_assertions(self, stats):
|
|
|
|
self.assertEqual(stats.deleted.artists, 1)
|
|
|
|
self.assertEqual(stats.deleted.albums, 1)
|
|
|
|
self.assertEqual(stats.deleted.tracks, 1)
|
|
|
|
self.assertEqual(db.Track.select().count(), 0)
|
|
|
|
self.assertEqual(db.Album.select().count(), 0)
|
|
|
|
self.assertEqual(db.Artist.select().count(), 0)
|
|
|
|
self.assertEqual(db.User.select().count(), 1)
|
|
|
|
self.assertEqual(db.Folder.select().count(), 1)
|
|
|
|
|
|
|
|
def test_parent_folder(self):
|
|
|
|
shutil.rmtree(self._firstsubdir)
|
|
|
|
stats = self._scan()
|
|
|
|
self._check_assertions(stats)
|
|
|
|
|
|
|
|
def test_track(self):
|
|
|
|
os.remove(self._trackpath)
|
|
|
|
stats = self._scan()
|
|
|
|
self._check_assertions(stats)
|
|
|
|
|
|
|
|
|
2019-06-29 15:25:44 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|