101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
dcfg "git.misaka.ren/M1saka/docker_backup/internal/config"
|
|
)
|
|
|
|
// WebDAVBackend uploads to a WebDAV server.
|
|
type WebDAVBackend struct {
|
|
cfg *dcfg.WebDAVConfig
|
|
client *http.Client
|
|
}
|
|
|
|
// NewWebDAVBackend creates a configured WebDAV backend.
|
|
func NewWebDAVBackend(cfg *dcfg.WebDAVConfig) *WebDAVBackend {
|
|
return &WebDAVBackend{
|
|
cfg: cfg,
|
|
client: &http.Client{
|
|
Timeout: 30 * time.Minute,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Upload implements Backend.
|
|
func (w *WebDAVBackend) Upload(ctx context.Context, projectName, localPath string) error {
|
|
baseURL := strings.TrimRight(strings.TrimSpace(w.cfg.URL), "/")
|
|
if baseURL == "" {
|
|
return fmt.Errorf("webdav url is required")
|
|
}
|
|
|
|
file, err := os.Open(localPath)
|
|
if err != nil {
|
|
return fmt.Errorf("open: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("stat: %w", err)
|
|
}
|
|
|
|
parentURL := baseURL + "/" + projectName
|
|
if err := w.mkcol(ctx, parentURL); err != nil {
|
|
return fmt.Errorf("webdav mkcol %s: %w", parentURL, err)
|
|
}
|
|
|
|
remoteURL := parentURL + "/" + filepath.Base(localPath)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, remoteURL, file)
|
|
if err != nil {
|
|
return fmt.Errorf("new request: %w", err)
|
|
}
|
|
req.ContentLength = stat.Size()
|
|
req.Header.Set("Content-Type", "application/gzip")
|
|
if w.cfg.Username != "" || w.cfg.Password != "" {
|
|
req.SetBasicAuth(w.cfg.Username, w.cfg.Password)
|
|
}
|
|
|
|
resp, err := w.client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("webdav put: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("webdav put failed: %s (%d): %s", remoteURL, resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// mkcol creates a WebDAV collection (directory). Existing collections are OK.
|
|
func (w *WebDAVBackend) mkcol(ctx context.Context, url string) error {
|
|
req, err := http.NewRequestWithContext(ctx, "MKCOL", url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if w.cfg.Username != "" || w.cfg.Password != "" {
|
|
req.SetBasicAuth(w.cfg.Username, w.cfg.Password)
|
|
}
|
|
resp, err := w.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// 201 Created is success. Many servers return 405 if collection already exists.
|
|
if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusMethodNotAllowed {
|
|
return nil
|
|
}
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
|
|
} |