package notify import ( "sort" "strconv" "time" ) type fileTransferDirection uint8 const ( fileTransferDirectionReceive fileTransferDirection = iota fileTransferDirectionSend ) type fileTransferSnapshot struct { Direction fileTransferDirection Scope string RuntimeScope string TransportGeneration uint64 NetType NetType Kind EnvelopeKind FileID string Path string Received int64 Total int64 Percent float64 Done bool Err error StartedAt time.Time UpdatedAt time.Time Duration time.Duration RateBPS float64 StepDuration time.Duration InstantRateBPS float64 Time time.Time Stage string } func fileTransferMonitorScope(event FileEvent) string { if logical := fileEventLogicalConnSnapshot(event); logical != nil { return serverFileScope(logical) } return clientFileScope() } func fileTransferRuntimeScope(event FileEvent) string { if event.TransportConn != nil { return serverTransportScopeForTransport(event.TransportConn) } if logical := fileEventLogicalConnSnapshot(event); logical != nil { return serverTransportScope(logical) } return clientFileScope() } func fileTransferTransportGeneration(event FileEvent) uint64 { if event.TransportConn != nil { return event.TransportConn.TransportGeneration() } logical := fileEventLogicalConnSnapshot(event) if logical == nil { return 0 } return logical.transportGenerationSnapshot() } func fileTransferMonitorKey(direction fileTransferDirection, scope string, fileID string) string { if fileID == "" { return "" } return strconv.Itoa(int(direction)) + "|" + scope + "|" + fileID } func fileTransferRuntimeMonitorKey(direction fileTransferDirection, runtimeScope string, fileID string) string { return fileTransferMonitorKey(direction, normalizeFileScope(runtimeScope), fileID) } func fileTransferSnapshotFromEvent(direction fileTransferDirection, event FileEvent) fileTransferSnapshot { return fileTransferSnapshot{ Direction: direction, Scope: fileTransferMonitorScope(event), RuntimeScope: fileTransferRuntimeScope(event), TransportGeneration: fileTransferTransportGeneration(event), NetType: event.NetType, Kind: event.Kind, FileID: event.Packet.FileID, Path: event.Path, Received: event.Received, Total: event.Total, Percent: event.Percent, Done: event.Done, Err: event.Err, StartedAt: event.StartedAt, UpdatedAt: event.UpdatedAt, Duration: event.Duration, RateBPS: event.RateBPS, StepDuration: event.StepDuration, InstantRateBPS: event.InstantRateBPS, Time: event.Time, Stage: event.Packet.Stage, } } func isFileTransferTerminal(kind EnvelopeKind) bool { return kind == EnvelopeFileEnd || kind == EnvelopeFileAbort } func isFileTransferObservable(kind EnvelopeKind) bool { return kind == EnvelopeFileMeta || kind == EnvelopeFileChunk || kind == EnvelopeFileEnd || kind == EnvelopeFileAbort } func sortedFileTransferSnapshots(src map[string]fileTransferSnapshot) []fileTransferSnapshot { keys := make([]string, 0, len(src)) for key := range src { keys = append(keys, key) } sort.Strings(keys) out := make([]fileTransferSnapshot, 0, len(keys)) for _, key := range keys { out = append(out, src[key]) } return out } func latestFileTransferSnapshotsLocked(active map[string]fileTransferSnapshot, completed map[string]fileTransferSnapshot) []fileTransferSnapshot { merged := make(map[string]fileTransferSnapshot, len(active)+len(completed)) for key, snapshot := range completed { merged[key] = snapshot } for key, snapshot := range active { merged[key] = snapshot } return sortedFileTransferSnapshots(merged) } func filteredFileTransferSnapshots(src map[string]fileTransferSnapshot, direction fileTransferDirection) []fileTransferSnapshot { out := make([]fileTransferSnapshot, 0, len(src)) for _, snapshot := range sortedFileTransferSnapshots(src) { if snapshot.Direction != direction { continue } out = append(out, snapshot) } return out } func filterFileTransferSnapshotsByFileID(src []fileTransferSnapshot, fileID string) []fileTransferSnapshot { out := make([]fileTransferSnapshot, 0, len(src)) for _, snapshot := range src { if snapshot.FileID != fileID { continue } out = append(out, snapshot) } return out } func filterFileTransferSnapshotsByDirectionAndFileID(src []fileTransferSnapshot, direction fileTransferDirection, fileID string) []fileTransferSnapshot { out := make([]fileTransferSnapshot, 0, len(src)) for _, snapshot := range src { if snapshot.Direction != direction || snapshot.FileID != fileID { continue } out = append(out, snapshot) } return out } func fileTransferSnapshotOlder(candidate fileTransferSnapshot, current fileTransferSnapshot, candidateKey string, currentKey string) bool { candidateTime := fileTransferSnapshotCompletedTime(candidate) currentTime := fileTransferSnapshotCompletedTime(current) if candidateTime.Before(currentTime) { return true } if currentTime.Before(candidateTime) { return false } return candidateKey < currentKey } func fileTransferSnapshotCompletedTime(snapshot fileTransferSnapshot) time.Time { if !snapshot.Time.IsZero() { return snapshot.Time } if !snapshot.UpdatedAt.IsZero() { return snapshot.UpdatedAt } return snapshot.StartedAt }