package notify import ( "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "strings" "time" ) func computeFileChecksum(path string) (string, error) { fd, err := os.Open(path) if err != nil { return "", err } defer fd.Close() h := sha256.New() if _, err := io.Copy(h, fd); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } func filePacketModTime(packet FilePacket) time.Time { if packet.ModTime <= 0 { return time.Time{} } return time.Unix(0, packet.ModTime) } func applyReceivedFileMeta(path string, mode os.FileMode, modTime time.Time) { if mode != 0 { _ = os.Chmod(path, mode.Perm()) } if !modTime.IsZero() { _ = os.Chtimes(path, modTime, modTime) } } func sanitizeFileName(name string) string { trimmed := strings.TrimSpace(name) if trimmed == "" { return "unnamed" } trimmed = strings.ReplaceAll(trimmed, "/", "_") trimmed = strings.ReplaceAll(trimmed, "\\", "_") trimmed = strings.ReplaceAll(trimmed, ":", "_") return trimmed } func shortFileIDSuffix(fileID string) string { cleaned := sanitizeFileName(fileID) if len(cleaned) > 12 { return cleaned[:12] } if cleaned == "" { return "copy" } return cleaned } func pathExists(path string) bool { _, err := os.Stat(path) return err == nil } func (p *fileReceivePool) receiveDirLocked() string { if p.dir != "" { return p.dir } return os.TempDir() } func (p *fileReceivePool) uniqueFinalPathLocked(baseDir string, name string, fileID string) string { cleanName := sanitizeFileName(filepath.Base(name)) if cleanName == "" { cleanName = "unnamed.bin" } ext := filepath.Ext(cleanName) base := strings.TrimSuffix(cleanName, ext) candidate := filepath.Join(baseDir, cleanName) if !p.pathReservedLocked(candidate) && !pathExists(candidate) { return candidate } suffix := shortFileIDSuffix(fileID) candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s%s", base, suffix, ext)) if !p.pathReservedLocked(candidate) && !pathExists(candidate) { return candidate } for i := 1; ; i++ { candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s.%d%s", base, suffix, i, ext)) if !p.pathReservedLocked(candidate) && !pathExists(candidate) { return candidate } } } func (p *fileReceivePool) pathReservedLocked(path string) bool { for _, session := range p.sessions { if session.finalPath == path || session.tmpPath == path { return true } } return false } func (p *fileReceivePool) trimCompletedLocked() { if p.completedLimit <= 0 || len(p.completed) <= p.completedLimit { return } for len(p.completed) > p.completedLimit { oldestKey := "" oldestTime := time.Time{} for key, session := range p.completed { candidateTime := completedFileReceiveTime(session) if oldestKey == "" || candidateTime.Before(oldestTime) || (candidateTime.Equal(oldestTime) && key < oldestKey) { oldestKey = key oldestTime = candidateTime } } if oldestKey == "" { return } delete(p.completed, oldestKey) } } func completedFileReceiveTime(session *fileReceiveSession) time.Time { if session == nil { return time.Time{} } if !session.updatedAt.IsZero() { return session.updatedAt } return session.startedAt } func (s *fileReceiveSession) copy() *fileReceiveSession { if s == nil { return nil } dup := *s return &dup }