package jobs import ( "context" "slices" "sync" "sync/atomic" "time" "ddc/internal/fetch" ) type Status string const ( StatusQueued Status = "queued" StatusDownloading Status = "downloading" StatusPaused Status = "paused" StatusExtracting Status = "extracting" StatusReady Status = "ready" StatusMoving Status = "moving" StatusCompleted Status = "completed" StatusFailed Status = "failed" StatusCancelled Status = "cancelled" ) type Snapshot struct { ID string `json:"id"` SourceURL string `json:"sourceUrl"` FileName string `json:"fileName"` Status Status `json:"status"` Message string `json:"message"` BytesDone int64 `json:"bytesDone"` BytesTotal int64 `json:"bytesTotal"` CreatedAt time.Time `json:"createdAt"` MoveTarget string `json:"moveTarget,omitempty"` QueuePosition int `json:"queuePosition,omitempty"` } type Job struct { id string sourceURL string displayURL string directory string requestedName string autoExtract bool target *moveTarget createdAt time.Time jobContext context.Context cancel context.CancelFunc workers sync.WaitGroup mutex sync.Mutex status Status message string fileName string deleted bool downloaded bool downloadState *fetch.State cancelRun context.CancelCauseFunc recordMutex sync.Mutex bytesDone atomic.Int64 bytesTotal atomic.Int64 } func (job *Job) setStatus(status Status, message string) { job.mutex.Lock() defer job.mutex.Unlock() if job.deleted { return } job.status = status job.message = message } func (job *Job) currentStatus() Status { job.mutex.Lock() defer job.mutex.Unlock() return job.status } func (job *Job) begin(to Status, allowedFrom ...Status) bool { job.mutex.Lock() defer job.mutex.Unlock() if job.deleted || !slices.Contains(allowedFrom, job.status) { return false } job.status = to job.message = "" job.workers.Add(1) return true } func (job *Job) markDeleted() bool { job.mutex.Lock() defer job.mutex.Unlock() if job.status == StatusMoving { return false } job.deleted = true job.status = StatusCancelled return true } func (job *Job) snapshot() Snapshot { job.mutex.Lock() defer job.mutex.Unlock() moveTargetPath := "" if job.target != nil { moveTargetPath = job.target.displayPath() } return Snapshot{ ID: job.id, SourceURL: job.displayURL, FileName: job.fileName, Status: job.status, Message: job.message, BytesDone: job.bytesDone.Load(), BytesTotal: job.bytesTotal.Load(), CreatedAt: job.createdAt, MoveTarget: moveTargetPath, } }