mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-05 13:37:38 +03:00
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package cache
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/djherbis/fscache"
|
|
"github.com/karrick/godirwalk"
|
|
"gopkg.in/djherbis/atime.v1"
|
|
"gopkg.in/djherbis/stream.v1"
|
|
)
|
|
|
|
type spreadFS struct {
|
|
root string
|
|
mode os.FileMode
|
|
init func() error
|
|
}
|
|
|
|
const keyFileExtension = ".key"
|
|
|
|
// NewSpreadFS returns a FileSystem rooted at directory dir. It
|
|
// Dir is created with perms if it doesn't exist.
|
|
func NewSpreadFS(dir string, mode os.FileMode) (fscache.FileSystem, error) {
|
|
fs := &spreadFS{root: dir, mode: mode, init: func() error {
|
|
return os.MkdirAll(dir, mode)
|
|
}}
|
|
return fs, fs.init()
|
|
}
|
|
|
|
func (fs *spreadFS) Reload(f func(key string, name string)) error {
|
|
return godirwalk.Walk(fs.root, &godirwalk.Options{
|
|
Callback: func(absoluteFilePath string, de *godirwalk.Dirent) error {
|
|
path, err := filepath.Rel(fs.root, absoluteFilePath)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Skip if name is not in the format XX/XX/XXXXXXXXXXXX.key
|
|
parts := strings.Split(path, string(os.PathSeparator))
|
|
if len(parts) != 3 || len(parts[0]) != 2 || len(parts[1]) != 2 ||
|
|
filepath.Ext(path) != keyFileExtension {
|
|
return nil
|
|
}
|
|
|
|
keyFileName := absoluteFilePath
|
|
dataFileName := absoluteFilePath[0 : len(absoluteFilePath)-len(keyFileExtension)]
|
|
|
|
// Load the key from the key file. Remove and skip on error
|
|
key, err := ioutil.ReadFile(keyFileName)
|
|
if err != nil {
|
|
_ = fs.Remove(dataFileName)
|
|
return nil
|
|
}
|
|
|
|
// If the data file is not readable, remove and skip
|
|
file, err := os.Open(dataFileName)
|
|
defer func() { _ = file.Close() }()
|
|
if err != nil {
|
|
_ = fs.Remove(dataFileName)
|
|
return nil
|
|
}
|
|
|
|
f(string(key), dataFileName)
|
|
return nil
|
|
},
|
|
Unsorted: true,
|
|
})
|
|
}
|
|
|
|
func (fs *spreadFS) Create(name string) (stream.File, error) {
|
|
key := fmt.Sprintf("%x", sha1.Sum([]byte(name)))
|
|
path := fmt.Sprintf("%s%c%s", key[0:2], os.PathSeparator, key[2:4])
|
|
err := os.MkdirAll(filepath.Join(fs.root, path), fs.mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
absolutePath := filepath.Join(fs.root, path, key)
|
|
err = ioutil.WriteFile(absolutePath+keyFileExtension, []byte(name), 0600)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return os.OpenFile(absolutePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
|
}
|
|
|
|
func (fs *spreadFS) Open(name string) (stream.File, error) {
|
|
return os.Open(name)
|
|
}
|
|
|
|
func (fs *spreadFS) Remove(name string) error {
|
|
_ = os.Remove(name + keyFileExtension)
|
|
return os.Remove(name)
|
|
}
|
|
|
|
func (fs *spreadFS) Stat(name string) (fscache.FileInfo, error) {
|
|
stat, err := os.Stat(name)
|
|
if err != nil {
|
|
return fscache.FileInfo{}, err
|
|
}
|
|
return fscache.FileInfo{FileInfo: stat, Atime: atime.Get(stat)}, nil
|
|
}
|
|
|
|
func (fs *spreadFS) RemoveAll() error {
|
|
if err := os.RemoveAll(fs.root); err != nil {
|
|
return err
|
|
}
|
|
return fs.init()
|
|
}
|