diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/daemon.py | 74 |
1 files changed, 42 insertions, 32 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index ae4e1f0..0015f1a 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -73,6 +73,12 @@ def _under_video_root(path: str, video_root: str) -> bool: return path == video_root or path.startswith(video_root + "/") +def _under_audio_root(path: str, audio_root: str) -> bool: + """Mirrors music-app.js's underAudioRoot — same shape as _under_video_root.""" + path = path or "" + return path == audio_root or path.startswith(audio_root + "/") + + # ── Argon2id calibration ────────────────────────────────────────────────────── def calibrate_argon2(target_ms: int = 500) -> None: @@ -340,6 +346,9 @@ class NodeDaemon: # group — "" means the whole group index. "video_root": await self._roster.video_root( group_cfg.id) if self._roster else "", + # Same shape, Music app's own entry point. + "audio_root": await self._roster.audio_root( + group_cfg.id) if self._roster else "", # Whether TMDB lookups run for this group at all — # per-group (2026-08-24, used to be node-wide), same # "read once, kept current in place by the signed op" @@ -541,7 +550,7 @@ class NodeDaemon: self._state["hub"] = hub self._state["reload_fn"] = self._reload_config self._state["enrich_video_root_fn"] = self._enrich_video_root_now - self._state["enrich_music_now_fn"] = self._enrich_music_now + self._state["enrich_audio_root_fn"] = self._enrich_audio_root_now # Rotating a key has to reach every transport holding a copy of it, # and clearing the denylist has to reach the one the handshake # consults — so both are published rather than reachable only @@ -750,6 +759,9 @@ class NodeDaemon: "video_root": ( await self._roster.video_root(group_cfg.id) if self._roster else ""), + "audio_root": ( + await self._roster.audio_root(group_cfg.id) + if self._roster else ""), "tmdb_enabled": ( await self._roster.tmdb_enabled(group_cfg.id) if self._roster else True), @@ -988,11 +1000,9 @@ class NodeDaemon: # INDEX_DELTA update (_on_enriched below). new_entries = delta.additions if delta is not None else list(idx.entries) asyncio.ensure_future(self._enrich_new_video_entries(indexer, new_entries)) - # Music app (docs/musicbay.md §6): same shape, gated on the group's - # enabled_apps rather than a root (Music has no video_root analogue — - # see musicbay.md §2.1's note on why that scoping wasn't carried - # over). Tag/cover extraction is local and cheap either way; the gate - # exists to not do it at all for a group that never turned Music on. + # Music app (docs/musicbay.md §6): same shape, gated on audio_root + # exactly like video_root above (added later — musicbay.md's + # original "no root, whole shared tree" call didn't hold up). asyncio.ensure_future(self._enrich_new_audio_entries(indexer, new_entries)) # A rename/move changes the very filename (or season folder) that @@ -1147,49 +1157,49 @@ class NodeDaemon: async def _enrich_new_audio_entries(self, indexer: DirectoryIndexer, entries: list) -> None: """ Music app (docs/musicbay.md §2.1, §6): fire (never await further) - tag/cover enrichment for unattempted audio entries, gated on the - group having "music" in its enabled_apps — there is no video_root - analogue for Music (musicbay.md §2.1 deliberately didn't add one: - tag reads are free/local, unlike ffprobe+ffmpeg thumbnailing, so the - cost this gate protects against is smaller, and most personal MP3 - libraries want their whole shared tree available rather than one - scoped subfolder). `_enriched_attempted` is shared with the video - path — content-addressed ids never collide across the two. + tag/cover enrichment for unattempted audio entries under the + group's configured audio_root — same gate as + `_enrich_new_video_entries` above (musicbay.md's original "no root, + whole shared tree" call turned out wrong against a real messy + library: everything under every shared folder got mixed together + with no way to scope it down). `_enriched_attempted` is shared with + the video path — content-addressed ids never collide across the two. """ if not self._audio_enricher or not self._roster: return - enabled_apps = await self._roster.enabled_apps(indexer.group_id) - if "music" not in enabled_apps: + audio_root = await self._roster.audio_root(indexer.group_id) + if not audio_root: return + root_boundary = indexer.roots.resolve(audio_root, require_available=False) for entry in entries: if entry.type != "audio" or entry.id in self._enriched_attempted: continue + if not _under_audio_root(entry.path, audio_root): + continue file_path = entry_abs_path(indexer.roots, entry) if not file_path or not file_path.exists(): continue self._enriched_attempted.add(entry.id) - # The entry's own named root, so the ancestor walk - # (enrich_audio._artist_album_from_ancestors) can tell "a - # top-level folder under this root" from "one level deeper" — - # without this boundary it used to read the root's own - # directory name as an artist for every flat top-level folder. - split = indexer.roots.split(entry.path) - root_path = split[0].path if split else None async def on_done(file_id: str, fields: dict, _indexer=indexer) -> None: await self._on_enriched(_indexer, file_id, fields) - self._audio_enricher.spawn(entry, file_path, on_done, root_path) + # `root_boundary` — audio_root itself, not the shared root it + # lives under — so the ancestor walk + # (enrich_audio._artist_album_from_ancestors) treats a flat + # top-level folder right under the *configured* Music root as + # ambiguous (artist-or-release, §2.1), not one level too shallow + # if audio_root is itself a subfolder of a larger shared root. + self._audio_enricher.spawn(entry, file_path, on_done, root_boundary) - async def _enrich_music_now(self, group_id: str) -> None: + async def _enrich_audio_root_now(self, group_id: str) -> None: """ - Music app: sweep a group's existing index right after "music" is - added to its enabled_apps (ops.set_enabled_apps) — the ordinary path - above only ever looks at entries new since the last broadcast, so a - library that was already sitting there before Music was turned on - would otherwise never get enriched. Mirrors - `_enrich_video_root_now`, triggered from a different setting because - Music has no root of its own to key off. + Music app: sweep a group's existing index right after its + audio_root is set or changed (ops.set_audio_root). Mirrors + `_enrich_video_root_now` exactly — the ordinary path above only + ever looks at entries new since the last broadcast, so a folder + that already had files in it before it became the audio_root would + otherwise never get enriched at all. """ indexer = self._state.get("indexers", {}).get(group_id) if not indexer: |