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
@@ -0,0 +1,96 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
serveAddress string
|
||||
serveDirectory string
|
||||
)
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Run a backup server",
|
||||
Long: "Run an HTTP server to upload and download backups.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if serveDirectory == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get user home directory: %v", err)
|
||||
}
|
||||
serveDirectory = filepath.Join(home, "backups")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(serveDirectory, 0755); err != nil {
|
||||
log.Fatalf("Failed to create backup directory: %v", err)
|
||||
}
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/backup/{filename}", uploadHandler).Methods("POST")
|
||||
r.HandleFunc("/backup/{filename}", downloadHandler).Methods("GET")
|
||||
|
||||
log.Printf("Starting server on %s", serveAddress)
|
||||
log.Printf("Using backup directory: %s", serveDirectory)
|
||||
if err := http.ListenAndServe(serveAddress, r); err != nil {
|
||||
log.Fatalf("Server failed: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func uploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
filename := vars["filename"]
|
||||
filePath := filepath.Join(serveDirectory, filename)
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create file", http.StatusInternalServerError)
|
||||
log.Printf("Error creating file %s: %v", filename, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to write to file", http.StatusInternalServerError)
|
||||
log.Printf("Error writing to file %s: %v", filename, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "File %s uploaded successfully.", filename)
|
||||
log.Printf("Uploaded %s", filename)
|
||||
}
|
||||
|
||||
func downloadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
filename := vars["filename"]
|
||||
filePath := filepath.Join(serveDirectory, filename)
|
||||
|
||||
// Проверяем, существует ли файл, перед отправкой
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
http.NotFound(w, r)
|
||||
log.Printf("File not found: %s", filename)
|
||||
return
|
||||
}
|
||||
|
||||
// http.ServeFile - это идиоматический способ отправки файлов в Go.
|
||||
// Он автоматически устанавливает Content-Type, Content-Length и другие заголовки.
|
||||
http.ServeFile(w, r, filePath)
|
||||
log.Printf("Downloaded %s", filename)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
serveCmd.Flags().StringVarP(&serveAddress, "address", "a", "localhost:8080", "Address and port for the server")
|
||||
serveCmd.Flags().StringVarP(&serveDirectory, "directory", "d", "", "Directory to store backups (defaults to ~/backups)")
|
||||
}
|
||||
Reference in new issue
Block a user