fix(explore): find library copies through the Plex search index and report servers that could not answer
Explore showed "Not in your library" for titles the user owns. Two causes: Plex's /library/all?title= filter is an ordered word-prefix substring match, so `Oshi no Ko` never found a library titled `[Oshi no Ko]` and a romaji or localized title was unreachable once the first candidate hit; and a server that answered slower than the 10s header budget was treated as a dead endpoint, dropped from the wave, cached as a negative for ten minutes and cascaded through its stale LAN candidates. Plex title candidates now go through /hubs/search with includeGuids, the full-text index behind Plex's own search, which also covers originalTitle. Both backends search every candidate concurrently and union the id-verified copies instead of stopping at the first title that hit; the candidate cap becomes two title families so a sequel can reach its alternate title. Lookup requests run under a dedicated deadline with endpoint failover off. The aggregation layer reports per-server failures, the matcher only memoizes complete positive waves, and the detail screen shows "Couldn't check N servers" instead of claiming absence. close #2098
This commit is contained in:
@@ -50,21 +50,13 @@ void main() {
|
||||
});
|
||||
|
||||
group('titleMatchCandidates', () {
|
||||
test('emits a title immediately followed by its stripped form', () {
|
||||
test('emits each title immediately followed by its stripped form', () {
|
||||
final candidates = titleMatchCandidates([
|
||||
'You and I Are Polar Opposites Season 2',
|
||||
'Seihantai na Kimi to Boku 2nd Season',
|
||||
]);
|
||||
// Capped at two, so the second provider title never gets a slot — the
|
||||
// stripped form of the first is worth far more than an alias.
|
||||
expect(candidates, ['You and I Are Polar Opposites Season 2', 'You and I Are Polar Opposites']);
|
||||
});
|
||||
|
||||
test('honours a wider cap by interleaving, never by listing raw titles first', () {
|
||||
final candidates = titleMatchCandidates([
|
||||
'You and I Are Polar Opposites Season 2',
|
||||
'Seihantai na Kimi to Boku 2nd Season',
|
||||
], limit: 4);
|
||||
// The second family is the only route to a copy filed under the romaji
|
||||
// title (#2098), and each family carries its own parent-show form.
|
||||
expect(candidates, [
|
||||
'You and I Are Polar Opposites Season 2',
|
||||
'You and I Are Polar Opposites',
|
||||
@@ -72,6 +64,14 @@ void main() {
|
||||
'Seihantai na Kimi to Boku',
|
||||
]);
|
||||
});
|
||||
|
||||
test('honours a narrower cap by interleaving, never by listing raw titles first', () {
|
||||
final candidates = titleMatchCandidates([
|
||||
'You and I Are Polar Opposites Season 2',
|
||||
'Seihantai na Kimi to Boku 2nd Season',
|
||||
], limit: 2);
|
||||
expect(candidates, ['You and I Are Polar Opposites Season 2', 'You and I Are Polar Opposites']);
|
||||
});
|
||||
test('normalizes typographic punctuation both backends miss on', () {
|
||||
// Verified live: Plex and Jellyfin both return 0 for the curly form.
|
||||
expect(titleMatchCandidates(['Frieren: Beyond Journey\u2019s End']), ["Frieren: Beyond Journey's End"]);
|
||||
@@ -103,7 +103,14 @@ void main() {
|
||||
'Mushoku Tensei 2',
|
||||
'MT2',
|
||||
]);
|
||||
expect(candidates, ['Mushoku Tensei: Jobless Reincarnation Season 2', 'Mushoku Tensei: Jobless Reincarnation']);
|
||||
// `II:` mid-title is not a sequel suffix, so the second family has no
|
||||
// stripped form and the third alias takes the last slot.
|
||||
expect(candidates, [
|
||||
'Mushoku Tensei: Jobless Reincarnation Season 2',
|
||||
'Mushoku Tensei: Jobless Reincarnation',
|
||||
'Mushoku Tensei II: Isekai Ittara Honki Dasu',
|
||||
'Mushoku Tensei 2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user