mirror of
https://github.com/navidrome/navidrome.git
synced 2025-06-04 01:21:19 +03:00
Use fs.FS in MergeFS implementation
This commit is contained in:
parent
7540881695
commit
774ad65155
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"))
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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"))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user