clipper/backend/filestore/fs.go

99 lines
2.9 KiB
Go

package filestore
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
// NewFileSystemStoreHTTPMiddleware returns an HTTP middleware which strips the
// base URL path prefix from incoming paths, suitable for passing to an
// appropriately-configured FileSystemStore.
func NewFileSystemStoreHTTPMiddleware(baseURL *url.URL, next http.Handler) http.Handler {
return http.StripPrefix(baseURL.Path, next)
}
// FileSystemStore is a file store that stores files on the local filesystem.
// It is currently intended for usage in a development environment.
type FileSystemStore struct {
rootPath string
baseURL *url.URL
}
// NewFileSystemStore creates a new FileSystemStore. It accepts a root path,
// which is the storage location on the local file system for stored objects,
// and a baseURL which is a URL which should be configured to serve the stored
// files over HTTP.
func NewFileSystemStore(rootPath string, baseURL *url.URL) (*FileSystemStore, error) {
url := *baseURL
if !strings.HasSuffix(url.Path, "/") {
url.Path += "/"
}
return &FileSystemStore{rootPath: rootPath, baseURL: &url}, nil
}
// GetObject retrieves an object from the local filesystem.
func (s *FileSystemStore) GetObject(ctx context.Context, key string) (io.ReadCloser, error) {
path := filepath.Join(s.rootPath, key)
fptr, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("error opening file: %v", err)
}
return fptr, nil
}
type readCloser struct {
io.Reader
io.Closer
}
// GetObjectWithRange retrieves an object from the local filesystem with the given byte range.
func (s *FileSystemStore) GetObjectWithRange(ctx context.Context, key string, start, end int64) (io.ReadCloser, error) {
path := filepath.Join(s.rootPath, key)
fptr, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("error opening file: %v", err)
}
_, err = fptr.Seek(start, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("error seeking in file: %v", err)
}
return readCloser{
Reader: io.LimitReader(fptr, end-start),
Closer: fptr,
}, nil
}
// GetURL returns an HTTP URL for the provided file path.
func (s *FileSystemStore) GetURL(ctx context.Context, key string) (string, error) {
var url url.URL
url.Scheme = s.baseURL.Scheme
url.Host = s.baseURL.Host
url.Path = fmt.Sprintf("%s%s", s.baseURL.Path, key)
return url.String(), nil
}
// PutObject writes an object to the local filesystem.
func (s *FileSystemStore) PutObject(ctx context.Context, key string, r io.Reader, _ string) (int64, error) {
path := filepath.Join(s.rootPath, key)
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
return 0, fmt.Errorf("error creating directories: %v", err)
}
fptr, err := os.Create(path)
if err != nil {
return 0, fmt.Errorf("error opening file: %v", err)
}
n, err := io.Copy(fptr, r)
if err != nil {
return n, fmt.Errorf("error writing file: %v", err)
}
return n, nil
}