127 lines
2.4 KiB
Go
127 lines
2.4 KiB
Go
|
package local
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"codeberg.org/u1f320/filer/store"
|
||
|
"emperror.dev/errors"
|
||
|
"github.com/google/uuid"
|
||
|
"github.com/rs/zerolog/log"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
StoreKey = "local"
|
||
|
ErrNotDirectory = errors.Sentinel("not a directory")
|
||
|
ErrAlreadyExists = errors.Sentinel("file already exists")
|
||
|
|
||
|
defaultPath = "uploads"
|
||
|
)
|
||
|
|
||
|
type localStore struct {
|
||
|
path string
|
||
|
}
|
||
|
|
||
|
var _ store.Store = (*localStore)(nil)
|
||
|
|
||
|
// New creates a new local store.
|
||
|
// It creates the directory if it doesn't exist,
|
||
|
func New(path string) (store.Store, error) {
|
||
|
if path == "" {
|
||
|
path = defaultPath
|
||
|
}
|
||
|
|
||
|
if !strings.HasPrefix(path, "/") {
|
||
|
wd, err := os.Getwd()
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "getting working directory")
|
||
|
}
|
||
|
|
||
|
path = filepath.Join(wd, path)
|
||
|
}
|
||
|
|
||
|
log.Info().Str("path", path).Msg("Using local storage")
|
||
|
|
||
|
fi, err := os.Stat(path)
|
||
|
if err != nil {
|
||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||
|
return nil, errors.Wrap(err, "calling Stat on path")
|
||
|
}
|
||
|
|
||
|
err = os.MkdirAll(path, 0o744)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "creating uploads directory")
|
||
|
}
|
||
|
} else if !fi.IsDir() {
|
||
|
return nil, ErrNotDirectory
|
||
|
}
|
||
|
|
||
|
return &localStore{
|
||
|
path: path,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (l *localStore) WriteFile(id uuid.UUID, data io.Reader, contentType string) (err error) {
|
||
|
path := filepath.Join(l.path, id.String())
|
||
|
|
||
|
_, err = os.Stat(path)
|
||
|
if err == nil {
|
||
|
return ErrAlreadyExists
|
||
|
}
|
||
|
|
||
|
file, err := os.Create(path)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "creating file")
|
||
|
}
|
||
|
defer func() {
|
||
|
err = errors.Append(err, file.Close())
|
||
|
}()
|
||
|
|
||
|
_, err = io.Copy(file, data)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "writing data to file")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (l *localStore) GetFile(id uuid.UUID) (r io.Reader, err error) {
|
||
|
path := filepath.Join(l.path, id.String())
|
||
|
|
||
|
file, err := os.Open(path)
|
||
|
if err != nil {
|
||
|
if errors.Is(err, fs.ErrNotExist) {
|
||
|
return nil, store.ErrNotExist
|
||
|
}
|
||
|
}
|
||
|
defer func() {
|
||
|
err = errors.Append(err, file.Close())
|
||
|
}()
|
||
|
|
||
|
b := new(bytes.Buffer)
|
||
|
_, err = io.Copy(b, file)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "copying file to buffer")
|
||
|
}
|
||
|
|
||
|
return b, nil
|
||
|
}
|
||
|
|
||
|
func (l *localStore) DeleteFile(id uuid.UUID) error {
|
||
|
path := filepath.Join(l.path, id.String())
|
||
|
|
||
|
err := os.Remove(path)
|
||
|
if err != nil {
|
||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||
|
return nil // a file already not existing is not an error
|
||
|
}
|
||
|
|
||
|
return errors.Wrap(err, "removing file")
|
||
|
}
|
||
|
return nil
|
||
|
}
|