diff --git a/conftest.py b/conftest.py index b21a96e..6faf178 100644 --- a/conftest.py +++ b/conftest.py @@ -83,6 +83,7 @@ def config(isolated_dir: Path) -> Config: fuse_labels_blacklist=None, cover_art_stems=["cover", "folder", "art", "front"], valid_art_exts=["jpg", "jpeg", "png"], + max_filename_bytes=180, path_templates=PathTemplateConfig.with_defaults(), rename_source_files=False, ignore_release_directories=[], diff --git a/rose/cache.py b/rose/cache.py index 3158b6d..072bc64 100644 --- a/rose/cache.py +++ b/rose/cache.py @@ -908,7 +908,8 @@ def _update_cache_for_releases_executor( new_source_path = release.source_path.with_name(wanted_dirname) # If there is a collision, bump the collision counter and retry. if new_source_path.exists(): - wanted_dirname = f"{original_wanted_dirname} [{collision_no}]" + new_max_len = c.max_filename_bytes - (3 + len(str(collision_no))) + wanted_dirname = f"{original_wanted_dirname[:new_max_len]} [{collision_no}]" collision_no += 1 continue # If no collision, rename the directory. @@ -943,9 +944,10 @@ def _update_cache_for_releases_executor( ) and wanted_filename != relpath: new_source_path = release.source_path / wanted_filename if new_source_path.exists(): - wanted_filename = ( - f"{original_wanted_stem} [{collision_no}]{original_wanted_suffix}" + new_max_len = c.max_filename_bytes - ( + 3 + len(str(collision_no)) + len(original_wanted_suffix) ) + wanted_filename = f"{original_wanted_stem[:new_max_len]} [{collision_no}]{original_wanted_suffix}" collision_no += 1 continue old_source_path = track.source_path diff --git a/rose/cache_test.py b/rose/cache_test.py index f3b75f0..50a4f4c 100644 --- a/rose/cache_test.py +++ b/rose/cache_test.py @@ -421,6 +421,26 @@ def test_update_cache_releases_delete_nonexistent(config: Config) -> None: assert cursor.fetchone()[0] == 0 +def test_update_cache_releases_enforces_max_len(config: Config) -> None: + """Test that an directory with no audio files is skipped.""" + config = dataclasses.replace(config, rename_source_files=True, max_filename_bytes=15) + shutil.copytree(TEST_RELEASE_1, config.music_source_dir / "a") + shutil.copytree(TEST_RELEASE_1, config.music_source_dir / "b") + shutil.copy(TEST_RELEASE_1 / "01.m4a", config.music_source_dir / "b" / "03.m4a") + update_cache_for_releases(config) + assert set(config.music_source_dir.iterdir()) == { + config.music_source_dir / "BLACKPINK - 199", + config.music_source_dir / "BLACKPINK - [2]", + } + # Nondeterministic: Pick the one with the extra file. + children_1 = set((config.music_source_dir / "BLACKPINK - 199").iterdir()) + children_2 = set((config.music_source_dir / "BLACKPINK - [2]").iterdir()) + files = children_1 if len(children_1) > len(children_2) else children_2 + release_dir = next(iter(files)).parent + assert release_dir / "01. Track 1.m4a" in files + assert release_dir / "01. Tra [2].m4a" in files + + def test_update_cache_releases_skips_empty_directory(config: Config) -> None: """Test that an directory with no audio files is skipped.""" rd = config.music_source_dir / "lalala" diff --git a/rose/common.py b/rose/common.py index feb7e87..1af6b29 100644 --- a/rose/common.py +++ b/rose/common.py @@ -97,7 +97,7 @@ def sanitize_dirname(c: "Config", name: str, enforce_maxlen: bool) -> str: """ name = ILLEGAL_FS_CHARS_REGEX.sub("_", name) if enforce_maxlen: - name = name.encode("utf-8")[: c.max_filename_bytes].decode("utf-8", "ignore") + name = name.encode("utf-8")[: c.max_filename_bytes].decode("utf-8", "ignore").strip() return name @@ -111,7 +111,7 @@ def sanitize_filename(c: "Config", name: str, enforce_maxlen: bool) -> str: if len(ext.encode()) > 6: stem = name ext = "" - stem = stem.encode("utf-8")[: c.max_filename_bytes].decode("utf-8", "ignore") + stem = stem.encode("utf-8")[: c.max_filename_bytes].decode("utf-8", "ignore").strip() name = stem + ext return name