156 lines
3.7 KiB
Go
156 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
var (
|
|
errFileNotFound = errors.New("file not found")
|
|
errFilenameGeneration = errors.New("cannot generate filename")
|
|
)
|
|
|
|
// NameGenerator is the type of the functions that generate the filenames
|
|
// under which the files are stored on disk from thz name the user passed
|
|
// as argument.
|
|
type NameGenerator func(string) (string, error)
|
|
|
|
// NameIdentity returns the original filename unchanged.
|
|
func NameIdentity(originalName string) (string, error) {
|
|
return originalName, nil
|
|
}
|
|
|
|
// FilestoreOption are options passed to the Fikeqtore constructors.
|
|
type FilestoreOption func(*Filestore)
|
|
|
|
// WithNameGen allows to set a custom name generator on the filestore.
|
|
func WithNameGen(f NameGenerator) FilestoreOption {
|
|
return func(fs *Filestore) {
|
|
fs.nameGenerator = f
|
|
}
|
|
}
|
|
|
|
// Filestore is an abstraction of the filesystem that allow one to store and
|
|
// get files in a simple way.
|
|
type Filestore struct {
|
|
nameGenerator NameGenerator
|
|
buckets map[string]*Bucket
|
|
dataDir string
|
|
}
|
|
|
|
// NewFilestore creates a new filestore.
|
|
func NewFilestore(path string, opts ...FilestoreOption) (*Filestore, error) {
|
|
f := &Filestore{
|
|
dataDir: path,
|
|
nameGenerator: NameIdentity,
|
|
buckets: map[string]*Bucket{},
|
|
}
|
|
|
|
for i := range opts {
|
|
opts[i](f)
|
|
}
|
|
|
|
if err := os.MkdirAll(f.dataDir, 0o700); err != nil {
|
|
return nil, fmt.Errorf("cannot create the filestore directory: %w", err)
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
// Bucket gets a bucket from the filestore. If the asked bucket does not exist,
|
|
// it is created.
|
|
func (f *Filestore) Bucket(name string) *Bucket {
|
|
if b, ok := f.buckets[name]; ok {
|
|
return b
|
|
}
|
|
|
|
b := &Bucket{
|
|
dataDir: filepath.Join(f.dataDir, name),
|
|
nameGenerator: f.nameGenerator,
|
|
}
|
|
|
|
f.buckets[name] = b
|
|
|
|
return b
|
|
}
|
|
|
|
// Bucket is a storage unit in the filestore.
|
|
type Bucket struct {
|
|
nameGenerator func(string) (string, error)
|
|
dataDir string
|
|
}
|
|
|
|
// Put stores a new file in the bucket.
|
|
func (b Bucket) Put(originalName string, r io.Reader) (string, error) {
|
|
var (
|
|
finalName string
|
|
err error
|
|
)
|
|
|
|
finalName, err = b.nameGenerator(originalName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
fullpath := filepath.Clean(filepath.Join(b.dataDir, finalName))
|
|
|
|
err = os.MkdirAll(filepath.Dir(fullpath), 0o700)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot create filestore subdirectories: %w", err)
|
|
}
|
|
|
|
fd, err := os.Create(fullpath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot create file %s: %w", fullpath, err)
|
|
}
|
|
|
|
defer func() { _ = fd.Close() }() //nolint:errcheck // nothing to handle the error
|
|
|
|
if _, err := io.Copy(fd, r); err != nil {
|
|
return "", fmt.Errorf("cannot write the content of file %s: %w", fullpath, err)
|
|
}
|
|
|
|
return finalName, nil
|
|
}
|
|
|
|
// Get retrieves a file from the bucket.
|
|
func (b Bucket) Get(name string) (io.ReadCloser, error) {
|
|
if !b.Exists(name) {
|
|
return nil, errFileNotFound
|
|
}
|
|
|
|
r, err := os.Open(filepath.Clean(filepath.Join(b.dataDir, name)))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot open file %s: %w", name, err)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Exists checks the existence of a file in the bucket.
|
|
func (b Bucket) Exists(filename string) bool {
|
|
if _, err := os.Stat(filepath.Join(b.dataDir, filename)); !os.IsNotExist(err) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Delete removes a file from the bucket. It is removed from storage and cannot
|
|
// be recovered.
|
|
// An error is returned if the file could not be removed.
|
|
func (b Bucket) Delete(filename string) error {
|
|
if err := os.Remove(filepath.Join(b.dataDir, filename)); err != nil {
|
|
return fmt.Errorf("cannot remove file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetNameGenerator sets the nameGenerator for this Bucket.
|
|
func (b *Bucket) SetNameGenerator(g func(string) (string, error)) {
|
|
b.nameGenerator = g
|
|
}
|