1package services
2
3import (
4 "sync"
5 "time"
6
7 "github.com/bnema/archup/internal/application/dto"
8)
9
10// ProgressTracker tracks and broadcasts installation progress
11type ProgressTracker struct {
12 subscribers []chan *dto.ProgressUpdate
13 mu sync.RWMutex
14}
15
16// NewProgressTracker creates a new progress tracker
17func NewProgressTracker() *ProgressTracker {
18 return &ProgressTracker{
19 subscribers: make([]chan *dto.ProgressUpdate, 0),
20 }
21}
22
23// Subscribe returns a channel for progress updates
24func (pt *ProgressTracker) Subscribe() <-chan *dto.ProgressUpdate {
25 pt.mu.Lock()
26 defer pt.mu.Unlock()
27
28 ch := make(chan *dto.ProgressUpdate, 10)
29 pt.subscribers = append(pt.subscribers, ch)
30 return ch
31}
32
33// Unsubscribe removes a subscriber
34func (pt *ProgressTracker) Unsubscribe(ch chan *dto.ProgressUpdate) {
35 pt.mu.Lock()
36 defer pt.mu.Unlock()
37
38 for i, sub := range pt.subscribers {
39 if sub == ch {
40 pt.subscribers = append(pt.subscribers[:i], pt.subscribers[i+1:]...)
41 close(ch)
42 break
43 }
44 }
45}
46
47// Emit sends a progress update to all subscribers
48func (pt *ProgressTracker) Emit(update *dto.ProgressUpdate) {
49 pt.mu.RLock()
50 defer pt.mu.RUnlock()
51
52 if update.Timestamp.IsZero() {
53 update.Timestamp = time.Now()
54 }
55
56 for _, ch := range pt.subscribers {
57 select {
58 case ch <- update:
59 default:
60 // Channel full, skip to avoid blocking
61 }
62 }
63}
64
65// EmitPhaseStarted sends a phase started event
66func (pt *ProgressTracker) EmitPhaseStarted(phase string, phaseNumber int, totalPhases int) {
67 pt.Emit(&dto.ProgressUpdate{
68 Phase: phase,
69 PhaseNumber: phaseNumber,
70 TotalPhases: totalPhases,
71 ProgressPercent: (phaseNumber - 1) * 100 / totalPhases,
72 Message: "Starting " + phase,
73 IsError: false,
74 })
75}
76
77// EmitPhaseCompleted sends a phase completed event
78func (pt *ProgressTracker) EmitPhaseCompleted(phase string, phaseNumber int, totalPhases int) {
79 pt.Emit(&dto.ProgressUpdate{
80 Phase: phase,
81 PhaseNumber: phaseNumber,
82 TotalPhases: totalPhases,
83 ProgressPercent: phaseNumber * 100 / totalPhases,
84 Message: phase + " completed",
85 IsError: false,
86 })
87}
88
89// EmitPhaseError sends a phase error event
90func (pt *ProgressTracker) EmitPhaseError(phase string, phaseNumber int, totalPhases int, errorMsg string) {
91 pt.Emit(&dto.ProgressUpdate{
92 Phase: phase,
93 PhaseNumber: phaseNumber,
94 TotalPhases: totalPhases,
95 ProgressPercent: (phaseNumber - 1) * 100 / totalPhases,
96 Message: errorMsg,
97 IsError: true,
98 })
99}
100
101// Close closes all subscriber channels
102func (pt *ProgressTracker) Close() {
103 pt.mu.Lock()
104 defer pt.mu.Unlock()
105
106 for _, ch := range pt.subscribers {
107 close(ch)
108 }
109 pt.subscribers = nil
110}