feat: Implement full network functionality with HTTP and P2P modes
Go Build and Test / build-and-test (pull_request) Successful in 37s
Go Build and Test / build-and-test (pull_request) Successful in 37s
This major update introduces a robust, multi-faceted networking capability, allowing backups to be managed through a central server or via direct peer-to-peer connections. - **Add `serve` command**: Implemented a new `serve` command that runs a persistent HTTP server. This allows for a centralized backup management workflow where multiple clients can upload and download archives. - **HTTP Client Mode**: The `create` and `restore` commands can now function as HTTP clients, targeting a `serve` instance using the `ip:port/filename` address format. - **Restore P2P Mode**: The original peer-to-peer streaming functionality (where `restore` acts as a TCP server and `create` as a client) has been fully restored and integrated into the new, more robust command architecture. - **Accurate Network Progress**: Fixed a critical bug where network progress bars would not display correctly. The `serve` command now sends a `Content-Length` header, and `restore` is refactored to handle asynchronous state updates correctly. - **Smart Address Parsing**: The application now automatically distinguishes between HTTP mode (`ip:port/filename`) and P2P mode (`ip:port`), providing a seamless user experience. - **Update `README.md`**: The project's README has been completely rewritten to document all three operating modes (Local, HTTP Server, P2P) with clear usage examples. - **Add Gitea Actions Workflow**: A new CI pipeline (`.gitea/workflows/build-and-test.yml`) has been added to automatically build and test the application on every push and pull request.
This commit is contained in:
1 parent
668721ab58
commit
6b1a302c18
7 files changed
+543
-240
No files matched your search
@@ -2,6 +2,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -22,3 +24,81 @@ func formatBytes(b int64) string {
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user