package worker import ( "context" "fmt" "log" "time" "vidarchive/internal/models" "vidarchive/internal/service" ) type Pool struct { downloadSvc *service.DownloadService workers int queue chan *models.Download ctx context.Context cancel context.CancelFunc } func New(downloadSvc *service.DownloadService, workers int) *Pool { ctx, cancel := context.WithCancel(context.Background()) return &Pool{ downloadSvc: downloadSvc, workers: workers, queue: make(chan *models.Download, 100), ctx: ctx, cancel: cancel, } } func (p *Pool) Start() { // Reset any downloads that were in progress during a previous run if err := p.downloadSvc.ResetStalledDownloads(); err != nil { log.Printf("Warning: failed to reset stalled downloads: %v", err) } for i := 0; i < p.workers; i++ { go p.worker(i) } // Queue checker - polls DB for queued downloads go p.queueChecker() } func (p *Pool) Stop() { p.cancel() } func (p *Pool) Submit(d *models.Download) { select { case p.queue <- d: log.Printf("Download %d queued", d.ID) case <-p.ctx.Done(): } } func (p *Pool) worker(id int) { log.Printf("Worker %d started", id) for { select { case d := <-p.queue: if d == nil { continue } log.Printf("Worker %d processing download %d", id, d.ID) processed, err := p.downloadSvc.ExecuteDownload(d) switch { case err != nil: log.Printf("Worker %d download %d failed: %v", id, d.ID, err) case processed: log.Printf("Worker %d download %d completed", id, d.ID) default: log.Printf("Worker %d download %d already claimed by another worker, skipping", id, d.ID) } case <-p.ctx.Done(): log.Printf("Worker %d stopped", id) return } } } func (p *Pool) queueChecker() { ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: p.checkQueue() case <-p.ctx.Done(): return } } } func (p *Pool) checkQueue() { // Pull at most a channel's worth of the oldest queued downloads rather than // loading every queued row each tick. Anything beyond the buffer is picked up // on a later tick; duplicates are harmless (MarkStarted claims atomically). downloads, err := p.downloadSvc.GetQueued(cap(p.queue)) if err != nil { log.Printf("Queue check error: %v", err) return } for _, d := range downloads { // Try to submit - if queue is full, it'll block briefly select { case p.queue <- d: default: // Queue is full, skip for now return } } } func (p *Pool) GetQueueStatus() (queued, active, completed, failed int, err error) { all, err := p.downloadSvc.GetAll("all", "date") if err != nil { return 0, 0, 0, 0, fmt.Errorf("get all downloads: %w", err) } for _, d := range all { switch d.Status { case "queued": queued++ case "downloading": active++ case "completed": completed++ case "error": failed++ } } return queued, active, completed, failed, nil }