Skip to content
Snippets Groups Projects
files.go 2.49 KiB
package liber

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"git.autistici.org/ale/liber/util"
)

// FileStorage exposes a read-only filesystem hierarchy as a root for
// relative paths (so that you can move archives around while the
// database is still valid). Calls will still accept absolute paths
// for backwards compatibility.
type FileStorage struct {
	Root string
}

func NewFileStorage(root string) *FileStorage {
	return &FileStorage{
		Root: root,
	}
}

// Return the absolute path of a file, given its relative path.
func (s *FileStorage) Abs(path string) string {
	if strings.HasPrefix(path, "/") {
		return path
	}
	return filepath.Join(s.Root, path)
}

// Return the relative path of a file with respect to the storage
// root.
func (s *FileStorage) Rel(abspath string) (string, error) {
	return filepath.Rel(s.Root, abspath)
}

// Create a new file for the given key. Directories containing the
// output file will be automatically created.
func (s *FileStorage) Create(path string) (*os.File, error) {
	path = s.Abs(path)
	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
		return nil, err
	}
	return os.Create(path)
}

// Exists returns true if the specified file exists.
func (s *FileStorage) Exists(path string) bool {
	_, err := os.Stat(s.Abs(path))
	return err == nil
}

// Open a file.
func (s *FileStorage) Open(path string) (*os.File, error) {
	return os.Open(s.Abs(path))
}

// Rename oldpath to newpath.
func (s *FileStorage) Rename(oldpath, newpath string) error {
	return os.Rename(s.Abs(oldpath), s.Abs(newpath))
}

func (s *FileStorage) Walk(w *util.Walker, fn filepath.WalkFunc) {
	w.Walk(s.Root, func(path string, info os.FileInfo, ferr error) error {
		if ferr != nil {
			return nil
		}
		relpath, err := s.Rel(path)
		if err != nil {
			return fmt.Errorf("%s is outside %s (?)", path, s.Root)
		}
		return fn(relpath, info, nil)
	})
}

// RWFileStorage adds a read-write API on top of a FileStorage, based
// on unique keys and directory sharding.
type RWFileStorage struct {
	*FileStorage
	Nesting int
}

func NewRWFileStorage(root string, nesting int) *RWFileStorage {
	return &RWFileStorage{
		FileStorage: NewFileStorage(root),
		Nesting:     nesting,
	}
}

// Path of the file corresponding to the given key, relative to the
// root directory.
func (s *RWFileStorage) Path(key string) string {
	var parts []string
	for i := 0; i < s.Nesting; i++ {
		if i >= len(key) {
			break
		}
		parts = append(parts, key[i:i+1])
	}
	parts = append(parts, key)
	return filepath.Join(parts...)
}