0.1.52 Gateway

This commit is contained in:
ponzischeme89
2026-08-17 19:09:17 +12:00
parent d5a8d3ad88
commit 1da91e40a1
46 changed files with 1626 additions and 106 deletions
+47 -6
View File
@@ -97,20 +97,61 @@ func heroScheduleTimeZone(location *time.Location) string {
return location.String()
}
// heroSearchLimit is what either half of the picker's search may contribute, and what the
// merged answer is trimmed back to.
const heroSearchLimit = 20
// handleAdminHeroSearch answers the picker from the imported catalogue and from Emby.
//
// The catalogue alone is up to a sync interval stale, so a film imported this afternoon
// was simply not findable here until the next hourly pass — and pinning is the one hero
// decision an operator makes about a title *because* it has just arrived. Emby is asked
// as well and `Find` imports what it returns, which is what makes a fresh id usable by the
// policy validation and by the hero row itself rather than only by this list.
//
// Either half may fail without failing the search: a stale answer and a live one are both
// better than an error, and the two are deliberately asked in that order so a gateway with
// no Emby credentials configured still has a picker.
func (s *Server) handleAdminHeroSearch(w http.ResponseWriter, r *http.Request) {
items, err := s.store.SearchLibrary(r.Context(), r.URL.Query().Get("q"), 20)
term := strings.TrimSpace(r.URL.Query().Get("q"))
items, err := s.store.SearchLibrary(r.Context(), term, heroSearchLimit)
if err != nil {
s.loggerFor(r.Context()).Error("hero library search failed", "error", err)
writeError(w, http.StatusInternalServerError, "could not search the library")
return
}
results := make([]heroAdminItem, 0, len(items))
for _, raw := range items {
if item, ok := adminHeroItem(raw); ok {
results = append(results, item)
if s.syncer != nil && term != "" {
found, findErr := s.syncer.Find(r.Context(), term, heroSearchLimit)
if findErr != nil {
s.loggerFor(r.Context()).Warn("hero live search unavailable", "error", findErr)
}
items = append(items, found...)
}
writeJSON(w, http.StatusOK, map[string]any{"items": results})
writeJSON(w, http.StatusOK, map[string]any{"items": mergeHeroSearchResults(items, heroSearchLimit)})
}
// mergeHeroSearchResults turns both halves of the search into one list.
//
// Almost every title comes back from both, so the dedupe is the ordinary case rather than
// the exception, and it keeps the first sighting: the catalogue answers first, and its
// ranking is the one an operator has been reading all along. Anything an id appears in
// only once is either a title Emby has and the last import missed — the whole point — or
// one deleted from Emby that the catalogue has not swept yet.
func mergeHeroSearchResults(items []json.RawMessage, limit int) []heroAdminItem {
results := make([]heroAdminItem, 0, len(items))
seen := make(map[string]bool, len(items))
for _, raw := range items {
if len(results) >= limit {
break
}
item, ok := adminHeroItem(raw)
if !ok || seen[item.ID] {
continue
}
seen[item.ID] = true
results = append(results, item)
}
return results
}
func (s *Server) handleAdminHeroPolicy(w http.ResponseWriter, r *http.Request) {