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 err := r.ParseForm(); err != nil {
82 http.Error(w, err.Error(), http.StatusBadRequest)
83 return
84 }
85
86 url := r.FormValue("url")
87 if url == "" {
88 flashError(w, "A URL is required to start a download.")
89 http.Redirect(w, r, "/download", http.StatusSeeOther)
90 return
91 }
92
93 var presetID *int64
94 if pid := r.FormValue("preset_id"); pid != "" {
95 id, err := strconv.ParseInt(pid, 10, 64)
96 if err == nil {
97 presetID = &id
98 }
99 }
100
101 formatOverride := r.FormValue("format_override")
102 customFlags := r.FormValue("custom_flags")
103 outputDir := r.FormValue("output_dir")
104
105 download, err := h.downloadSvc.Create(url, presetID, formatOverride, customFlags, outputDir)
106 if err != nil {
107 redirectWithError(w, r, "/download", "Couldn't queue this download.", err)
108 return
109 }
110
111 h.workerPool.Submit(download)
112
113 redirectWithSuccess(w, r, "/queue", "Download queued.")
114}
115
116func (h *Handler) DeleteDownload(w http.ResponseWriter, r *http.Request) {
117 id, ok := parseID(w, r)
118 if !ok {
119 return
120 }
121
122 if err := h.downloadSvc.Delete(id); err != nil {
123 redirectWithError(w, r, backToQueue(r), "Couldn't remove this download.", err)
124 return
125 }
126
127 redirectWithSuccess(w, r, backToQueue(r), "Download removed.")
128}
129
130func (h *Handler) RetryDownload(w http.ResponseWriter, r *http.Request) {
131 id, ok := parseID(w, r)
132 if !ok {
133 return
134 }
135
136 if err := h.downloadSvc.Retry(id); err != nil {
137 redirectWithError(w, r, backToQueue(r), "Couldn't retry this download.", err)
138 return
139 }
140
141 redirectWithSuccess(w, r, backToQueue(r), "Download queued again.")
142}
143
144func (h *Handler) CancelDownload(w http.ResponseWriter, r *http.Request) {
145 id, ok := parseID(w, r)
146 if !ok {
147 return
148 }
149
150 if err := h.downloadSvc.Cancel(id); err != nil {
151 redirectWithError(w, r, backToQueue(r), "Couldn't cancel this download.", err)
152 return
153 }
154
155 redirectWithSuccess(w, r, backToQueue(r), "Download cancelled.")
156}
157
158// clearable maps the queue page's bulk-clear buttons to the status each one
159// removes. Only these are accepted, so a hand-made form can't delete rows the
160// page never offers to clear (in particular 'downloading').
161var clearable = map[string]string{
162 "completed": "Completed downloads cleared.",
163 "error": "Failed downloads cleared.",
164}
165
166// ClearDownloads removes finished downloads: all of them, or just one status
167// when the form names one.
168func (h *Handler) ClearDownloads(w http.ResponseWriter, r *http.Request) {
169 status := r.FormValue("status")
170 if status == "" {
171 if err := h.downloadSvc.DeleteAll(); err != nil {
172 redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err)
173 return
174 }
175 redirectWithSuccess(w, r, backToQueue(r), "Queue cleared.")
176 return
177 }
178
179 message, ok := clearable[status]
180 if !ok {
181 http.Error(w, "Invalid status", http.StatusBadRequest)
182 return
183 }
184 if err := h.downloadSvc.DeleteByStatus(status); err != nil {
185 redirectWithError(w, r, backToQueue(r), "Couldn't clear the queue.", err)
186 return
187 }
188 redirectWithSuccess(w, r, backToQueue(r), message)
189}
190
191func (h *Handler) DownloadForm(w http.ResponseWriter, r *http.Request) {
192 presets, err := h.presetSvc.GetAll()
193 if err != nil {
194 h.serverError(w, r, "list presets", err)
195 return
196 }
197
198 // A missing default preset is normal (the user may not have set one); the
199 // template handles a nil DefaultPreset, so this isn't surfaced as an error.
200 defaultPreset, _ := h.presetSvc.GetDefault()
201
202 url := r.URL.Query().Get("url")
203 var formats []*models.FormatInfo
204 var playlistEntry string
205 var flash *Flash
206 if r.URL.Query().Get("list_formats") == "1" && url != "" {
207 var err error
208 if formats, playlistEntry, err = h.downloadSvc.ListFormats(r.Context(), url); err != nil {
209 slog.Error("listing formats failed", "url", url, "err", err)
210 flash = &Flash{Kind: "error", Message: "Couldn't list formats: " + err.Error()}
211 }
212 }
213
214 h.renderWithRequest(w, r, "download_form", PageData{
215 Title: "Download",
216 ActiveTab: "download",
217 Flash: flash,
218 Data: struct {
219 Presets []*models.Preset
220 DefaultPreset *models.Preset
221 URL string
222 FormatOverride string
223 CustomFlags string
224 OutputDir string
225 Formats []*models.FormatInfo
226 ShowFormats bool
227 PlaylistEntry string
228 }{
229 Presets: presets,
230 DefaultPreset: defaultPreset,
231 URL: url,
232 FormatOverride: r.URL.Query().Get("format_override"),
233 CustomFlags: r.URL.Query().Get("custom_flags"),
234 OutputDir: r.URL.Query().Get("output_dir"),
235 Formats: formats,
236 ShowFormats: r.URL.Query().Get("list_formats") == "1",
237 PlaylistEntry: playlistEntry,
238 },
239 })
240}
241