From 385a716516858f144c9d33e040540d185b8e472e Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Thu, 18 Sep 2025 02:48:31 -0500 Subject: [PATCH] fixed jellyfin refresh --- backend/internal/jellyfin.go | 8 +--- backend/internal/refresh.go | 82 ++++++++++++++++++++++++++++++++++++ backend/main.go | 2 +- 3 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 backend/internal/refresh.go diff --git a/backend/internal/jellyfin.go b/backend/internal/jellyfin.go index 729e072..ce3495b 100644 --- a/backend/internal/jellyfin.go +++ b/backend/internal/jellyfin.go @@ -4,7 +4,7 @@ package internal import ( "bytes" "encoding/json" - "fmt" + "fmt" // <-- keep this; used by fmt.Errorf "net/http" "os" "time" @@ -57,9 +57,3 @@ func (j *Jellyfin) AuthenticateByName(username, password string) (jfAuthResult, } return out, nil } - -func (j *Jellyfin) RefreshLibrary(token string) { - req, _ := http.NewRequest("GET", j.BaseURL+"/Library/Refresh", nil) - req.Header.Set("X-MediaBrowser-Token", token) - _, _ = j.Client.Do(req) -} diff --git a/backend/internal/refresh.go b/backend/internal/refresh.go new file mode 100644 index 0000000..7212230 --- /dev/null +++ b/backend/internal/refresh.go @@ -0,0 +1,82 @@ +// backend/internal/refresh.go +package internal + +import ( + "io" + "log" + "net/http" + "os" + "strings" + "sync" + "time" +) + +// Debounce refresh calls so a burst of uploads/renames/deletes only triggers one scan. +var ( + refreshMu sync.Mutex + refreshTimer *time.Timer +) + +// RefreshLibrary triggers a Jellyfin library scan. +// - Prefers JELLYFIN_API_KEY (admin key). Falls back to userToken if no key. +// - Debounced: calls within 2s are coalesced to a single scan. +func (j *Jellyfin) RefreshLibrary(userToken string) { + refreshMu.Lock() + defer refreshMu.Unlock() + + if refreshTimer != nil { + // push the timer out a bit if more events arrive + refreshTimer.Reset(2 * time.Second) + return + } + + refreshTimer = time.AfterFunc(2*time.Second, func() { + base := strings.TrimRight(os.Getenv("JELLYFIN_URL"), "/") + if base == "" { + log.Printf("jellyfin refresh: JELLYFIN_URL missing; skipping") + safeClearTimer() + return + } + token := os.Getenv("JELLYFIN_API_KEY") + if token == "" { + token = userToken // last resort (may fail if user is not admin) + } + if token == "" { + log.Printf("jellyfin refresh: no token; skipping") + safeClearTimer() + return + } + + req, _ := http.NewRequest(http.MethodPost, base+"/Library/Refresh", nil) + // Jellyfin/Emby accept API key via header or query; header is cleanest. + // Either X-Emby-Token or X-MediaBrowser-Token are accepted; we use X-Emby-Token. + req.Header.Set("X-Emby-Token", token) + + client := j.Client + if client == nil { + client = &http.Client{Timeout: 20 * time.Second} + } + resp, err := client.Do(req) + if err != nil { + log.Printf("jellyfin refresh: request error: %v", err) + safeClearTimer() + return + } + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + + if resp.StatusCode >= 300 { + log.Printf("jellyfin refresh: HTTP %s", resp.Status) + } else { + log.Printf("jellyfin refresh: triggered") + } + + safeClearTimer() + }) +} + +func safeClearTimer() { + refreshMu.Lock() + defer refreshMu.Unlock() + refreshTimer = nil +} diff --git a/backend/main.go b/backend/main.go index fbb73bf..79fdbeb 100644 --- a/backend/main.go +++ b/backend/main.go @@ -100,7 +100,7 @@ func main() { BasePath: "/tus/", StoreComposer: composer, NotifyCompleteUploads: true, - MaxSize: 4 * 1024 * 1024 * 1024, // 4GB per file + MaxSize: 8 * 1024 * 1024 * 1024, // 8GB per file RespectForwardedHeaders: true, // <<< important for https behind Traefik } tusHandler, err := tusd.NewUnroutedHandler(config)