115 lines
3.0 KiB
Go
115 lines
3.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
"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
|
|
}
|
|
|
|
addressport := strings.SplitN(parts[0], ":", 2)
|
|
if len(addressport) != 2 {
|
|
return nil, false // Invalid format
|
|
}
|
|
|
|
if _, err := strconv.Atoi(addressport[1]); err != nil {
|
|
return nil, false // Invalid port
|
|
}
|
|
|
|
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
|
|
}
|