package notify import "sync" const defaultFileTransferCompletedLimit = 128 type fileTransferMonitor struct { mu sync.Mutex active map[string]fileTransferSnapshot completed map[string]fileTransferSnapshot runtimeActive map[string]fileTransferSnapshot runtimeCompleted map[string]fileTransferSnapshot completedLimit int } func newFileTransferMonitor() *fileTransferMonitor { return newFileTransferMonitorWithConfig(defaultFileTransferConfig()) } func newFileTransferMonitorWithConfig(cfg fileTransferConfig) *fileTransferMonitor { cfg = normalizeFileTransferConfig(cfg) return newFileTransferMonitorWithCompletedLimit(cfg.MonitorCompletedLimit) } func newFileTransferMonitorWithCompletedLimit(limit int) *fileTransferMonitor { if limit <= 0 { limit = defaultFileTransferCompletedLimit } return &fileTransferMonitor{ active: make(map[string]fileTransferSnapshot), completed: make(map[string]fileTransferSnapshot), runtimeActive: make(map[string]fileTransferSnapshot), runtimeCompleted: make(map[string]fileTransferSnapshot), completedLimit: limit, } } func (m *fileTransferMonitor) applyConfig(cfg fileTransferConfig) { if m == nil { return } cfg = normalizeFileTransferConfig(cfg) m.mu.Lock() m.completedLimit = cfg.MonitorCompletedLimit m.trimCompletedLocked() m.mu.Unlock() } func (m *fileTransferMonitor) observe(direction fileTransferDirection, event FileEvent) { if m == nil { return } if !isFileTransferObservable(event.Kind) { return } snapshot := fileTransferSnapshotFromEvent(direction, event) key := fileTransferMonitorKey(direction, snapshot.Scope, snapshot.FileID) runtimeKey := fileTransferRuntimeMonitorKey(direction, snapshot.RuntimeScope, snapshot.FileID) if key == "" || runtimeKey == "" { return } m.mu.Lock() defer m.mu.Unlock() if isFileTransferTerminal(snapshot.Kind) { delete(m.active, key) m.completed[key] = snapshot delete(m.runtimeActive, runtimeKey) m.runtimeCompleted[runtimeKey] = snapshot m.trimCompletedLocked() return } delete(m.completed, key) m.active[key] = snapshot delete(m.runtimeCompleted, runtimeKey) m.runtimeActive[runtimeKey] = snapshot } func (m *fileTransferMonitor) activeSnapshots() []fileTransferSnapshot { if m == nil { return nil } m.mu.Lock() defer m.mu.Unlock() return sortedFileTransferSnapshots(m.active) } func (m *fileTransferMonitor) activeSnapshotsByDirection(direction fileTransferDirection) []fileTransferSnapshot { if m == nil { return nil } m.mu.Lock() defer m.mu.Unlock() return filteredFileTransferSnapshots(m.active, direction) } func (m *fileTransferMonitor) completedSnapshots() []fileTransferSnapshot { if m == nil { return nil } m.mu.Lock() defer m.mu.Unlock() return sortedFileTransferSnapshots(m.completed) } func (m *fileTransferMonitor) completedSnapshotsByDirection(direction fileTransferDirection) []fileTransferSnapshot { if m == nil { return nil } m.mu.Lock() defer m.mu.Unlock() return filteredFileTransferSnapshots(m.completed, direction) } func (m *fileTransferMonitor) latestSnapshot(direction fileTransferDirection, scope string, fileID string) (fileTransferSnapshot, bool) { if m == nil { return fileTransferSnapshot{}, false } key := fileTransferMonitorKey(direction, scope, fileID) if key == "" { return fileTransferSnapshot{}, false } m.mu.Lock() defer m.mu.Unlock() if snapshot, ok := m.active[key]; ok { return snapshot, true } snapshot, ok := m.completed[key] return snapshot, ok } func (m *fileTransferMonitor) snapshotsByFileID(fileID string) []fileTransferSnapshot { if m == nil || fileID == "" { return nil } m.mu.Lock() defer m.mu.Unlock() latest := latestFileTransferSnapshotsLocked(m.active, m.completed) return filterFileTransferSnapshotsByFileID(latest, fileID) } func (m *fileTransferMonitor) snapshotsByDirectionAndFileID(direction fileTransferDirection, fileID string) []fileTransferSnapshot { if m == nil || fileID == "" { return nil } m.mu.Lock() defer m.mu.Unlock() latest := latestFileTransferSnapshotsLocked(m.active, m.completed) return filterFileTransferSnapshotsByDirectionAndFileID(latest, direction, fileID) } func (m *fileTransferMonitor) trimCompletedLocked() { trimFileTransferSnapshotsLocked(m.completed, m.completedLimit) trimFileTransferSnapshotsLocked(m.runtimeCompleted, m.completedLimit) } func trimFileTransferSnapshotsLocked(snapshots map[string]fileTransferSnapshot, limit int) { if limit <= 0 || len(snapshots) <= limit { return } for len(snapshots) > limit { oldestKey := "" oldestSnapshot := fileTransferSnapshot{} for key, snapshot := range snapshots { if oldestKey == "" || fileTransferSnapshotOlder(snapshot, oldestSnapshot, key, oldestKey) { oldestKey = key oldestSnapshot = snapshot } } if oldestKey == "" { return } delete(snapshots, oldestKey) } }