package backgrounds import ( "context" "encoding/base64" "errors" "net/http" "net/url" "path" "sync" "time" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/utils/number" "gopkg.in/yaml.v3" ) type Handler struct { list []string lock sync.RWMutex } func NewHandler() *Handler { h := &Handler{} go func() { _, _ = h.getImageList(context.Background()) }() return h } const ndImageServiceURL = "https://www.navidrome.org/images" func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { image, err := h.getRandomImage(r.Context()) if err != nil { defaultImage, _ := base64.StdEncoding.DecodeString(consts.DefaultUILoginBackgroundOffline) w.Header().Set("content-type", "image/png") _, _ = w.Write(defaultImage) return } http.Redirect(w, r, buildPath(ndImageServiceURL, image), http.StatusFound) } func (h *Handler) getRandomImage(ctx context.Context) (string, error) { list, err := h.getImageList(ctx) if err != nil { return "", err } if len(list) == 0 { return "", errors.New("no images available") } rnd := number.RandomInt64(int64(len(list))) return list[rnd], nil } func (h *Handler) getImageList(ctx context.Context) ([]string, error) { h.lock.RLock() if len(h.list) > 0 { defer h.lock.RUnlock() return h.list, nil } h.lock.RUnlock() h.lock.Lock() defer h.lock.Unlock() start := time.Now() c := http.Client{ Timeout: 5 * time.Second, } req, _ := http.NewRequestWithContext(ctx, http.MethodGet, buildPath(ndImageServiceURL, "index.yml"), nil) resp, err := c.Do(req) if err != nil { log.Warn(ctx, "Could not get list from image service", err) return nil, err } defer resp.Body.Close() dec := yaml.NewDecoder(resp.Body) err = dec.Decode(&h.list) log.Debug(ctx, "Loaded images from image service", "total", len(h.list), "elapsed", time.Since(start)) return h.list, err } func buildPath(baseURL string, endpoint ...string) string { u, _ := url.Parse(baseURL) p := path.Join(endpoint...) u.Path = path.Join(u.Path, p) return u.String() }