queue.go
⎇
Raw
1package handler
2
3import (
4 "log/slog"
5 "net/http"
6 "strconv"
7 "strings"
8
9 "vidarchive/internal/models"
10)
11
12func (h *Handler) Downloads(w http.ResponseWriter, r *http.Request) {
13 status := r.URL.Query().Get("status")
14 sortBy := sortFromRequest(w, r, "queue_sort", "date")
15
16 // One row beyond the page answers "is there a next one" without a COUNT query.
17 page := pageFromRequest(r)
18 downloads, err := h.downloadSvc.GetAll(status, sortBy, pageSize+1, (page-1)*pageSize)
19 if err != nil {
20 h.serverError(w, r, "list downloads", err)
21 return
22 }
23 hasNext := len(downloads) > pageSize
24 if hasNext {
25 downloads = downloads[:pageSize]
26 }
27
28 settings := h.settingsOrDefault()
29
30 h.renderWithRequest(w, r, "queue", PageData{
31 Title: "Queue",
32 ActiveTab: "queue",
33 AutoRefresh: settings.AutoRefreshDownloads,
34 RefreshSec: settings.RefreshInterval,
35 Data: struct {
36 Items []*models.Download
37 Status string
38 SortBy string
39 Pagination Pagination
40 }{
41 Items: downloads,
42 Status: status,
43 SortBy: sortBy,
44 Pagination: newPagination(r, page, hasNext),
45 },
46 })
47}
48
49// backToQueue keeps the filter, sort and page the action was triggered from,
50// instead of dropping the user on an unfiltered page 1.
51func backToQueue(r *http.Request) string {
52 ref := localReferer(r)
53 if strings.HasPrefix(ref, "/queue") {
54 return ref
55 }
56 return "/queue"
57}
58
59func (h *Handler) DownloadDetail(w http.ResponseWriter, r *http.Request) {
60 id, ok := parseID(w, r)
61 if !ok {
62 return
63 }
64
65 download, err := h.downloadSvc.GetByID(id)
66 if err != nil {
67 http.Error(w, "Not found", http.StatusNotFound)
68 return
69 }
70
71 h.renderWithRequest(w, r, "queue_detail", PageData{
72 Title: "Queue Details",
73 ActiveTab: "queue",
74 Data: download,
75 })
76}
77
78func (h *Handler) CreateDownload(w http.ResponseWriter, r *http.Request) {
79 if !parseForm(w, r) {
80 return
81 }
82
83 url := r.FormValue("url")
84 if url == "" {
85 flashError(w, "A URL is required to start a download.")
86 http.Redirect(w, r, "/download", http.StatusSeeOther)
87 return
88 }
89
90 var presetID *int64
91 if pid := r.FormValue("preset_id"); pid != "" {
92 id, err := strconv.ParseInt(pid, 10, 64)
93 if err == nil {
94 presetID = &id
95 }
96 }
97
98 formatOverride := r.FormValue("format_override")
99 customFlags := r.FormValue("custom_flags")
100 outputDir := r.FormValue("output_dir")
101
102 download, err := h.downloadSvc.Create(url, presetID, formatOverride, customFlags, outputDir)
103 if err != nil {
104 redirectWithError(w, r, "/download", "Couldn't queue this download.", err)
105 return
106 }
107
108 h.workerPool.Submit(download)
109
110 redirectWithSuccess(w, r, "/queue", "Download queued.")
111}
112
113func (h *Handler) DeleteDownload(w http.ResponseWriter, r *http.Request) {
114 id, ok := parseID(w, r)
115 if !ok {
116 return
117 }
118
119 if err := h.downloadSvc.Delete(id); err != nil {
120 redirectWithError(w, r, backToQueue(r), "Couldn't remove this download.", err)
121 return
122 }
123
124 redirectWithSuccess(w, r, backToQueue(r), "Download removed.")
125}
126
127func (h *Handler) RetryDownload(w http.ResponseWriter, r *http.Request) {
128 id, ok := parseID(w, r)
129 if !ok {
130 return
131 }
132
133 if err := h.downloadSvc.Retry(id); err != nil {
134 redirectWithError(w, r, backToQueue(r), "Couldn't retry this download.", err)
135 return
136 }
137
138 redirectWithSuccess(w, r, backToQueue(r), "Download queued again.")
139}
140
141func (h *Handler) CancelDownload(w http.ResponseWriter, r *http.Request) {
142 id, ok := parseID(w, r)
143 if !ok {
144 return
145 }
146
147 if err := h.downloadSvc.Cancel(id); err != nil {
148 redirectWithError(w, r, backToQueue(r), "Couldn't cancel this download.", err)
149 return
150 }
151
152 redirectWithSuccess(w, r, backToQueue(r), "Download cancelled.")
153}
154
155// clearable is the accepted set, so a hand-made form cannot delete rows the page
156// never offers to clear, 'downloading' in particular.
157var clearable = map[string]string{
158 "completed": "Completed downloads cleared.",
159 "error": "Failed downloads cleared.",
160}
161
162// ClearDownloads removes every finished download, or one status when the form
163// names one.
164func (h *Handler) ClearDownloads(w http.ResponseWriter, r *http.Request) {
165 status := r.FormValue("status")
166 if status == "" {
167 if err := h.downloadSvc.DeleteAll(); err != nil {
168 redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err)
169 return
170 }
171 redirectWithSuccess(w, r, backToQueue(r), "Queue cleared.")
172 return
173 }
174
175 message, ok := clearable[status]
176 if !ok {
177 http.Error(w, "Invalid status", http.StatusBadRequest)
178 return
179 }
180 if err := h.downloadSvc.DeleteByStatus(status); err != nil {
181 redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err)
182 return
183 }
184 redirectWithSuccess(w, r, backToQueue(r), message)
185}
186
187func (h *Handler) DownloadForm(w http.ResponseWriter, r *http.Request) {
188 presets, err := h.presetSvc.GetAll()
189 if err != nil {
190 h.serverError(w, r, "list presets", err)
191 return
192 }
193
194 // A missing default preset is normal, and the template handles a nil one.
195 defaultPreset, _ := h.presetSvc.GetDefault()
196
197 url := r.URL.Query().Get("url")
198 var formats []*models.FormatInfo
199 var playlistEntry string
200 var flash *Flash
201 if r.URL.Query().Get("list_formats") == "1" && url != "" {
202 var err error
203 if formats, playlistEntry, err = h.downloadSvc.ListFormats(r.Context(), url); err != nil {
204 slog.Error("listing formats failed", "url", url, "err", err)
205 flash = &Flash{Kind: "error", Message: "Couldn't list formats: " + err.Error()}
206 }
207 }
208
209 h.renderWithRequest(w, r, "download_form", PageData{
210 Title: "Download",
211 ActiveTab: "download",
212 Flash: flash,
213 Data: struct {
214 Presets []*models.Preset
215 DefaultPreset *models.Preset
216 URL string
217 FormatOverride string
218 CustomFlags string
219 OutputDir string
220 Formats []*models.FormatInfo
221 ShowFormats bool
222 PlaylistEntry string
223 }{
224 Presets: presets,
225 DefaultPreset: defaultPreset,
226 URL: url,
227 FormatOverride: r.URL.Query().Get("format_override"),
228 CustomFlags: r.URL.Query().Get("custom_flags"),
229 OutputDir: r.URL.Query().Get("output_dir"),
230 Formats: formats,
231 ShowFormats: r.URL.Query().Get("list_formats") == "1",
232 PlaylistEntry: playlistEntry,
233 },
234 })
235}
236