diff --git a/core/metadata/art_apply.py b/core/metadata/art_apply.py index fe79d266..faac0b7d 100644 --- a/core/metadata/art_apply.py +++ b/core/metadata/art_apply.py @@ -126,10 +126,13 @@ def apply_art_to_album_files( # Pre-flight: if the mount is read-only, every save below would fail with # EROFS one by one (Tim's report: a wall of per-file warnings and a 777 # chmod that couldn't help). statvfs asks the kernel without writing. + # POSIX-only — Windows has no os.statvfs (and no ':ro' bind mounts); + # there we skip straight to the per-file path, same as before this fix. probe_dir = folder or (os.path.dirname(paths[0]) if paths else None) - if probe_dir: + _statvfs = getattr(os, "statvfs", None) + if probe_dir and _statvfs is not None: try: - if os.statvfs(probe_dir).f_flag & os.ST_RDONLY: + if _statvfs(probe_dir).f_flag & getattr(os, "ST_RDONLY", 1): logger.warning( "Art apply skipped: %s is on a READ-ONLY filesystem " "(docker ':ro' volume mount — chmod cannot fix this)", diff --git a/tests/test_art_apply.py b/tests/test_art_apply.py index a7661ef3..4169eab9 100644 --- a/tests/test_art_apply.py +++ b/tests/test_art_apply.py @@ -203,3 +203,22 @@ def test_apply_normal_failure_not_flagged_read_only(tmp_path, monkeypatch): assert res['read_only_fs'] is False # EACCES is chmod-able, EROFS is not assert res['failed'] == 1 + + +def test_apply_works_without_statvfs_windows(tmp_path, monkeypatch): + """Windows has no os.statvfs — the pre-flight must vanish gracefully, + not crash the apply (Boulder runs on Windows).""" + f = tmp_path / 'a.mp3' + f.write_bytes(b'') + saved = [] + audio = SimpleNamespace(pictures=[], tags=None, add_tags=lambda: None, + save=lambda: saved.append(True)) + monkeypatch.setattr(aa, 'get_mutagen_symbols', lambda: _fake_symbols(audio)) + monkeypatch.setattr(aa, 'embed_album_art_metadata', lambda *a, **k: True) + monkeypatch.setattr(aa, 'download_cover_art', lambda *a, **k: None) + monkeypatch.delattr(aa.os, 'statvfs', raising=False) + + res = aa.apply_art_to_album_files([str(f)], {}, {}, folder=str(tmp_path)) + + assert res['embedded'] == 1 and saved == [True] + assert res['read_only_fs'] is False