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...) }