diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-29 15:57:06 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-29 15:57:06 +0200 |
| commit | 4d167e9f958d26c1db920274974ae446426928e3 (patch) | |
| tree | e056e6b4fceca76ab67586548f0aa6ed80f925bc /packages/meshbay-node/src/meshbay_node | |
| parent | bc40ab2c4486cca4ae63e33976151b5d7f856a84 (diff) | |
| download | meshbay-4d167e9f958d26c1db920274974ae446426928e3.tar.gz | |
feat: V13 — per-card "re-match this one file" (MNP 0.13)
A one-click alternative to the full "Fix match" search-and-pick flow, and
reachable without SSH (`meshbay-node video rematch` clears a whole group).
- MNP 0.13: tmdb_rematch / tmdb_rematch_ack (additive — an older node
logs "unknown type", the button just does nothing). OP_TMDB_REMATCH,
signed like tmdb_override (media_cache is shared node-wide).
- media_cache.drop_tmdb_match(file_id): forgets the match AND the
override marker — deliberately stronger than clear_file_tmdb, since the
operator is explicitly asking for a fresh resolution.
- webrtc_server: _do_tmdb_rematch / _admin_exec_tmdb_rematch, dispatch +
admin-response routing, broadcasts tmdb_rematch_ack.
- transport.js: rematchTmdbMatch(fileId, signFn); 'tmdb_rematch' in the
admin-op allowlist; tmdb_rematch_ack handled like tmdb_override_ack.
- video-app.js: a "Re-match" button beside "Fix match" in the detail
modal (isNodeAdmin), then bumpMediaMetaGeneration(). video.rematch_one
key in all ten locales.
docs/mediacenter.md §10.1: V8–V13 marked done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/media_cache.py | 12 | ||||
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 49 |
2 files changed, 61 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_cache.py b/packages/meshbay-node/src/meshbay_node/media_cache.py index 4600a09..8233270 100644 --- a/packages/meshbay-node/src/meshbay_node/media_cache.py +++ b/packages/meshbay-node/src/meshbay_node/media_cache.py @@ -175,6 +175,18 @@ class MediaCache: "(SELECT file_id FROM tmdb_override)", (file_id,)) await self._db.commit() + async def drop_tmdb_match(self, file_id: str) -> None: + """ + Full per-file reset: forget the match *and* any manual override + marker, so the next `media_meta_req` re-resolves from scratch with + the current matcher. This is the explicit operator "re-match this + one" action (§10.1/V13) — deliberately stronger than + `clear_file_tmdb`, which spares an override. + """ + await self._db.execute("DELETE FROM file_tmdb WHERE file_id = ?", (file_id,)) + await self._db.execute("DELETE FROM tmdb_override WHERE file_id = ?", (file_id,)) + await self._db.commit() + async def clear_tmdb_matches(self, file_ids: list[str]) -> int: """ Drop the auto-resolved file->tmdb mappings for these files so the diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index c75819e..af6bf08 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -70,6 +70,7 @@ from meshbay_common.adminop import ( OP_TMDB_ENABLED, OP_VIDEO_ROOT, OP_TMDB_OVERRIDE, + OP_TMDB_REMATCH, OP_MUSICBRAINZ_ENABLED, OP_AUDIO_ROOT, OP_PHOTO_ROOTS, @@ -475,6 +476,8 @@ class WebRTCPeerSession: self._spawn(self._do_tmdb_search_request(msg)) elif mtype == MNP.TMDB_OVERRIDE: self._do_tmdb_override(msg) + elif mtype == MNP.TMDB_REMATCH: + self._do_tmdb_rematch(msg) elif mtype == MNP.MUSICBRAINZ_ENABLED: self._do_musicbrainz_enabled(msg) elif mtype == MNP.MUSIC_META_REQ: @@ -3171,6 +3174,49 @@ class WebRTCPeerSession: except Exception: pass + def _do_tmdb_rematch(self, msg: dict) -> None: + """ + An operator dropping one file's cached TMDB match so it re-resolves + with the current matcher (§10.1/V13) — the one-click alternative to + the full search-and-pick "Fix match" flow, and reachable without + SSH (`meshbay-node video rematch` clears a whole group). Signed like + `tmdb_override`: `media_cache` is shared node-wide. + """ + file_id = msg.get("file_id") + if not isinstance(file_id, str) or not file_id: + self._send({"type": "error", "detail": "Missing file_id"}) + return + if not self._group_ctx()["index"].get_entry(file_id): + self._send({"type": "error", "detail": "File not found"}) + return + if not self._has_admin_authority(): + self._send({"type": "error", "detail": "No authorized key for this"}) + return + self._issue_admin_challenge(OP_TMDB_REMATCH, f"file_id={file_id}") + + async def _admin_exec_tmdb_rematch( + self, pending: dict, transcript: bytes, sig: bytes, + ) -> None: + subject = pending["subject"] + if not await self._verify_admin_sig(transcript, sig): + self._send({"type": "error", "detail": "Signature verification failed"}) + self._audit("admin_auth_failed", f"tmdb_rematch:{subject}") + return + file_id = dict(part.split("=", 1) for part in subject.split(","))["file_id"] + media_cache = self._ctx.get("media_cache") + if media_cache is None: + self._send({"type": "error", "detail": "Media cache not available"}) + return + await media_cache.drop_tmdb_match(file_id) + self._audit("tmdb_rematch", subject) + + notice = {"type": MNP.TMDB_REMATCH_ACK, "v": MNP_VERSION, "file_id": file_id} + for uid, session in list(self._peer_registry().items()): + try: + session._send(notice) + except Exception: + pass + async def _tmdb_search(self, tmdb_client, entry, is_show: bool): """ §3.3's retry ladder — same shape for movies and shows (§10.1/V8). @@ -3874,6 +3920,9 @@ class WebRTCPeerSession: elif pending["op"] == OP_TMDB_OVERRIDE: self._spawn( self._admin_exec_tmdb_override(pending, transcript, sig_bytes)) + elif pending["op"] == OP_TMDB_REMATCH: + self._spawn( + self._admin_exec_tmdb_rematch(pending, transcript, sig_bytes)) elif pending["op"] == OP_MUSICBRAINZ_ENABLED: self._spawn( self._admin_exec_musicbrainz_enabled(pending, transcript, sig_bytes)) |