Use fs.FS in MergeFS implementation

This commit is contained in:
Deluan 2021-07-20 19:50:00 -04:00
parent 7540881695
commit 774ad65155
4 changed files with 68 additions and 81 deletions

View File

@ -4,8 +4,9 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"io/fs"
"io/ioutil" "io/ioutil"
"net/http" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
@ -30,10 +31,10 @@ var (
) )
func newTranslationRepository(context.Context) rest.Repository { func newTranslationRepository(context.Context) rest.Repository {
dir := utils.NewMergeFS( dir := utils.MergeFS{
http.FS(resources.FS), Base: resources.FS,
http.Dir(filepath.Join(conf.Server.DataFolder, "resources")), Overlay: os.DirFS(filepath.Join(conf.Server.DataFolder, "resources")),
) }
if err := loadTranslations(dir); err != nil { if err := loadTranslations(dir); err != nil {
log.Error("Error loading translation files", err) log.Error("Error loading translation files", err)
} }
@ -72,22 +73,22 @@ func (r *translationRepository) NewInstance() interface{} {
return &translation{} return &translation{}
} }
func loadTranslations(fs http.FileSystem) (loadError error) { func loadTranslations(fsys fs.FS) (loadError error) {
once.Do(func() { once.Do(func() {
translations = make(map[string]translation) translations = make(map[string]translation)
dir, err := fs.Open(consts.I18nFolder) dir, err := fsys.Open(consts.I18nFolder)
if err != nil { if err != nil {
loadError = err loadError = err
return return
} }
files, err := dir.Readdir(0) files, err := dir.(fs.ReadDirFile).ReadDir(-1)
if err != nil { if err != nil {
loadError = err loadError = err
return return
} }
var languages []string var languages []string
for _, f := range files { for _, f := range files {
t, err := loadTranslation(fs, f.Name()) t, err := loadTranslation(fsys, f.Name())
if err != nil { if err != nil {
log.Error("Error loading translation file", "file", f.Name(), err) log.Error("Error loading translation file", "file", f.Name(), err)
continue continue
@ -100,14 +101,14 @@ func loadTranslations(fs http.FileSystem) (loadError error) {
return return
} }
func loadTranslation(fs http.FileSystem, fileName string) (translation translation, err error) { func loadTranslation(fsys fs.FS, fileName string) (translation translation, err error) {
// Get id and full path // Get id and full path
name := filepath.Base(fileName) name := filepath.Base(fileName)
id := strings.TrimSuffix(name, filepath.Ext(name)) id := strings.TrimSuffix(name, filepath.Ext(name))
filePath := filepath.Join(consts.I18nFolder, name) filePath := filepath.Join(consts.I18nFolder, name)
// Load translation from json file // Load translation from json file
file, err := fs.Open(filePath) file, err := fsys.Open(filePath)
if err != nil { if err != nil {
return return
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
@ -37,7 +38,7 @@ var _ = Describe("Translations", func() {
Describe("loadTranslation", func() { Describe("loadTranslation", func() {
It("loads a translation file correctly", func() { It("loads a translation file correctly", func() {
fs := http.Dir("ui/src") fs := os.DirFS("ui/src")
tr, err := loadTranslation(fs, "en.json") tr, err := loadTranslation(fs, "en.json")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(tr.ID).To(Equal("en")) Expect(tr.ID).To(Equal("en"))

View File

@ -1,64 +1,58 @@
package utils package utils
import ( import (
"fmt" "errors"
"io" "io"
"net/http" "io/fs"
"os"
"sort" "sort"
) )
// NewMergeFS returns a simple implementation of a merged FS (http.FileSystem), that can combine a base FS with a // MergeFS implements a simple merged fs.FS, that can combine a Base FS with an Overlay FS. The semantics are:
// overlay FS. The semantics are: // - Files from the Overlay FS will override files with the same name in the Base FS
// - Files from the overlay FS will override file s with the same name in the base FS // - Directories are combined, with priority for the Overlay FS over the Base FS for files with matching names
// - Directories are combined, with priority for the overlay FS over the base FS for files with matching names type MergeFS struct {
// TODO Replace http.FileSystem with new fs.FS interface Base fs.FS
func NewMergeFS(base, overlay http.FileSystem) http.FileSystem { Overlay fs.FS
return &mergeFS{
base: base,
overlay: overlay,
}
} }
type mergeFS struct { func (m MergeFS) Open(name string) (fs.File, error) {
base http.FileSystem file, err := m.Overlay.Open(name)
overlay http.FileSystem
}
func (m mergeFS) Open(name string) (http.File, error) {
f, err := m.overlay.Open(name)
if err != nil { if err != nil {
return m.base.Open(name) return m.Base.Open(name)
} }
info, err := f.Stat() info, err := file.Stat()
if err != nil { if err != nil {
_ = f.Close() _ = file.Close()
return m.base.Open(name) return nil, err
}
overlayDirFile, ok := file.(fs.ReadDirFile)
if !info.IsDir() || !ok {
return file, nil
} }
if !info.IsDir() { baseDir, _ := m.Base.Open(name)
return f, nil
}
baseDir, _ := m.base.Open(name)
defer func() { defer func() {
_ = baseDir.Close() _ = baseDir.Close()
_ = f.Close() _ = file.Close()
}() }()
return m.mergeDirs(name, info, baseDir, f) baseDirFile, ok := baseDir.(fs.ReadDirFile)
if !ok {
return nil, fs.ErrInvalid
}
return m.mergeDirs(name, info, baseDirFile, overlayDirFile)
} }
func (m mergeFS) mergeDirs(name string, info os.FileInfo, baseDir http.File, overlayDir http.File) (http.File, error) { func (m MergeFS) mergeDirs(name string, info fs.FileInfo, baseDir fs.ReadDirFile, overlayDir fs.ReadDirFile) (fs.File, error) {
merged := map[string]os.FileInfo{} merged := map[string]fs.DirEntry{}
baseFiles, err := baseDir.Readdir(-1) baseFiles, err := baseDir.ReadDir(-1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sort.Slice(baseFiles, func(i, j int) bool { return baseFiles[i].Name() < baseFiles[j].Name() }) sort.Slice(baseFiles, func(i, j int) bool { return baseFiles[i].Name() < baseFiles[j].Name() })
overlayFiles, err := overlayDir.Readdir(-1) overlayFiles, err := overlayDir.ReadDir(-1)
if err != nil { if err != nil {
overlayFiles = nil overlayFiles = nil
} }
@ -71,7 +65,7 @@ func (m mergeFS) mergeDirs(name string, info os.FileInfo, baseDir http.File, ove
merged[f.Name()] = f merged[f.Name()] = f
} }
var entries []os.FileInfo var entries []fs.DirEntry
for _, i := range merged { for _, i := range merged {
entries = append(entries, i) entries = append(entries, i)
} }
@ -86,32 +80,27 @@ func (m mergeFS) mergeDirs(name string, info os.FileInfo, baseDir http.File, ove
type mergedDir struct { type mergedDir struct {
name string name string
info os.FileInfo info fs.FileInfo
entries []os.FileInfo entries []fs.DirEntry
pos int pos int
} }
func (d *mergedDir) Readdir(count int) ([]os.FileInfo, error) { var _ fs.ReadDirFile = (*mergedDir)(nil)
func (d *mergedDir) ReadDir(count int) ([]fs.DirEntry, error) {
if d.pos >= len(d.entries) && count > 0 { if d.pos >= len(d.entries) && count > 0 {
return nil, io.EOF return nil, io.EOF
} }
if count <= 0 || count > len(d.entries)-d.pos { if count <= 0 || count > len(d.entries)-d.pos {
count = len(d.entries) - d.pos count = len(d.entries) - d.pos
} }
e := d.entries[d.pos : d.pos+count] entries := d.entries[d.pos : d.pos+count]
d.pos += count d.pos += count
return e, nil return entries, nil
} }
func (d *mergedDir) Close() error { return nil } func (d *mergedDir) Close() error { return nil }
func (d *mergedDir) Stat() (os.FileInfo, error) { return d.info, nil } func (d *mergedDir) Stat() (fs.FileInfo, error) { return d.info, nil }
func (d *mergedDir) Read(p []byte) (n int, err error) { func (d *mergedDir) Read([]byte) (n int, err error) {
return 0, fmt.Errorf("cannot Read from directory %s", d.name) return 0, &fs.PathError{Op: "read", Path: d.name, Err: errors.New("is a directory")}
}
func (d *mergedDir) Seek(offset int64, whence int) (int64, error) {
if offset == 0 && whence == io.SeekStart {
d.pos = 0
return 0, nil
}
return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
} }

View File

@ -1,9 +1,8 @@
package utils_test package utils_test
import ( import (
"io" "io/fs"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -12,19 +11,19 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ = Describe("mergeFS", func() { var _ = Describe("MergeFS", func() {
var baseName, overlayName string var baseName, overlayName string
var baseDir, overlayDir, mergedDir http.FileSystem var baseDir, overlayDir, mergedDir fs.FS
BeforeEach(func() { BeforeEach(func() {
baseName, _ = ioutil.TempDir("", "merge_fs_base_test") baseName, _ = ioutil.TempDir("", "merge_fs_base_test")
overlayName, _ = ioutil.TempDir("", "merge_fs_overlay_test") overlayName, _ = ioutil.TempDir("", "merge_fs_overlay_test")
baseDir = http.Dir(baseName) baseDir = os.DirFS(baseName)
overlayDir = http.Dir(overlayName) overlayDir = os.DirFS(overlayName)
mergedDir = utils.NewMergeFS(baseDir, overlayDir) mergedDir = utils.MergeFS{Base: baseDir, Overlay: overlayDir}
}) })
It("reads from base dir if not found in overlay", func() { It("reads from Base dir if not found in Overlay", func() {
_f(baseName, "a.json") _f(baseName, "a.json")
file, err := mergedDir.Open("a.json") file, err := mergedDir.Open("a.json")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
@ -47,13 +46,13 @@ var _ = Describe("mergeFS", func() {
Expect(string(content)).To(Equal("overridden")) Expect(string(content)).To(Equal("overridden"))
}) })
It("reads only files from base if overlay is empty", func() { It("reads only files from Base if Overlay is empty", func() {
_f(baseName, "test.txt") _f(baseName, "test.txt")
dir, err := mergedDir.Open(".") dir, err := mergedDir.Open(".")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
list, err := dir.Readdir(-1) list, err := dir.(fs.ReadDirFile).ReadDir(-1)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(list).To(HaveLen(1)) Expect(list).To(HaveLen(1))
@ -67,7 +66,7 @@ var _ = Describe("mergeFS", func() {
dir, err := mergedDir.Open(".") dir, err := mergedDir.Open(".")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
list, err := dir.Readdir(-1) list, err := dir.(fs.ReadDirFile).ReadDir(-1)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(list).To(HaveLen(2)) Expect(list).To(HaveLen(2))
@ -83,17 +82,14 @@ var _ = Describe("mergeFS", func() {
dir, err := mergedDir.Open(".") dir, err := mergedDir.Open(".")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
list, _ := dir.Readdir(2) list, _ := dir.(fs.ReadDirFile).ReadDir(2)
Expect(list).To(HaveLen(2)) Expect(list).To(HaveLen(2))
Expect(list[0].Name()).To(Equal("1111")) Expect(list[0].Name()).To(Equal("1111"))
Expect(list[1].Name()).To(Equal("2222")) Expect(list[1].Name()).To(Equal("2222"))
Expect(dir.Seek(0, io.SeekStart)).To(Equal(int64(0))) list, _ = dir.(fs.ReadDirFile).ReadDir(2)
Expect(list).To(HaveLen(1))
list, _ = dir.Readdir(2) Expect(list[0].Name()).To(Equal("3333"))
Expect(list).To(HaveLen(2))
Expect(list[0].Name()).To(Equal("1111"))
Expect(list[1].Name()).To(Equal("2222"))
}) })
}) })