From 84aa73e89cfe486c62317f761dcf87d7845eb4f7 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 26 Aug 2026 15:36:23 +0200 Subject: fix(video): TMDB match cache never invalidated on movie<->show reclassification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real reason a node restart alone didn't fix already-indexed entries after the previous commit's enrichment change: _do_media_meta_request checked media_cache's file->tmdb mapping (keyed by content hash) and trusted it unconditionally, before ever comparing it against the file's *current* movie/show classification. A file whose season/episode changed on a later scan — exactly what the Specials-folder fix does, for every file it reclassifies from "movie" to "tv" — kept answering with its stale, wrong-kind-of-match forever, since nothing about a reclassification touches this cache or its key. Now falls through to a fresh search whenever the cached media_type disagrees with what the entry resolves to right now, rather than trusting a mapping that predates the file's current classification. --- .../src/meshbay_node/transport/webrtc_server.py | 16 ++++++++-- .../meshbay-node/tests/test_media_meta_request.py | 37 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) (limited to 'packages/meshbay-node') 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 724527b..2b3e3ee 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -2914,8 +2914,20 @@ class WebRTCPeerSession: meta = None tmdb_id = None if cached is not None: - tmdb_id, media_type = cached - meta = await media_cache.get_tmdb_meta(tmdb_id, media_type) + cached_tmdb_id, cached_media_type = cached + # Trustworthy only if it still agrees with what this file + # resolves to *now*. season/episode come from index-time + # enrichment (enrich.py), which can reclassify a file between + # movie and show on a later scan without this cache knowing — + # it is keyed by the file's content hash alone, which a + # reclassification never changes. Found live: an enrichment fix + # to a Specials-folder bug reclassified hundreds of files from + # "movie" to "tv", and every one kept answering with its + # stale movie-era match forever, because this was trusted + # before ever comparing media_type against the current one. + if cached_media_type == media_type: + tmdb_id = cached_tmdb_id + meta = await media_cache.get_tmdb_meta(tmdb_id, media_type) if meta is None: result, ratio = await self._tmdb_search(tmdb_client, entry, is_show) diff --git a/packages/meshbay-node/tests/test_media_meta_request.py b/packages/meshbay-node/tests/test_media_meta_request.py index a7355e8..a752825 100644 --- a/packages/meshbay-node/tests/test_media_meta_request.py +++ b/packages/meshbay-node/tests/test_media_meta_request.py @@ -112,6 +112,43 @@ async def test_two_episodes_in_the_same_season_folder_each_get_their_own_metadat "two different shows sharing a season folder must not resolve to the same match") +async def test_a_reclassified_file_ignores_its_stale_cached_match(media_cache): + """ + A file's classification (movie vs show) comes from its IndexEntry's + season/episode — set by index-time enrichment, which can change its + mind on a later scan (a filename-parsing fix reclassifying a whole + folder from "movie" to "tv", say) without media_cache's file->tmdb + mapping knowing anything happened: that cache is keyed by the file's + content hash alone, unchanged by any such reclassification. Found + live: exactly this scenario left every affected file answering with + its stale, wrong-kind-of-match forever, since the cache was trusted + before ever comparing media_type against what the entry resolves to + now. + """ + index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate()) + entry = IndexEntry(id="id-1", name="ep.mkv", path="shows/Show/Specials", + size=1, type="video", added_at=0, + display_title="Some Show", season=0, episode=1) + index.add_entry(entry) + # Pre-populate the cache exactly as it would be left over from before + # entry.season/episode existed — a movie search matched to some + # unrelated title, cached by this file's content hash. + await media_cache.set_file_tmdb("id-1", "stale-movie-id", "movie") + await media_cache.set_tmdb_meta("stale-movie-id", "movie", { + "title": "An Unrelated Movie", "original_title": "An Unrelated Movie", + "release_date": "1999-01-01", "confidence": 1.0, + }) + client = FakeTmdbClient() + session = _session(index, media_cache, client) + + await session._do_media_meta_request({"file_id": "id-1"}) + + resp = session.sent[0] + assert resp["title"] == "Some Show" + assert ("tv", "Some Show") in client.searched + assert resp["tmdb_id"] != "stale-movie-id" + + async def test_missing_file_id_is_refused(media_cache): index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate()) session = _session(index, media_cache, FakeTmdbClient()) -- cgit v1.2.3