package firmware import ( "sync" "time" ) // Task 代表一個進行中的 firmware 升降版 task(TDD §6 + §8.6.1)。 // service 用 ProgressTracker 維護 deviceID → *Task、用來: // 1. 拒絕同一 device 同時兩個 firmware task(§3.3 FW_DEVICE_BUSY) // 2. 給 graceful shutdown handler 查 HasActiveTask(§8.6.1) // 3. 給 control panel modal 顯示 active task 資訊(§8.6.2) type Task struct { ID string DeviceID string DeviceName string Chip string Direction string StartTs time.Time ProgressCh chan FirmwareProgress mu sync.Mutex stage string // 目前推到的 stage、tracker 內部維護 done bool // cancel 是 task 的 context.CancelFunc、由 service.UpgradeFirmware 寫入。 // // 目前 service 只在 runUpgrade 的 defer 內呼叫一次(避免 ctx leak)、 // 沒有外部 reader 主動呼叫。預留給未來 SIGTERM force-cancel 流程 // (TDD §8.6.3)使用:graceful shutdown 等不到 task 結束時、可逐 // task 呼叫 cancel() 提前釋放 driver 資源。 cancel func() } // Stage 回傳該 task 最後一次廣播的 stage(thread-safe)。 func (t *Task) Stage() string { t.mu.Lock() defer t.mu.Unlock() return t.stage } func (t *Task) setStage(s string) { t.mu.Lock() t.stage = s t.mu.Unlock() } // Done 回傳 task 是否完成(thread-safe)。 func (t *Task) Done() bool { t.mu.Lock() defer t.mu.Unlock() return t.done } func (t *Task) markDone() { t.mu.Lock() t.done = true t.mu.Unlock() } // ProgressTracker 仿 flash.ProgressTracker 的 pattern、map[deviceID]*Task。 // // 注意:key 是 deviceID 而非 taskID(每 device 同時只能有 1 個 firmware // task、不像 flash 可以同 device 不同 model)。 type ProgressTracker struct { tasks map[string]*Task mu sync.Mutex } // NewProgressTracker 建立空 tracker。 func NewProgressTracker() *ProgressTracker { return &ProgressTracker{tasks: make(map[string]*Task)} } // Create 嘗試建立新 task。若同 deviceID 已有未完成的 task、回傳 nil // (caller 應回 FW_DEVICE_BUSY 給上層)。 func (pt *ProgressTracker) Create(deviceID, deviceName, chip, direction string) *Task { pt.mu.Lock() defer pt.mu.Unlock() if existing, ok := pt.tasks[deviceID]; ok && !existing.Done() { return nil } taskID := direction + "-" + deviceID + "-" + time.Now().UTC().Format("20060102T150405.000") t := &Task{ ID: taskID, DeviceID: deviceID, DeviceName: deviceName, Chip: chip, Direction: direction, StartTs: time.Now(), ProgressCh: make(chan FirmwareProgress, 32), stage: StagePreparing, } pt.tasks[deviceID] = t return t } // Get 取得指定 device 的 task(thread-safe)、沒有時回 nil。 func (pt *ProgressTracker) Get(deviceID string) *Task { pt.mu.Lock() defer pt.mu.Unlock() return pt.tasks[deviceID] } // Remove 移除指定 device 的 task entry(caller 應在 progressCh 已 close // 且讀者已完成消費後呼叫、否則前端可能讀不到尾端事件)。 func (pt *ProgressTracker) Remove(deviceID string) { pt.mu.Lock() defer pt.mu.Unlock() delete(pt.tasks, deviceID) } // ActiveTasks 列出所有未完成 task 的快照(thread-safe)、給 graceful // shutdown handler 用(TDD §8.6.1)。 func (pt *ProgressTracker) ActiveTasks() []*ActiveTaskInfo { pt.mu.Lock() defer pt.mu.Unlock() out := make([]*ActiveTaskInfo, 0, len(pt.tasks)) for _, t := range pt.tasks { if t.Done() { continue } elapsed := time.Since(t.StartTs).Milliseconds() info := &ActiveTaskInfo{ TaskID: t.ID, DeviceID: t.DeviceID, DeviceName: t.DeviceName, Chip: t.Chip, Direction: t.Direction, Stage: t.Stage(), StartTs: t.StartTs, ElapsedMs: elapsed, } // ETA:粗估「該 chip 該 stage 還剩多少秒」、用 UpgradeTimeoutFor // 減去 elapsed 作為上界(不精確、Design §9.6 文案已用 "~{n}s")。 timeout := UpgradeTimeoutFor(t.Chip) remaining := int((timeout.Milliseconds() - elapsed) / 1000) if remaining < 0 { remaining = 0 } info.EtaSeconds = remaining out = append(out, info) } return out }