139 lines
2.7 KiB
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
|
|
}
|
|
|