local.go

  1package storage
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7	"io/fs"
  8	"os"
  9	"path/filepath"
 10	"strings"
 11)
 12
 13// LocalStorage is a storage implementation that stores objects on the local
 14// filesystem.
 15type LocalStorage struct {
 16	root string
 17}
 18
 19var _ Storage = (*LocalStorage)(nil)
 20
 21// NewLocalStorage creates a new LocalStorage.
 22func NewLocalStorage(root string) *LocalStorage {
 23	return &LocalStorage{root: root}
 24}
 25
 26// Delete implements Storage.
 27func (l *LocalStorage) Delete(name string) error {
 28	name = l.fixPath(name)
 29	if err := os.Remove(name); err != nil {
 30		return fmt.Errorf("failed to remove file %s: %w", name, err)
 31	}
 32	return nil
 33}
 34
 35// Open implements Storage.
 36func (l *LocalStorage) Open(name string) (Object, error) {
 37	name = l.fixPath(name)
 38	f, err := os.Open(name)
 39	if err != nil {
 40		return nil, fmt.Errorf("failed to open file %s: %w", name, err)
 41	}
 42	return f, nil
 43}
 44
 45// Stat implements Storage.
 46func (l *LocalStorage) Stat(name string) (fs.FileInfo, error) {
 47	name = l.fixPath(name)
 48	info, err := os.Stat(name)
 49	if err != nil {
 50		return nil, fmt.Errorf("failed to stat file %s: %w", name, err)
 51	}
 52	return info, nil
 53}
 54
 55// Put implements Storage.
 56func (l *LocalStorage) Put(name string, r io.Reader) (int64, error) {
 57	name = l.fixPath(name)
 58	if err := os.MkdirAll(filepath.Dir(name), os.ModePerm); err != nil {
 59		return 0, fmt.Errorf("failed to create directory for %s: %w", name, err)
 60	}
 61
 62	f, err := os.Create(name)
 63	if err != nil {
 64		return 0, fmt.Errorf("failed to create file %s: %w", name, err)
 65	}
 66	defer f.Close()
 67	n, err := io.Copy(f, r)
 68	if err != nil {
 69		return n, fmt.Errorf("failed to copy data to file %s: %w", name, err)
 70	}
 71	return n, nil
 72}
 73
 74// Exists implements Storage.
 75func (l *LocalStorage) Exists(name string) (bool, error) {
 76	name = l.fixPath(name)
 77	_, err := os.Stat(name)
 78	if err == nil {
 79		return true, nil
 80	}
 81	if errors.Is(err, fs.ErrNotExist) {
 82		return false, nil
 83	}
 84	return false, fmt.Errorf("failed to check existence of file %s: %w", name, err)
 85}
 86
 87// Rename implements Storage.
 88func (l *LocalStorage) Rename(oldName, newName string) error {
 89	oldName = l.fixPath(oldName)
 90	newName = l.fixPath(newName)
 91	if err := os.MkdirAll(filepath.Dir(newName), os.ModePerm); err != nil {
 92		return fmt.Errorf("failed to create directory for %s: %w", newName, err)
 93	}
 94
 95	if err := os.Rename(oldName, newName); err != nil {
 96		return fmt.Errorf("failed to rename %s to %s: %w", oldName, newName, err)
 97	}
 98	return nil
 99}
100
101// Replace all slashes with the OS-specific separator.
102func (l LocalStorage) fixPath(path string) string {
103	path = strings.ReplaceAll(path, "/", string(os.PathSeparator))
104	if !filepath.IsAbs(path) {
105		return filepath.Join(l.root, path)
106	}
107
108	return path
109}