package cmd import ( "fmt" "io" "net" "strings" ) // isNetworkAddress checks if the address is in host:port format func isNetworkAddress(addr string) bool { return strings.Contains(addr, ":") } func formatBytes(b int64) string { const unit = 1024 if b < unit { return fmt.Sprintf("%d B", b) } div, exp := int64(unit), 0 for n := b / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) } // ServerTargetInfo holds the parsed information from a server target string. type ServerTargetInfo struct { // Full HTTP URL for the request, e.g., http://127.0.0.1:8080/backup/mybackup.tar.gz URL string // The address of the server, e.g., 127.0.0.1:8080 Address string // The filename for the backup, e.g., mybackup.tar.gz Filename string } // parseServerTarget parses a target string like `127.0.0.1:8080/backup.gz`. // It returns a struct with the full URL and filename, or an error if the format is invalid. func parseServerTarget(target string) (*ServerTargetInfo, bool) { if !strings.Contains(target, "/") || !strings.Contains(target, ":") { return nil, false } parts := strings.SplitN(target, "/", 2) if len(parts) != 2 { return nil, false // Invalid format } address := parts[0] filename := parts[1] if filename == "" { return nil, false // Filename cannot be empty } // Validate that the first part is a host:port host, port, err := net.SplitHostPort(address) if err != nil { return nil, false // Not a valid host:port } if host == "" || port == "" { return nil, false } // It looks like a valid server target. fullURL := fmt.Sprintf("http://%s/backup/%s", address, filename) return &ServerTargetInfo{ URL: fullURL, Address: address, Filename: filename, }, true } // progressTracker реализует io.Reader и io.Writer для отслеживания прогресса type progressTracker struct { Reader io.Reader Writer io.Writer progressCh chan int64 // Для restore, где io.Copy может вызываться много раз (в tar), // нам нужно отслеживать общий прогресс. processed int64 } // Write отслеживает прогресс записи (для create) func (pt *progressTracker) Write(p []byte) (int, error) { n, err := pt.Writer.Write(p) if n > 0 { pt.progressCh <- int64(n) // Отправляем дельту } return n, err } // Read отслеживает прогресс чтения (для restore) func (pt *progressTracker) Read(p []byte) (int, error) { n, err := pt.Reader.Read(p) if n > 0 { pt.processed += int64(n) pt.progressCh <- pt.processed // Отправляем общий обработанный объем } return n, err }