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}