The Identify dialog has nothing to call: ItemLookup is one stubbed route of eleven #199
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ItemLookupis 1 of 11 answered inENDPOINTS.md, and the one is a stub. Theweb UI's Identify dialog — the one that fixes a film the scan matched
wrongly — has nothing to call, so a mismatched row can only be corrected by
renaming the file and rescanning.
Unanswered:
POST /Items/RemoteSearch/{Movie,Series,BoxSet,MusicAlbum,MusicArtist,MusicVideo,Person,Trailer,Book}and
POST /Items/RemoteSearch/Apply/{itemId}.Answered, emptily:
ExternalIdInfosis what populates the dialog's or enter an id yourselffields, so even the manual half is blank.
The fetchers are here; the routes are not
crates/ephyra-app/src/metadata/already reaches every database the dialogwould search, and one of them already searches:
It takes the first result, as upstream takes it when a scan identifies a
series. That is the asymmetry to close: a scan wants one answer, and the
Identify dialog wants the list the first was taken from. So the work is not a
new fetcher — it is lifting the search to return candidates
(
RemoteSearchResult: name, year, the poster url, the provider ids) andspelling a route per item kind over it.
Providers in the tree:
metadata/tmdb/{mod,tv,person,box_set}.rs,metadata/omdb.rs,metadata/musicbrainz.rs,metadata/audiodb.rs. Betweenthem they cover every
RemoteSearchkind exceptBook.Apply is a refresh, not a write
upstream/Jellyfin.Api/Controllers/ItemLookupController.cs:245—ApplySearchCriteriasets the provider ids on the row explicitly (a refreshwill not erase them), then calls
RefreshFullItemwithReplaceAllMetadata,RemoveOldMetadata, andReplaceAllImagesoff the query string.204onsuccess,
404for an unknown item, and it requires elevation.Our equivalent of that refresh is the provider chain in
crates/ephyra-app/src/metadata/mod.rs(merge/apply) driven byscan::run's refresh path, and the lock rule already there matters: M10 mademergetake the set of locked fields, so an Apply has to decide whether theoperator's explicit identification overrides a lock. Upstream's does.
Verifying it
D27 is the constraint: a fetcher reaches a network and a network is not
something two servers can be compared over. The recording is
crates/ephyra-app/corpus/fetched.json, the answer key isenriched.json, andthey are cut together by
cargo xtask reference gen-enriched-corpus [--extend]—--extendkeeps everyanswer already recorded and asks only for the new ones, which is what a search
route needs. A replay that misses hard-fails rather than falling through to the
network, so an unrecorded search is a failing test and not a silent live call.
Open
ExternalIdInfoscomes first. It is the cheap half — a staticdescription of the providers a kind has, with no network at all — and it
makes the manual id entry in the dialog work on its own.
so that route has no provider to call and should probably stay unwritten
rather than answer an empty list.
Done in
f6ddc801andc4bb3040.The half that reaches no network
/Items/{id}/ExternalIdInfosanswered an empty array, and so did the editdialog's own copy of the same list — so the dialog drew no id boxes at all and
an operator could not correct a row by hand. Both now answer from one table,
and the route resolves its item and takes an administrator, neither of which
the stub did.
The table is upstream's twenty-five registered ids in upstream's
registration order, which is assembly scan order rather than anything the C#
states. It was read off a running upstream:
MusicBrainzbeforeTheAudioDb,and
TmdbbeforeTmdbCollection, were both the wrong way round first.Measured over 159 items in thirteen kinds, every one agreeing — three of those
kinds are in no fixture and were seeded into both databases to be asked about.
The half that does
Every
RemoteSearchroute upstream declares.TMDbsearches for a film, aseries and a person;
OMDbfor a film, a trailer and a series. The chain isupstream's own — the id the item carries, then the id another database
resolves, then a search for the name — and
identify_serieskeeps itsshortcut, so a refresh holding an id still asks for nothing. A kind no
database here searches answers an empty list rather than a
404, which iswhat upstream answers where none of its providers supports one.
Applywrites the chosen ids through a newset_providersbefore therefresh rather than handing them to it: a pass reads what a row is filed
under, so an id not yet on the row is one nothing can look up. The refresh
keeps none of what was there, which is what corrects a row rather than merging
into it.
Twelve of thirteen searches agree with the pin field for field. Three things
it disagreed about first, each found by diffing rather than by reading:
release date as written; a search calls
ToUniversalTime()on a date withno zone in it, which .NET reads as local — so a film released on the first
of a year is offered under the year before it and the row a refresh then
writes says the other. Its two providers then disagree with each other: a
film's year comes off the converted stamp and a series' off the raw date.
Matched rather than corrected; on a server running UTC they coincide.
N/Aposter are kept as that databasespells them.
TMDbnumbers theTVDbid and writes theIMDbone.Not written, named rather than implied
The
MusicBrainzalbum and artist searches: upstream's query is not the onethe metadata path already makes (
artist:"X"c, quotes stripped from thename), so it is a port of its own rather than exposing what exists — and that
database's one-request-a-second limit makes a differential slow enough that
guessing would be worse than waiting.
BoxSet,MusicVideoandBookhaveroutes and no database here that answers them.