package filestore import ( "bytes" "errors" "fmt" "io" "os" "path/filepath" ) // Backend defines the set of methods that a storage backend must implement. // It is quite naïve and regroups what is used by the Filestore in order to // handle its operations. type Backend interface { // Sub should return a new backend rooted in a subdirectory of the // original backend. Sub(path string) Backend // Create should open a file for writing and return a type that // implements WriteCloser . The caller MUST call close when the file is // not needed anymore (typically when all the data has been written). // // It should not return an error if the file already exists, but // instead truncate or replace the existing file. Create(name string) (io.WriteCloser, error) // Open should open a file for reading and return a type that // implements ReadCloser . The caller MUST call close when the file is // not needed anymore (typically when all the data has been read). Open(name string) (io.ReadCloser, error) // Exists should return true if the file exists and false otherwise. Exists(name string) bool // Delete should delete the actual file. It should not return an error // if the file does not exist. Delete(name string) error } type OSBackend struct { path string } func NewOSBackend(path string) *OSBackend { return &OSBackend{ path: path, } } func (ob OSBackend) Sub(path string) Backend { return &OSBackend{filepath.Join(ob.path, path)} } func (ob OSBackend) Create(name string) (io.WriteCloser, error) { if err := os.MkdirAll(ob.path, 0o700); err != nil { return nil, fmt.Errorf("cannot create the directory to write the file: %w", err) } file, err := os.OpenFile(filepath.Join(ob.path, name), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if err != nil { return nil, fmt.Errorf("cannot create file: %w", err) } return file, nil } func (ob OSBackend) Open(name string) (io.ReadCloser, error) { file, err := os.Open(filepath.Join(ob.path, name)) if err != nil { return nil, fmt.Errorf("cannot open file %q: %w", name, err) } return file, nil } func (ob OSBackend) Exists(name string) bool { if _, err := os.Stat(filepath.Join(ob.path, name)); !os.IsNotExist(err) { return true } return false } func (ob OSBackend) Delete(name string) error { if !ob.Exists(name) { return nil } if err := os.Remove(filepath.Join(ob.path, name)); err != nil { return fmt.Errorf("cannot remove file %q: %w", name, err) } return nil } type MemoryFile struct { *bytes.Buffer } func (MemoryFile) Close() error { return nil } type MemoryBackend struct { files map[string][]byte } func (mb *MemoryBackend) Sub(name string) Backend { return new(MemoryBackend) } func (mb *MemoryBackend) Create(name string) (io.WriteCloser, error) { if mb.files == nil { mb.files = map[string][]byte{} } mb.files[name] = []byte{} return MemoryFile{bytes.NewBuffer(mb.files[name])}, nil } func (mb *MemoryBackend) Open(name string) (io.ReadCloser, error) { if b, ok := mb.files[name]; ok { return MemoryFile{bytes.NewBuffer(b)}, nil } return nil, errors.New("file not found") } func (mb *MemoryBackend) Delete(name string) error { delete(mb.files, name) return nil } func (mb *MemoryBackend) Exists(name string) bool { _, ok := mb.files[name] return ok }