package filestore import ( "context" "fmt" "io" "net/url" "os" "path/filepath" ) // 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 string) (*FileSystemStore, error) { url, err := url.Parse(baseURL) if err != nil { return nil, fmt.Errorf("error parsing URL: %v", err) } 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, os.SEEK_SET) 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) { url := url.URL{} 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 }