Something went wrong. Try again.
Self-hosted web interface and downloader for Qobuz.
downloader self-hosted music qobuz
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184package queue
import ( "context" "errors" "io" "log/slog" "net/http" "strconv" "strings"
"github.com/go-chi/chi/v5"
"gobuz/internal/log" "gobuz/internal/models" "gobuz/internal/web/pages")
type Catalog interface { GetArtistReleases(ctx context.Context, artistID string) ([]models.Release, error)}
type Controller struct { Manager *Manager Broker *SSEBroker Pages *pages.Pages Catalog Catalog logger *slog.Logger}
func NewController(manager *Manager, broker *SSEBroker, pages *pages.Pages, catalog Catalog, logger *slog.Logger) *Controller { return &Controller{ Manager: manager, Broker: broker, Pages: pages, Catalog: catalog, logger: logger, }}
func (c *Controller) Router() http.Handler { r := chi.NewRouter()
r.Get("/", c.QueueHandler) r.Post("/add", c.AddHandler) r.Post("/remove", c.RemoveHandler) r.Post("/clear", c.ClearHandler) r.Post("/start", c.StartHandler)
return r}
func (c *Controller) QueueHandler(w http.ResponseWriter, r *http.Request) { userID := models.GetUserIDFromContext(r.Context()) if err := c.Pages.RenderQueue(w, c.Manager.GetViewModel(userID)); err != nil { log.FromContext(r.Context()).Error("failed to render queue", "err", err) }}
func (c *Controller) AddHandler(w http.ResponseWriter, r *http.Request) { c.handleQueueItem(w, r, true)}
func (c *Controller) RemoveHandler(w http.ResponseWriter, r *http.Request) { c.handleQueueItem(w, r, false)}
func (c *Controller) handleQueueItem(w http.ResponseWriter, r *http.Request, adding bool) { logger := log.FromContext(r.Context()) if err := r.ParseForm(); err != nil { var maxBytesErr *http.MaxBytesError if errors.As(err, &maxBytesErr) { http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge) return } } id := strings.TrimSpace(r.FormValue("id")) if id == "" { http.Error(w, "Missing id", http.StatusBadRequest) return }
userID := models.GetUserIDFromContext(r.Context()) requestedBy := models.GetUsernameFromContext(r.Context()) isArtist := strings.TrimSpace(r.FormValue("type")) == "artist"
var releases []models.Release if isArtist { releases = c.fetchArtistReleases(r.Context(), id) c.renderDiscogButton(r.Context(), w, id, adding) } else if adding { releases = []models.Release{parseReleaseForm(r)} } else { item := c.Manager.GetItem(userID, id) if item == nil { return } releases = []models.Release{item.Release} }
for _, rel := range releases { if adding { c.Manager.StageRelease(userID, models.QueueItem{Release: rel, RequestedBy: requestedBy}) } else { c.Manager.RemoveRelease(userID, rel.ID) } c.renderButtons(r.Context(), w, rel, adding) }
if adding { logger.Info("item enqueued", "id", id, "is_artist", isArtist, "user", requestedBy, "items", len(releases)) } else { logger.Info("item dequeued", "id", id, "is_artist", isArtist, "user", requestedBy) }
if err := c.Pages.RenderQueue(w, c.Manager.GetViewModel(userID)); err != nil { logger.Error("failed to render queue", "err", err) }}
func (c *Controller) ClearHandler(w http.ResponseWriter, r *http.Request) { logger := log.FromContext(r.Context()) userID := models.GetUserIDFromContext(r.Context()) vm := c.Manager.GetViewModel(userID) c.Manager.Clear(userID)
for _, item := range vm.Items { c.renderButtons(r.Context(), w, item.Release, false) } logger.Info("queue cleared", "user", models.GetUsernameFromContext(r.Context()), "items", len(vm.Items)) if err := c.Pages.RenderQueue(w, c.Manager.GetViewModel(userID)); err != nil { logger.Error("failed to render queue", "err", err) }}
func (c *Controller) StartHandler(w http.ResponseWriter, r *http.Request) { logger := log.FromContext(r.Context()) userID := models.GetUserIDFromContext(r.Context()) logger.Info("queue download started", "user", models.GetUsernameFromContext(r.Context())) c.Manager.StartDownloads(userID) w.WriteHeader(http.StatusNoContent)}
func parseReleaseForm(r *http.Request) models.Release { tracks, _ := strconv.Atoi(r.FormValue("tracks")) duration, _ := strconv.Atoi(r.FormValue("duration")) return models.Release{ ID: strings.TrimSpace(r.FormValue("id")), Title: strings.TrimSpace(r.FormValue("title")), ArtistName: strings.TrimSpace(r.FormValue("artist")), Year: strings.TrimSpace(r.FormValue("year")), TracksCount: tracks, Duration: duration, UPC: strings.TrimSpace(r.FormValue("upc")), CoverURL: strings.TrimSpace(r.FormValue("cover")), }}
func (c *Controller) fetchArtistReleases(ctx context.Context, artistID string) []models.Release { releases, err := c.Catalog.GetArtistReleases(ctx, artistID) if err != nil { return nil } return releases}
func (c *Controller) renderButtons(ctx context.Context, w io.Writer, rel models.Release, inQueue bool) { rel.InQueue = inQueue item := pages.ReleaseCard{Release: rel} if err := c.Pages.RenderCardQueueButton(w, item); err != nil { log.FromContext(ctx).Error("failed to render card queue button", "err", err) } if err := c.Pages.RenderDetailQueueButton(w, item); err != nil { log.FromContext(ctx).Error("failed to render detail queue button", "err", err) }}
func (c *Controller) renderDiscogButton(ctx context.Context, w io.Writer, artistID string, inQueue bool) { item := pages.ReleaseCard{Release: models.Release{ArtistID: artistID, InQueue: inQueue}} if err := c.Pages.RenderDiscogQueueButton(w, item); err != nil { log.FromContext(ctx).Error("failed to render discog queue button", "err", err) }}