v0.12.1: stop asking where the music is when config.json already says
Restarting on the machine that mounts the drive at /media/trav/muzak opened "LinTunes can't find your music folder", offering ~/Music. The library's only music_folder is the other machine's mount point (/run/media/trav/tummult/...) because this library predates 0.10 and has no music_folder_rel yet — but config.json has held the right path all along under music_root, which `--music-root … --save-config` writes and the importer stores as library.music_folder. resolve() only looked for music_folder_override, a key that exists solely because the prompt writes it, so a library imported the normal way could never satisfy the check that decides whether to prompt. music_root is now a machine-local candidate alongside music_folder_override, consulted in the same last-resort position and subject to the same must-exist rule, so a stale one still reports "missing" rather than resolving to a phantom tree. Read-only: nothing writes it back, and the newer key still wins when both are present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y9ZEFi4qNJ39FMiBtiAxy2
This commit is contained in:
@@ -140,7 +140,10 @@ persistence) → GUI (Qt widgets that read the manager and connect to its signal
|
|||||||
floor between versions), `music_folder_rel` relative to the data dir (the
|
floor between versions), `music_folder_rel` relative to the data dir (the
|
||||||
portable one, same trick as `Track.location`), and `music_folder_set_at`, a
|
portable one, same trick as `Track.location`), and `music_folder_set_at`, a
|
||||||
stamp so a merge prefers the newest *setting* rather than the newest *file*.
|
stamp so a merge prefers the newest *setting* rather than the newest *file*.
|
||||||
`resolve()` tries rel → legacy → the per-machine override in `config.json`,
|
`resolve()` tries rel → legacy → the per-machine override in `config.json`
|
||||||
|
(both `music_folder_override` *and* the older `music_root` that
|
||||||
|
`--music-root … --save-config` writes — an imported library already
|
||||||
|
knows where its music is on this machine, so don't ask),
|
||||||
and every candidate must **exist** — the override is consulted last so a
|
and every candidate must **exist** — the override is consulted last so a
|
||||||
machine that once needed one isn't pinned to it forever. Two rules that bite:
|
machine that once needed one isn't pinned to it forever. Two rules that bite:
|
||||||
`LibraryManager.set_music_folder` must call `mark_library_settings_dirty()`
|
`LibraryManager.set_music_folder` must call `mark_library_settings_dirty()`
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
||||||
|
|
||||||
__version__ = "0.12.0"
|
__version__ = "0.12.1"
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ single place that decides, in this order:
|
|||||||
machine even when the absolute paths are not.
|
machine even when the absolute paths are not.
|
||||||
2. ``music_folder`` — the legacy absolute value. Libraries written before 0.10,
|
2. ``music_folder`` — the legacy absolute value. Libraries written before 0.10,
|
||||||
and libraries last written by an older LinTunes, only have this one.
|
and libraries last written by an older LinTunes, only have this one.
|
||||||
3. The machine-local override in ``~/.config/lintunes/config.json``.
|
3. The machine-local override in ``~/.config/lintunes/config.json`` — either
|
||||||
|
``music_folder_override`` (set by the folder prompt) or the older
|
||||||
|
``music_root`` that ``--music-root … --save-config`` has always written.
|
||||||
|
|
||||||
The override is consulted **last, not first**, on purpose. If it won outright, a
|
The override is consulted **last, not first**, on purpose. If it won outright, a
|
||||||
machine that once needed an override would be pinned to it forever and would
|
machine that once needed an override would be pinned to it forever and would
|
||||||
@@ -32,6 +34,13 @@ from lintunes.paths import to_absolute
|
|||||||
# Keys in the per-machine config.json (never synced).
|
# Keys in the per-machine config.json (never synced).
|
||||||
OVERRIDE_KEY = "music_folder_override"
|
OVERRIDE_KEY = "music_folder_override"
|
||||||
PROMPT_SEEN_KEY = "music_folder_prompted"
|
PROMPT_SEEN_KEY = "music_folder_prompted"
|
||||||
|
# The same fact under its pre-0.10 name: `--music-root … --save-config` has
|
||||||
|
# always written this machine's media folder here, and the importer stores it as
|
||||||
|
# `library.music_folder`. Every library set up that way therefore has the right
|
||||||
|
# answer sitting in config.json, and ignoring it meant a machine that mounts the
|
||||||
|
# drive somewhere else was asked to locate a folder it had already been told
|
||||||
|
# about. Read-only — nothing writes it back, so the newer key stays canonical.
|
||||||
|
LEGACY_OVERRIDE_KEY = "music_root"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -73,8 +82,10 @@ def resolve(library, data_dir, config=None) -> Resolution:
|
|||||||
if found is not None:
|
if found is not None:
|
||||||
return Resolution(found, "legacy", legacy)
|
return Resolution(found, "legacy", legacy)
|
||||||
|
|
||||||
override = config.get(OVERRIDE_KEY, "")
|
for key in (OVERRIDE_KEY, LEGACY_OVERRIDE_KEY):
|
||||||
if override:
|
override = config.get(key, "")
|
||||||
|
if not override:
|
||||||
|
continue
|
||||||
found = _existing_dir(override)
|
found = _existing_dir(override)
|
||||||
if found is not None:
|
if found is not None:
|
||||||
return Resolution(found, "machine", override)
|
return Resolution(found, "machine", override)
|
||||||
|
|||||||
@@ -241,6 +241,46 @@ class TestMusicFolderResolution:
|
|||||||
assert state.path == elsewhere
|
assert state.path == elsewhere
|
||||||
assert state.source == "machine"
|
assert state.source == "machine"
|
||||||
|
|
||||||
|
def test_the_pre_010_music_root_key_counts_as_an_override(
|
||||||
|
self, tmp_path, isolated_config):
|
||||||
|
"""`--music-root … --save-config` has always written this machine's
|
||||||
|
media folder to config.json as `music_root`. Every library imported that
|
||||||
|
way already knows where its music is here, so a machine that mounts the
|
||||||
|
drive at a different path must not be asked to go find it."""
|
||||||
|
from lintunes.config import save_config
|
||||||
|
media = tmp_path / "media"
|
||||||
|
media.mkdir()
|
||||||
|
save_config({"music_root": str(media)})
|
||||||
|
|
||||||
|
# The shared value is another machine's mount point, unreachable here.
|
||||||
|
gone = Library(music_folder="/run/media/trav/tummult/music/iTunes Media")
|
||||||
|
state = music_folder.resolve(gone, tmp_path)
|
||||||
|
|
||||||
|
assert state.path == media
|
||||||
|
assert state.source == "machine"
|
||||||
|
|
||||||
|
def test_the_newer_override_key_still_wins(self, tmp_path, isolated_config):
|
||||||
|
from lintunes.config import save_config
|
||||||
|
chosen, imported = tmp_path / "chosen", tmp_path / "imported"
|
||||||
|
chosen.mkdir()
|
||||||
|
imported.mkdir()
|
||||||
|
save_config({"music_folder_override": str(chosen),
|
||||||
|
"music_root": str(imported)})
|
||||||
|
|
||||||
|
state = music_folder.resolve(Library(music_folder="/nope"), tmp_path)
|
||||||
|
|
||||||
|
assert state.path == chosen
|
||||||
|
|
||||||
|
def test_a_stale_music_root_does_not_resolve(self, tmp_path, isolated_config):
|
||||||
|
"""It's still a candidate, not an answer — the folder has to be there."""
|
||||||
|
from lintunes.config import save_config
|
||||||
|
save_config({"music_root": str(tmp_path / "unplugged")})
|
||||||
|
|
||||||
|
state = music_folder.resolve(Library(music_folder="/nope"), tmp_path)
|
||||||
|
|
||||||
|
assert state.path is None
|
||||||
|
assert state.source == "missing"
|
||||||
|
|
||||||
def test_unset_and_missing_are_different_conversations(self, tmp_path,
|
def test_unset_and_missing_are_different_conversations(self, tmp_path,
|
||||||
isolated_config):
|
isolated_config):
|
||||||
assert music_folder.resolve(Library(), tmp_path).source == "unset"
|
assert music_folder.resolve(Library(), tmp_path).source == "unset"
|
||||||
|
|||||||
Reference in New Issue
Block a user