package services import ( "encoding/json" "fmt" "sync" "time" "qr-scanner/models" ) type Progress struct { mu sync.RWMutex Total int StartTime time.Time EndTime time.Time Current string Processed int Success int Failed int Results map[int]models.ScanResult listeners map[chan models.ProgressUpdate]struct{} } func NewProgress(total int) *Progress { return &Progress{ Total: total, StartTime: time.Now(), Results: make(map[int]models.ScanResult, total), listeners: make(map[chan models.ProgressUpdate]struct{}), } } func (p *Progress) AddListener(ch chan models.ProgressUpdate) { p.mu.Lock() defer p.mu.Unlock() p.listeners[ch] = struct{}{} } func (p *Progress) RemoveListener(ch chan models.ProgressUpdate) { p.mu.Lock() defer p.mu.Unlock() delete(p.listeners, ch) close(ch) } func (p *Progress) SetCurrent(current string) { p.mu.Lock() p.Current = current p.mu.Unlock() } func (p *Progress) Update(result models.ScanResult, status string) { p.mu.Lock() p.Processed++ if result.Success { p.Success++ } else { p.Failed++ } p.Results[result.Index] = result update := p.buildUpdateLocked(status, &result) for ch := range p.listeners { select { case ch <- update: default: } } p.mu.Unlock() } func (p *Progress) Complete(status string) { p.mu.Lock() p.EndTime = time.Now() update := p.buildUpdateLocked(status, nil) for ch := range p.listeners { select { case ch <- update: default: } } p.mu.Unlock() } func (p *Progress) Snapshot(status string) models.ProgressUpdate { p.mu.RLock() defer p.mu.RUnlock() return p.buildUpdateLocked(status, nil) } func (p *Progress) ResultsSnapshot() map[int]models.ScanResult { p.mu.RLock() defer p.mu.RUnlock() out := make(map[int]models.ScanResult, len(p.Results)) for k, v := range p.Results { out[k] = v } return out } func (p *Progress) buildUpdateLocked(status string, result *models.ScanResult) models.ProgressUpdate { elapsed := time.Since(p.StartTime).Seconds() speed := 0.0 if elapsed > 0 { speed = float64(p.Processed) / elapsed } remaining := "" if speed > 0 && p.Total > p.Processed { sec := float64(p.Total-p.Processed) / speed remaining = (time.Duration(sec * float64(time.Second))).Truncate(time.Second).String() } return models.ProgressUpdate{ Total: p.Total, Processed: p.Processed, Success: p.Success, Failed: p.Failed, Speed: speed, Remaining: remaining, Current: p.Current, Status: status, Result: result, UpdatedAt: time.Now(), } } func SSEWrite(w interface { Write([]byte) (int, error) }, update models.ProgressUpdate) error { b, err := json.Marshal(update) if err != nil { return err } _, err = w.Write([]byte(fmt.Sprintf("data: %s\n\n", b))) return err }