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