worker を追加(yt-dlp によるダウンロード、段階別進捗、ffprobe 検査、実行中の取り消し)
This commit is contained in:
@@ -32,7 +32,7 @@ type JobStore interface {
|
||||
GetJob(context.Context, int64) (store.Job, error)
|
||||
Retry(context.Context, int64) error
|
||||
RetryEpisode(context.Context, string) error
|
||||
CancelQueued(context.Context, int64) error
|
||||
Cancel(context.Context, int64) error
|
||||
SubscribedSeries(context.Context, []string) (map[string]bool, error)
|
||||
RegisterSubscription(context.Context, string, string, []store.Episode, bool) (bool, error)
|
||||
ListSubscriptions(context.Context) ([]store.Subscription, error)
|
||||
|
||||
+23
-20
@@ -145,34 +145,37 @@ func errorKindText(kind string) string {
|
||||
}
|
||||
|
||||
func (s *server) retryJob(w http.ResponseWriter, r *http.Request) {
|
||||
s.queueMutation(w, r, s.jobs.Retry, false)
|
||||
s.queueMutation(w, r, s.jobs.Retry)
|
||||
}
|
||||
|
||||
func (s *server) cancelJob(w http.ResponseWriter, r *http.Request) {
|
||||
s.queueMutation(w, r, s.jobs.CancelQueued, true)
|
||||
}
|
||||
|
||||
func (s *server) queueMutation(w http.ResponseWriter, r *http.Request, change func(context.Context, int64) error, queuedOnly bool) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "ジョブ ID が不正です", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if queuedOnly {
|
||||
job, err := s.jobs.GetJob(r.Context(), id)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
http.Error(w, "ジョブが見つかりません", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Printf("ジョブ状態の取得に失敗: %v", err)
|
||||
http.Error(w, "操作できませんでした", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if job.Status != "queued" {
|
||||
http.Error(w, "待機中のジョブだけ取り消せます", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
job, err := s.jobs.GetJob(r.Context(), id)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
http.Error(w, "ジョブが見つかりません", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Printf("ジョブ状態の取得に失敗: %v", err)
|
||||
http.Error(w, "操作できませんでした", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if job.Status != "queued" && job.Status != "running" {
|
||||
http.Error(w, "待機中またはダウンロード中のジョブだけ取り消せます", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
s.queueMutation(w, r, s.jobs.Cancel)
|
||||
}
|
||||
|
||||
func (s *server) queueMutation(w http.ResponseWriter, r *http.Request, change func(context.Context, int64) error) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "ジョブ ID が不正です", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := change(r.Context(), id); err != nil {
|
||||
if errors.Is(err, store.ErrInvalidTransition) {
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestQueueGroupsProgressAndPolling(t *testing.T) {
|
||||
t.Errorf("待ち行列に %q がない", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(body, errorMessage) || strings.Contains(body, `action="/jobs/1/cancel"`) || strings.Contains(body, "全体") {
|
||||
if strings.Contains(body, errorMessage) || !strings.Contains(body, `action="/jobs/1/cancel"`) || strings.Contains(body, "全体") {
|
||||
t.Errorf("不正な詳細・操作・全体進捗: %s", body)
|
||||
}
|
||||
fragment := getPage(handler, "/queue/list")
|
||||
@@ -195,7 +195,7 @@ func TestQueueJobsAppearInCorrectSectionsWithLimit(t *testing.T) {
|
||||
cancelAction := `action="/jobs/` + string(rune('1'+i)) + `/cancel"`
|
||||
retryAction := `action="/jobs/` + string(rune('1'+i)) + `/retry"`
|
||||
switch statuses[i] {
|
||||
case "queued":
|
||||
case "running", "queued":
|
||||
if !strings.Contains(section[1], cancelAction) || strings.Contains(section[1], retryAction) {
|
||||
t.Errorf("%s の操作が不正: %s", statuses[i], section[1])
|
||||
}
|
||||
@@ -246,9 +246,13 @@ func TestQueueCancelRetryAndStopPolling(t *testing.T) {
|
||||
if got := postForm(handler, "/jobs/bad/cancel", nil).Code; got != http.StatusBadRequest {
|
||||
t.Fatalf("不正な ID: %d", got)
|
||||
}
|
||||
if got := postForm(handler, "/jobs/99/cancel", nil).Code; got != http.StatusNotFound {
|
||||
t.Fatalf("存在しないジョブ: %d", got)
|
||||
}
|
||||
jobs.jobs["ep"] = store.Job{ID: 1, Status: "running"}
|
||||
if got := postForm(handler, "/jobs/1/cancel", nil).Code; got != http.StatusConflict {
|
||||
t.Fatalf("実行中の取り消し: %d", got)
|
||||
runningCanceled := postForm(handler, "/jobs/1/cancel", nil)
|
||||
if runningCanceled.Code != http.StatusOK || jobs.jobs["ep"].Status != "canceled" || !strings.Contains(runningCanceled.Body.String(), `action="/jobs/1/retry"`) {
|
||||
t.Fatalf("実行中の取り消し: %d %+v", runningCanceled.Code, jobs.jobs["ep"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func (f *fakeJobStore) Cancel(_ context.Context, id int64) error {
|
||||
return f.err
|
||||
}
|
||||
for ep, job := range f.jobs {
|
||||
if job.ID == id && job.Status == "queued" {
|
||||
if job.ID == id && (job.Status == "queued" || job.Status == "running") {
|
||||
job.Status = "canceled"
|
||||
f.jobs[ep] = job
|
||||
return nil
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
{{if .SpeedText}}/{{.SpeedText}}{{end}}
|
||||
{{if .ETAText}}/{{.ETAText}}{{end}}
|
||||
{{if .FragmentText}}/{{.FragmentText}}{{end}}</p>
|
||||
<form action="/jobs/{{.ID}}/cancel" method="post" hx-post="/jobs/{{.ID}}/cancel" hx-target="#queue-list" hx-swap="outerHTML">
|
||||
<button type="submit">取り消し</button>
|
||||
</form>
|
||||
{{end}}
|
||||
{{if eq .Status "failed"}}
|
||||
<p>失敗理由: {{.ErrorKindText}}</p>
|
||||
|
||||
Reference in New Issue
Block a user