fix(explore): match Plex Discover titles to the library again
Two defects sank Explore's Plex integration. Discover started rejecting X-Plex-Container-Size=500 with a 400, so the watchlist membership snapshot never loaded: hearts stayed unknown and toggles dead. The snapshot now pages at 100, and getWatchlist refetches a rejected page in chunks of the row fetch's field-proven 25, so the next cap drift degrades gracefully instead of failing and callers' offset math survives either way. Worse, every Plex catalog item reached the library matcher carrying only its Discover rating key: listings were fetched without includeGuids, so the lookup rested entirely on exact plex:// guid equality between two metadata universes (Discover duplicate entries break it, notoriously for anime), and the title fallback can never confirm a candidate without external ids to intersect - "Not in your library" for owned titles the MAL provider matched fine. Discover listings now request Guids, the detail screen re-runs the matcher when enrichment gains id forms (generation-guarded so the slower bare lookup cannot overwrite the richer verdict), the matcher keys its memo by id fingerprint so the poor form's cached negative cannot answer for the rich one, and the Plex client stops burning title requests that external-id verification is guaranteed to reject. Discover requests are now logged like every other API surface; this bug shipped blind because they were not. close #1715
This commit is contained in:
@@ -103,7 +103,7 @@ class _CatalogItemDetailScreenState extends State<CatalogItemDetailScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_syncDetailCollections(widget.item);
|
||||
unawaited(_resolveMatches());
|
||||
unawaited(_resolveMatches(widget.item));
|
||||
unawaited(_loadDetail());
|
||||
final sources = context.read<CatalogSourcesProvider>();
|
||||
_watchlistSource = sources.watchlistSourceFor(widget.item);
|
||||
@@ -149,13 +149,22 @@ class _CatalogItemDetailScreenState extends State<CatalogItemDetailScreen> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _resolveMatches() async {
|
||||
/// Monotonic guard for match resolution: the bare row item and its
|
||||
/// detail-enriched form resolve concurrently, and only the latest-issued
|
||||
/// resolution may publish (a slow bare lookup must not overwrite the
|
||||
/// enriched verdict with its own).
|
||||
int _matchGeneration = 0;
|
||||
|
||||
Future<void> _resolveMatches(CatalogItem item) async {
|
||||
final generation = ++_matchGeneration;
|
||||
List<MediaItem> matches;
|
||||
try {
|
||||
_setMatches(await context.read<CatalogLibraryMatcher>().match(widget.item));
|
||||
matches = await context.read<CatalogLibraryMatcher>().match(item);
|
||||
} catch (e) {
|
||||
appLogger.w('Catalog library match failed for ${widget.item.identityKey}', error: e);
|
||||
_setMatches(const []);
|
||||
appLogger.w('Catalog library match failed for ${item.identityKey}', error: e);
|
||||
matches = const [];
|
||||
}
|
||||
if (generation == _matchGeneration) _setMatches(matches);
|
||||
}
|
||||
|
||||
void _setMatches(List<MediaItem> matches) {
|
||||
@@ -221,6 +230,14 @@ class _CatalogItemDetailScreenState extends State<CatalogItemDetailScreen> {
|
||||
_related = detail.related;
|
||||
_relationEntries = relationEntries;
|
||||
});
|
||||
// The row form of a Plex Discover item carries only its rating key;
|
||||
// the detail body brings the external ids (#1715). When enrichment
|
||||
// added id forms and the bare lookup found nothing, ask again with
|
||||
// the full set.
|
||||
final gainedIds = !widget.item.ids.allKeys.toSet().containsAll(detail.item.ids.allKeys);
|
||||
if (gainedIds && (_matches?.isEmpty ?? true)) {
|
||||
unawaited(_resolveMatches(detail.item));
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.d('Catalog detail load failed for ${widget.item.identityKey}', error: e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user