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