Files
djk/internal/app/ui_merge_jobs.go
T

142 lines
3.6 KiB
Go

package app
import (
"context"
"fmt"
"net/http"
"time"
)
type uiMergeJob struct {
dramaID string
tasks []*UITask
remove bool
ctx context.Context
cancel context.CancelFunc
done chan struct{}
result uiMergeResult
running bool
}
func (a *UIApp) enqueueMergesLocked(ids []string, deleteEpisodes bool) ([]*uiMergeJob, error) {
if a.mergeStopped {
return nil, fmt.Errorf("服务正在停止,无法提交合并")
}
if len(a.mergeJobs)+len(ids) > 256 {
return nil, fmt.Errorf("合并队列已满,请等待当前任务完成后再提交")
}
if a.mergeJobs == nil {
a.mergeJobs = make(map[string]*uiMergeJob)
}
if a.merges == nil {
a.merges = make(map[string]*UIMergeState)
}
byDrama := make(map[string][]*UITask)
for _, taskID := range a.taskOrder {
if task := a.tasks[taskID]; task != nil && task.Status == uiStatusSuccess && !task.RemoveRequested {
byDrama[task.DramaID] = append(byDrama[task.DramaID], cloneUITask(task))
}
}
previous := cloneMergeStates(a.merges)
var jobs, added []*uiMergeJob
for _, id := range ids {
job := a.mergeJobs[id]
if job == nil {
ctx, cancel := context.WithCancel(context.Background())
job = &uiMergeJob{dramaID: id, tasks: byDrama[id], remove: deleteEpisodes, ctx: ctx, cancel: cancel, done: make(chan struct{})}
a.mergeJobs[id] = job
added = append(added, job)
title := ""
if len(job.tasks) > 0 {
title = job.tasks[0].DramaTitle
}
a.merges[id] = &UIMergeState{DramaID: id, DramaTitle: title, Status: "queued", Detail: "已提交后台合并,可关闭或刷新页面", DeleteEpisodes: deleteEpisodes, UpdatedAt: time.Now()}
}
jobs = append(jobs, job)
}
if err := a.saveStateLocked(); err != nil {
for _, job := range added {
delete(a.mergeJobs, job.dramaID)
job.cancel()
}
a.merges = previous
return nil, fmt.Errorf("保存合并任务失败:%w", err)
}
if len(added) > 0 {
go a.runMergeJobs(added)
}
return jobs, nil
}
func (a *UIApp) runMergeJobs(jobs []*uiMergeJob) {
a.mergeMu.Lock()
defer a.mergeMu.Unlock()
for _, job := range jobs {
a.mu.Lock()
if a.mergeJobs[job.dramaID] != job {
a.mu.Unlock()
continue
}
job.running = true
a.mu.Unlock()
if job.ctx.Err() == nil {
job.result = a.mergeDrama(job.ctx, job.dramaID, job.tasks, job.remove)
} else {
job.result = uiMergeResult{DramaID: job.dramaID, Error: "合并已取消,原分集已保留"}
a.setMergeState(job.result, "failed", 0, false, job.remove)
}
job.cancel()
a.mu.Lock()
delete(a.mergeJobs, job.dramaID)
close(job.done)
a.mu.Unlock()
}
}
func (a *UIApp) stopMergeJobs() {
a.mu.Lock()
a.mergeStopped = true
var jobs []*uiMergeJob
for _, job := range a.mergeJobs {
job.cancel()
jobs = append(jobs, job)
}
a.mu.Unlock()
for _, job := range jobs {
<-job.done
}
}
func (a *UIApp) handleMergeCancel(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
if !requireDownload(w, r) {
return
}
ids, ok := readDramaIDsRequest(w, r)
if !ok || !a.requireDramaSources(w, r, ids) {
return
}
a.mu.Lock()
defer a.mu.Unlock()
canceled := 0
for _, id := range ids {
if job := a.mergeJobs[id]; job != nil {
job.cancel()
if !job.running {
job.result = uiMergeResult{DramaID: id, Error: "合并已取消,原分集已保留"}
if state := a.merges[id]; state != nil {
state.Status, state.Detail, state.Error = "failed", "", job.result.Error
}
delete(a.mergeJobs, id)
close(job.done)
}
canceled++
}
}
_ = a.saveStateLocked()
writeJSON(w, http.StatusOK, map[string]int{"canceled": canceled})
}