package handler import ( "log/slog" "net/http" "strconv" "strings" "vidarchive/internal/models" ) func (h *Handler) Downloads(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") sortBy := sortFromRequest(w, r, "queue_sort", "date") // One row beyond the page tells us whether a next page exists without a // second COUNT query. page := pageFromRequest(r) downloads, err := h.downloadSvc.GetAll(status, sortBy, pageSize+1, (page-1)*pageSize) if err != nil { h.serverError(w, r, "list downloads", err) return } hasNext := len(downloads) > pageSize if hasNext { downloads = downloads[:pageSize] } settings := h.settingsOrDefault() h.renderWithRequest(w, r, "queue", PageData{ Title: "Queue", ActiveTab: "queue", AutoRefresh: settings.AutoRefreshDownloads, RefreshSec: settings.RefreshInterval, Data: struct { Items []*models.Download Status string SortBy string Pagination Pagination }{ Items: downloads, Status: status, SortBy: sortBy, Pagination: newPagination(r, page, hasNext), }, }) } // backToQueue returns the queue page the action was triggered from, so a // redirect keeps the filter, sort and page the user was looking at instead of // dropping them on an unfiltered page 1. func backToQueue(r *http.Request) string { ref := localReferer(r) if strings.HasPrefix(ref, "/queue") { return ref } return "/queue" } func (h *Handler) DownloadDetail(w http.ResponseWriter, r *http.Request) { id, ok := parseID(w, r) if !ok { return } download, err := h.downloadSvc.GetByID(id) if err != nil { http.Error(w, "Not found", http.StatusNotFound) return } h.renderWithRequest(w, r, "queue_detail", PageData{ Title: "Queue Details", ActiveTab: "queue", Data: download, }) } func (h *Handler) CreateDownload(w http.ResponseWriter, r *http.Request) { if !parseForm(w, r) { return } url := r.FormValue("url") if url == "" { flashError(w, "A URL is required to start a download.") http.Redirect(w, r, "/download", http.StatusSeeOther) return } var presetID *int64 if pid := r.FormValue("preset_id"); pid != "" { id, err := strconv.ParseInt(pid, 10, 64) if err == nil { presetID = &id } } formatOverride := r.FormValue("format_override") customFlags := r.FormValue("custom_flags") outputDir := r.FormValue("output_dir") download, err := h.downloadSvc.Create(url, presetID, formatOverride, customFlags, outputDir) if err != nil { redirectWithError(w, r, "/download", "Couldn't queue this download.", err) return } h.workerPool.Submit(download) redirectWithSuccess(w, r, "/queue", "Download queued.") } func (h *Handler) DeleteDownload(w http.ResponseWriter, r *http.Request) { id, ok := parseID(w, r) if !ok { return } if err := h.downloadSvc.Delete(id); err != nil { redirectWithError(w, r, backToQueue(r), "Couldn't remove this download.", err) return } redirectWithSuccess(w, r, backToQueue(r), "Download removed.") } func (h *Handler) RetryDownload(w http.ResponseWriter, r *http.Request) { id, ok := parseID(w, r) if !ok { return } if err := h.downloadSvc.Retry(id); err != nil { redirectWithError(w, r, backToQueue(r), "Couldn't retry this download.", err) return } redirectWithSuccess(w, r, backToQueue(r), "Download queued again.") } func (h *Handler) CancelDownload(w http.ResponseWriter, r *http.Request) { id, ok := parseID(w, r) if !ok { return } if err := h.downloadSvc.Cancel(id); err != nil { redirectWithError(w, r, backToQueue(r), "Couldn't cancel this download.", err) return } redirectWithSuccess(w, r, backToQueue(r), "Download cancelled.") } // clearable maps the queue page's bulk-clear buttons to the status each one // removes. Only these are accepted, so a hand-made form can't delete rows the // page never offers to clear (in particular 'downloading'). var clearable = map[string]string{ "completed": "Completed downloads cleared.", "error": "Failed downloads cleared.", } // ClearDownloads removes finished downloads: all of them, or just one status // when the form names one. func (h *Handler) ClearDownloads(w http.ResponseWriter, r *http.Request) { status := r.FormValue("status") if status == "" { if err := h.downloadSvc.DeleteAll(); err != nil { redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err) return } redirectWithSuccess(w, r, backToQueue(r), "Queue cleared.") return } message, ok := clearable[status] if !ok { http.Error(w, "Invalid status", http.StatusBadRequest) return } if err := h.downloadSvc.DeleteByStatus(status); err != nil { redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err) return } redirectWithSuccess(w, r, backToQueue(r), message) } func (h *Handler) DownloadForm(w http.ResponseWriter, r *http.Request) { presets, err := h.presetSvc.GetAll() if err != nil { h.serverError(w, r, "list presets", err) return } // A missing default preset is normal (the user may not have set one); the // template handles a nil DefaultPreset, so this isn't surfaced as an error. defaultPreset, _ := h.presetSvc.GetDefault() url := r.URL.Query().Get("url") var formats []*models.FormatInfo var playlistEntry string var flash *Flash if r.URL.Query().Get("list_formats") == "1" && url != "" { var err error if formats, playlistEntry, err = h.downloadSvc.ListFormats(r.Context(), url); err != nil { slog.Error("listing formats failed", "url", url, "err", err) flash = &Flash{Kind: "error", Message: "Couldn't list formats: " + err.Error()} } } h.renderWithRequest(w, r, "download_form", PageData{ Title: "Download", ActiveTab: "download", Flash: flash, Data: struct { Presets []*models.Preset DefaultPreset *models.Preset URL string FormatOverride string CustomFlags string OutputDir string Formats []*models.FormatInfo ShowFormats bool PlaylistEntry string }{ Presets: presets, DefaultPreset: defaultPreset, URL: url, FormatOverride: r.URL.Query().Get("format_override"), CustomFlags: r.URL.Query().Get("custom_flags"), OutputDir: r.URL.Query().Get("output_dir"), Formats: formats, ShowFormats: r.URL.Query().Get("list_formats") == "1", PlaylistEntry: playlistEntry, }, }) }