diff --git a/Makefile b/Makefile index f9cc78b..846a059 100644 --- a/Makefile +++ b/Makefile @@ -12,11 +12,11 @@ test-seq: coverage html lintcheck: - ruff . ruff format --check . + ruff . lint: - ruff --fix . ruff format . + ruff --fix . .PHONY: check test typecheck lintcheck lint diff --git a/conftest.py b/conftest.py index badde3f..0cf9279 100644 --- a/conftest.py +++ b/conftest.py @@ -80,8 +80,9 @@ def config(isolated_dir: Path) -> Config: fuse_artists_blacklist=None, fuse_genres_blacklist=None, fuse_labels_blacklist=None, - cover_art_stems=["cover","folder","art","front"], - valid_art_exts=["jpg","jpeg","png"], + cover_art_stems=["cover", "folder", "art", "front"], + valid_art_exts=["jpg", "jpeg", "png"], + ignore_release_directories=[], hash="00ff", ) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a7a7ec5..df14be3 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -84,6 +84,17 @@ fuse_labels_blacklist = [ "xxx" ] cover_art_stems = [ "folder", "cover", "art", "front" ] valid_art_exts = [ "jpg", "jpeg", "png" ] +# You may have some directories in your music source directory that should not +# be treated like releases. You can make Rosé ignore them by adding the +# directory names to this configuration variable. For example, if you use +# Syncthing versioning, the `.stversions` directory will contain music files, +# but Rosé should not scan them. +# +# By default, `!collages` and # `!playlists` are ignored. You do not need to +# add them to your ignore list: they will be ignored regardless of this +# configuration variable. +ignore_release_directories = [ ".stversions" ] + # The directory to write the cache to. Defaults to # `${XDG_CACHE_HOME:-$HOME/.cache}/rose`. cache_dir = "~/.cache/rose" diff --git a/rose/cache.py b/rose/cache.py index 9876daa..c32eaa0 100644 --- a/rose/cache.py +++ b/rose/cache.py @@ -223,9 +223,6 @@ class StoredDataFile: STORED_DATA_FILE_REGEX = re.compile(r"\.rose\.([^.]+)\.toml") -# TODO: Push this into the configuration. -ROSE_IGNORE_DIRS = [".stversions", "!collages", "!playlists"] - def update_cache(c: Config, force: bool = False) -> None: """ @@ -279,10 +276,14 @@ def update_cache_for_releases( We also shard the directories across multiple processes and execute them simultaneously. """ release_dirs = release_dirs or [ - Path(d.path) - for d in os.scandir(c.music_source_dir) - if d.is_dir() - if d.name not in ROSE_IGNORE_DIRS + Path(d.path) for d in os.scandir(c.music_source_dir) if d.is_dir() + ] + release_dirs = [ + d + for d in release_dirs + if d.name != "!collages" + and d.name != "!playlists" + and d.name not in c.ignore_release_directories ] logger.info(f"Refreshing the read cache for {len(release_dirs)} releases") logger.debug(f"Refreshing cached data for {', '.join([r.name for r in release_dirs])}") diff --git a/rose/cache_test.py b/rose/cache_test.py index 2673e7e..83cf1d1 100644 --- a/rose/cache_test.py +++ b/rose/cache_test.py @@ -526,6 +526,24 @@ def test_update_cache_releases_adds_aliased_artist(config: Config) -> None: } +def test_update_cache_releases_ignores_directories(config: Config) -> None: + """Test that the ignore_release_directories configuration value works.""" + config = Config(**{**asdict(config), "ignore_release_directories": ["lalala"]}) + release_dir = config.music_source_dir / "lalala" + shutil.copytree(TEST_RELEASE_1, release_dir) + + # Test that both arg+no-arg ignore the directory. + update_cache_for_releases(config) + with connect(config) as conn: + cursor = conn.execute("SELECT COUNT(*) FROM releases") + assert cursor.fetchone()[0] == 0 + + update_cache_for_releases(config) + with connect(config) as conn: + cursor = conn.execute("SELECT COUNT(*) FROM releases") + assert cursor.fetchone()[0] == 0 + + def test_update_cache_collages(config: Config) -> None: shutil.copytree(TEST_RELEASE_2, config.music_source_dir / TEST_RELEASE_2.name) shutil.copytree(TEST_COLLAGE_1, config.music_source_dir / "!collages") diff --git a/rose/config.py b/rose/config.py index d97f88c..269b68d 100644 --- a/rose/config.py +++ b/rose/config.py @@ -58,6 +58,8 @@ class Config: cover_art_stems: list[str] valid_art_exts: list[str] + ignore_release_directories: list[str] + hash: str @classmethod @@ -291,6 +293,22 @@ def read(cls, config_path_override: Path | None = None) -> Config: cover_art_stems = [x.lower() for x in cover_art_stems] valid_art_exts = [x.lower() for x in valid_art_exts] + try: + ignore_release_directories = data.get("ignore_release_directories", []) + if not isinstance(ignore_release_directories, list): + raise ValueError( + "ignore_release_directories must be a list[str]: " + f"got {type(ignore_release_directories)}" + ) + for s in ignore_release_directories: + if not isinstance(s, str): + raise ValueError(f"Each release directory must be of type str: got {type(s)}") + except ValueError as e: + raise InvalidConfigValueError( + f"Invalid value for ignore_release_directories in configuration file ({cfgpath}): " + "must be a list of strings" + ) from e + return cls( music_source_dir=music_source_dir, fuse_mount_dir=fuse_mount_dir, @@ -307,6 +325,7 @@ def read(cls, config_path_override: Path | None = None) -> Config: fuse_labels_blacklist=fuse_labels_blacklist, cover_art_stems=cover_art_stems, valid_art_exts=valid_art_exts, + ignore_release_directories=ignore_release_directories, hash=sha256(cfgtext.encode()).hexdigest(), ) diff --git a/rose/config_test.py b/rose/config_test.py index 4c886d1..d73eeb4 100644 --- a/rose/config_test.py +++ b/rose/config_test.py @@ -42,6 +42,7 @@ def test_config_full() -> None: fuse_labels_blacklist = [ "zzz" ] cover_art_stems = [ "aa", "bb" ] valid_art_exts = [ "tiff" ] + ignore_release_directories = [ "dummy boy" ] """ # noqa: E501 ) @@ -78,6 +79,7 @@ def test_config_full() -> None: fuse_labels_blacklist=["zzz"], cover_art_stems=["aa", "bb"], valid_art_exts=["tiff"], + ignore_release_directories=["dummy boy"], hash=c.hash, ) @@ -372,3 +374,20 @@ def write(x: str) -> None: == f"Invalid value for valid_art_exts in configuration file ({path}): must be a list of strings" # noqa ) config += '\nvalid_art_exts = [ "jpg" ]' + + # ignore_release_directories + write(config + '\nignore_release_directories = "lalala"') + with pytest.raises(InvalidConfigValueError) as excinfo: + Config.read(config_path_override=path) + assert ( + str(excinfo.value) + == f"Invalid value for ignore_release_directories in configuration file ({path}): must be a list of strings" # noqa + ) + write(config + "\nignore_release_directories = [123]") + with pytest.raises(InvalidConfigValueError) as excinfo: + Config.read(config_path_override=path) + assert ( + str(excinfo.value) + == f"Invalid value for ignore_release_directories in configuration file ({path}): must be a list of strings" # noqa + ) + config += '\nignore_release_directories = [ ".stversions" ]'