125 lines
2.8 KiB
Go
125 lines
2.8 KiB
Go
|
|
package services
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type localStorageBackend struct {
|
||
|
|
id string
|
||
|
|
root string
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) ID() string { return b.id }
|
||
|
|
func (b localStorageBackend) Type() string { return StorageBackendLocal }
|
||
|
|
|
||
|
|
func (b localStorageBackend) Put(_ context.Context, key string, body io.Reader, _ int64, _ string) error {
|
||
|
|
path, err := b.path(key)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
target, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer target.Close()
|
||
|
|
_, err = io.Copy(target, body)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) Get(_ context.Context, key string) (StorageObject, error) {
|
||
|
|
path, err := b.path(key)
|
||
|
|
if err != nil {
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
source, err := os.Open(path)
|
||
|
|
if err != nil {
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
stat, err := source.Stat()
|
||
|
|
if err != nil {
|
||
|
|
source.Close()
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
return StorageObject{Key: key, Size: stat.Size(), ModTime: stat.ModTime(), Body: source}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) Delete(_ context.Context, key string) error {
|
||
|
|
path, err := b.path(key)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) DeletePrefix(_ context.Context, prefix string) error {
|
||
|
|
path, err := b.path(prefix)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) Usage(_ context.Context) (int64, error) {
|
||
|
|
var total int64
|
||
|
|
err := filepath.WalkDir(b.root, func(path string, entry os.DirEntry, err error) error {
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if entry.IsDir() {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
info, err := entry.Info()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
total += info.Size()
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
if os.IsNotExist(err) {
|
||
|
|
return 0, nil
|
||
|
|
}
|
||
|
|
return total, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) Test(ctx context.Context) error {
|
||
|
|
key := ".warpbox-storage-test-" + randomID(6)
|
||
|
|
if err := b.Put(ctx, key, strings.NewReader("ok"), 2, "text/plain"); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return b.Delete(ctx, key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b localStorageBackend) path(key string) (string, error) {
|
||
|
|
key = filepath.Clean(strings.TrimPrefix(key, "/"))
|
||
|
|
if key == "." || strings.HasPrefix(key, "..") || filepath.IsAbs(key) {
|
||
|
|
return "", fmt.Errorf("invalid storage key")
|
||
|
|
}
|
||
|
|
path := filepath.Join(b.root, key)
|
||
|
|
root, err := filepath.Abs(b.root)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
abs, err := filepath.Abs(path)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
if abs != root && !strings.HasPrefix(abs, root+string(os.PathSeparator)) {
|
||
|
|
return "", fmt.Errorf("invalid storage key")
|
||
|
|
}
|
||
|
|
return abs, nil
|
||
|
|
}
|