package syncengine import ( "regexp" "strconv" "strings" ) type RsyncStats struct { SentBytes int64 ReceivedBytes int64 TotalSize int64 Speedup float64 FilesSent int FilesTotal int } type ProgressLine struct { Phase string Percent float64 Files int Total int SentBytes int64 XferedBytes int64 } var ( progressRegex = regexp.MustCompile(`\s*([\d,]+)\s+([\d,]+)\s+([\d%]+)\s*`) sentRegex = regexp.MustCompile(`sent\s+([\d,]+)\s+bytes`) recvRegex = regexp.MustCompile(`received\s+([\d,]+)\s+bytes`) totalRegex = regexp.MustCompile(`total size is\s+([\d,]+)`) filesRegex = regexp.MustCompile(`Number of files: ([\d,]+)`) ) func ParseProgressLine(line string) *ProgressLine { if strings.Contains(line, "files to consider") || strings.Contains(line, "files...") { return &ProgressLine{Phase: "scanning"} } if strings.Contains(line, "building file list") { return &ProgressLine{Phase: "listing"} } if strings.Contains(line, "sent") && strings.Contains(line, "bytes") { return &ProgressLine{Phase: "stats"} } return nil } func ParseStatsLine(line string) (int64, bool) { m := sentRegex.FindStringSubmatch(line) if len(m) >= 2 { n, _ := strconv.ParseInt(strings.ReplaceAll(m[1], ",", ""), 10, 64) return n, true } return 0, false } func ParseFinalStats(output string) *RsyncStats { stats := &RsyncStats{} lines := strings.Split(output, "\n") for _, line := range lines { line = strings.TrimSpace(line) if m := sentRegex.FindStringSubmatch(line); len(m) >= 2 { stats.SentBytes, _ = strconv.ParseInt(strings.ReplaceAll(m[1], ",", ""), 10, 64) } if m := recvRegex.FindStringSubmatch(line); len(m) >= 2 { stats.ReceivedBytes, _ = strconv.ParseInt(strings.ReplaceAll(m[1], ",", ""), 10, 64) } if m := totalRegex.FindStringSubmatch(line); len(m) >= 2 { stats.TotalSize, _ = strconv.ParseInt(strings.ReplaceAll(m[1], ",", ""), 10, 64) } if m := filesRegex.FindStringSubmatch(line); len(m) >= 2 { stats.FilesTotal, _ = strconv.Atoi(strings.ReplaceAll(m[1], ",", "")) } } return stats }