qr-scanner/services/progress.go

139 lines
2.7 KiB
Go

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
}