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 }