mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-11-20 21:43:13 -05:00
140 lines
3.6 KiB
Go
140 lines
3.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/zoriya/kyoo/transcoder/src/utils"
|
|
)
|
|
|
|
type FileStorageBackend struct {
|
|
baseDirectory string
|
|
root *os.Root
|
|
}
|
|
|
|
func NewFileStorageBackend(baseDirectory string) (*FileStorageBackend, error) {
|
|
// Attempt to create the directory if it doesn't exist
|
|
// This should be the only filesystem call in this file that does not use os.Root.
|
|
// This is to prevent directory traversal attacks when the provided input is untrusted.
|
|
if err := os.MkdirAll(baseDirectory, 0770); err != nil {
|
|
return nil, fmt.Errorf("failed to create storage base directory: %w", err)
|
|
}
|
|
|
|
root, err := os.OpenRoot(baseDirectory)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open storage base directory %q: %w", baseDirectory, err)
|
|
}
|
|
|
|
return &FileStorageBackend{
|
|
baseDirectory: baseDirectory,
|
|
root: root,
|
|
}, nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) Close() error {
|
|
if fsb.root != nil {
|
|
return fsb.root.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) DoesItemExist(_ context.Context, path string) (bool, error) {
|
|
_, err := fsb.root.Stat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("failed to check if item %q exists: %w", path, err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) ListItemsWithPrefix(_ context.Context, pathPrefix string) ([]string, error) {
|
|
var items []string
|
|
|
|
err := fs.WalkDir(fsb.root.FS(), pathPrefix, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !d.IsDir() {
|
|
items = append(items, path)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) DeleteItem(_ context.Context, path string) error {
|
|
err := fsb.root.Remove(path)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to delete item %q: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) DeleteItemsWithPrefix(ctx context.Context, pathPrefix string) error {
|
|
return fsb.root.RemoveAll(pathPrefix)
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) SaveItemWithCallback(ctx context.Context, path string, writeContents ContentsWriterCallback) (err error) {
|
|
file, err := fsb.openFileForWriting(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %q for writing: %w", path, err)
|
|
}
|
|
defer utils.CleanupWithErr(&err, file.Close, "failed to close file %q", path)
|
|
|
|
if err := writeContents(ctx, file); err != nil {
|
|
return fmt.Errorf("failed to write contents to file %q: %w", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) SaveItem(ctx context.Context, path string, contents io.Reader) (err error) {
|
|
file, err := fsb.openFileForWriting(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %q for writing: %w", path, err)
|
|
}
|
|
defer utils.CleanupWithErr(&err, file.Close, "failed to close file %q", path)
|
|
|
|
if _, err := io.Copy(file, contents); err != nil {
|
|
return fmt.Errorf("failed to copy contents to file %q: %w", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) openFileForWriting(path string) (*os.File, error) {
|
|
dir := filepath.Dir(path)
|
|
if err := fsb.root.MkdirAll(dir, 0770); err != nil {
|
|
return nil, fmt.Errorf("failed to create directory %q: %w", dir, err)
|
|
}
|
|
|
|
file, err := fsb.root.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0660)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create file %q: %w", path, err)
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func (fsb *FileStorageBackend) GetItem(_ context.Context, path string) (io.ReadCloser, error) {
|
|
file, err := fsb.root.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file %q: %w", path, err)
|
|
}
|
|
|
|
return file, nil
|
|
}
|