mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-06-07 10:49:43 +08:00
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package localdisk
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"backupx/server/internal/storage"
|
|
)
|
|
|
|
type Provider struct {
|
|
basePath string
|
|
}
|
|
|
|
type Factory struct{}
|
|
|
|
func NewFactory() Factory { return Factory{} }
|
|
|
|
func (Factory) Type() storage.ProviderType { return storage.ProviderTypeLocalDisk }
|
|
func (Factory) SensitiveFields() []string { return nil }
|
|
|
|
func (Factory) New(_ context.Context, rawConfig map[string]any) (storage.StorageProvider, error) {
|
|
cfg, err := storage.DecodeConfig[storage.LocalDiskConfig](rawConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(cfg.BasePath) == "" {
|
|
return nil, fmt.Errorf("local disk basePath is required")
|
|
}
|
|
return &Provider{basePath: filepath.Clean(cfg.BasePath)}, nil
|
|
}
|
|
|
|
func (p *Provider) Type() storage.ProviderType { return storage.ProviderTypeLocalDisk }
|
|
|
|
func (p *Provider) TestConnection(_ context.Context) error {
|
|
if err := os.MkdirAll(p.basePath, 0o755); err != nil {
|
|
return fmt.Errorf("ensure local disk base path: %w", err)
|
|
}
|
|
tempFile, err := os.CreateTemp(p.basePath, ".backupx-connection-test-*")
|
|
if err != nil {
|
|
return fmt.Errorf("write access check failed: %w", err)
|
|
}
|
|
name := tempFile.Name()
|
|
_ = tempFile.Close()
|
|
_ = os.Remove(name)
|
|
return nil
|
|
}
|
|
|
|
func (p *Provider) Upload(_ context.Context, objectKey string, reader io.Reader, _ int64, _ map[string]string) error {
|
|
targetPath, err := p.resolvePath(objectKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
|
|
return fmt.Errorf("create local disk directories: %w", err)
|
|
}
|
|
file, err := os.Create(targetPath)
|
|
if err != nil {
|
|
return fmt.Errorf("create local disk object: %w", err)
|
|
}
|
|
defer file.Close()
|
|
if _, err := io.Copy(file, reader); err != nil {
|
|
return fmt.Errorf("write local disk object: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Provider) Download(_ context.Context, objectKey string) (io.ReadCloser, error) {
|
|
targetPath, err := p.resolvePath(objectKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
file, err := os.Open(targetPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open local disk object: %w", err)
|
|
}
|
|
return file, nil
|
|
}
|
|
|
|
func (p *Provider) Delete(_ context.Context, objectKey string) error {
|
|
targetPath, err := p.resolvePath(objectKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("delete local disk object: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Provider) List(_ context.Context, prefix string) ([]storage.ObjectInfo, error) {
|
|
items := make([]storage.ObjectInfo, 0)
|
|
err := filepath.WalkDir(p.basePath, func(path string, entry fs.DirEntry, walkErr error) error {
|
|
if walkErr != nil {
|
|
return walkErr
|
|
}
|
|
if entry.IsDir() {
|
|
return nil
|
|
}
|
|
rel, err := filepath.Rel(p.basePath, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key := filepath.ToSlash(rel)
|
|
if prefix != "" && !strings.HasPrefix(key, prefix) {
|
|
return nil
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
items = append(items, storage.ObjectInfo{Key: key, Size: info.Size(), UpdatedAt: info.ModTime().UTC()})
|
|
return nil
|
|
})
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("list local disk objects: %w", err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (p *Provider) resolvePath(objectKey string) (string, error) {
|
|
cleanBase := filepath.Clean(p.basePath)
|
|
cleanKey := filepath.Clean(filepath.FromSlash(strings.TrimSpace(objectKey)))
|
|
if cleanKey == "." || cleanKey == string(filepath.Separator) || cleanKey == "" {
|
|
return "", fmt.Errorf("object key is required")
|
|
}
|
|
fullPath := filepath.Clean(filepath.Join(cleanBase, cleanKey))
|
|
baseWithSep := cleanBase + string(filepath.Separator)
|
|
if fullPath != cleanBase && !strings.HasPrefix(fullPath, baseWithSep) {
|
|
return "", fmt.Errorf("object key escapes base path")
|
|
}
|
|
return fullPath, nil
|
|
}
|