0.2.76 - Icon Packs
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/library"
|
||||
)
|
||||
|
||||
// The two things that push into the gateway.
|
||||
//
|
||||
// Radarr's hook was already here, announcing a film as news. Both hooks now also *record*
|
||||
// what changed, which is the half that replaces asking Emby every hour whether anything
|
||||
// had happened: Sonarr and Radarr are the things that put files on disk, so they are the
|
||||
// things that know.
|
||||
//
|
||||
// Recording is all a hook does. The lookup, the import and the retry all belong to the
|
||||
// worker in internal/library, which is what lets these answer in a millisecond and, more
|
||||
// importantly, what lets them answer *at all* during quiet hours — see below.
|
||||
|
||||
// ingesterHandle is the slice of the ingest worker the API needs, so api does not depend
|
||||
// on the concrete type for testing. Same arrangement as syncerHandle.
|
||||
type ingesterHandle interface {
|
||||
Enqueue(ctx context.Context, source string, requests []library.IngestRequest) (int, error)
|
||||
}
|
||||
|
||||
// handleSonarrWebhook accepts Sonarr's import, upgrade, rename and delete notifications.
|
||||
//
|
||||
// Unconfigured means absent — the stance /admin and the Radarr hook already take: a
|
||||
// deployment that never set a token must not expose an endpoint anything can post to.
|
||||
func (s *Server) handleSonarrWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
if s.cfg.SonarrWebhookToken == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if subtle.ConstantTimeCompare(
|
||||
[]byte(webhookToken(r)), []byte(s.cfg.SonarrWebhookToken),
|
||||
) != 1 {
|
||||
writeError(w, http.StatusUnauthorized, "invalid webhook token")
|
||||
return
|
||||
}
|
||||
|
||||
var payload library.SonarrWebhook
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&payload); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid webhook payload")
|
||||
return
|
||||
}
|
||||
// Sonarr's Test button posts a stub. Answering 200 without recording work about a
|
||||
// series that does not exist is what makes that button mean "reachable".
|
||||
if library.IsTestEvent(payload.EventType) {
|
||||
s.loggerFor(r.Context()).Info("sonarr webhook test received")
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "test": true})
|
||||
return
|
||||
}
|
||||
|
||||
queued := s.queueIngest(r, "sonarr", payload.EventType, library.SonarrRequests(payload))
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "queued": queued})
|
||||
}
|
||||
|
||||
// queueIngest records the work a notification implies and reports how much of it was news.
|
||||
//
|
||||
// The context is deliberately detached from the request. A webhook is answered in a
|
||||
// millisecond and Sonarr closes the connection; hanging the insert off the request would
|
||||
// abandon exactly the deliveries that arrive in bursts, which is what a season pack is.
|
||||
func (s *Server) queueIngest(
|
||||
r *http.Request, source, eventType string, requests []library.IngestRequest,
|
||||
) int {
|
||||
log := s.loggerFor(r.Context())
|
||||
if s.ingester == nil {
|
||||
return 0
|
||||
}
|
||||
if len(requests) == 0 {
|
||||
// A grab, a health check, an unfollowed series whose files stayed on disk: all
|
||||
// real events, none of them a reason to re-read anything.
|
||||
log.Debug("webhook implies no catalogue work", "source", source, "event", eventType)
|
||||
return 0
|
||||
}
|
||||
ctx := context.WithoutCancel(r.Context())
|
||||
queued, err := s.ingester.Enqueue(ctx, source, requests)
|
||||
if err != nil {
|
||||
// The event is lost, which is the one failure worth an error line here: the *arrs
|
||||
// do not re-deliver, so nothing will bring this news again. The reconciliation
|
||||
// sweep is what eventually covers it.
|
||||
log.Error("could not record webhook work",
|
||||
"source", source, "event", eventType, "error", err)
|
||||
return queued
|
||||
}
|
||||
if queued > 0 {
|
||||
log.Info("arr ingest queued",
|
||||
"event", "arr_ingest", "source", source, "webhook_event", eventType,
|
||||
"outcome", "queued", "items", queued, "reason", requests[0].Reason)
|
||||
}
|
||||
return queued
|
||||
}
|
||||
Reference in New Issue
Block a user