diff --git a/server/nativeapi/translations.go b/server/nativeapi/translations.go index 176fa6ac5..93f738f42 100644 --- a/server/nativeapi/translations.go +++ b/server/nativeapi/translations.go @@ -4,8 +4,9 @@ import ( "bytes" "context" "encoding/json" + "io/fs" "io/ioutil" - "net/http" + "os" "path/filepath" "strings" "sync" @@ -30,10 +31,10 @@ var ( ) func newTranslationRepository(context.Context) rest.Repository { - dir := utils.NewMergeFS( - http.FS(resources.FS), - http.Dir(filepath.Join(conf.Server.DataFolder, "resources")), - ) + dir := utils.MergeFS{ + Base: resources.FS, + Overlay: os.DirFS(filepath.Join(conf.Server.DataFolder, "resources")), + } if err := loadTranslations(dir); err != nil { log.Error("Error loading translation files", err) } @@ -72,22 +73,22 @@ func (r *translationRepository) NewInstance() interface{} { return &translation{} } -func loadTranslations(fs http.FileSystem) (loadError error) { +func loadTranslations(fsys fs.FS) (loadError error) { once.Do(func() { translations = make(map[string]translation) - dir, err := fs.Open(consts.I18nFolder) + dir, err := fsys.Open(consts.I18nFolder) if err != nil { loadError = err return } - files, err := dir.Readdir(0) + files, err := dir.(fs.ReadDirFile).ReadDir(-1) if err != nil { loadError = err return } var languages []string for _, f := range files { - t, err := loadTranslation(fs, f.Name()) + t, err := loadTranslation(fsys, f.Name()) if err != nil { log.Error("Error loading translation file", "file", f.Name(), err) continue @@ -100,14 +101,14 @@ func loadTranslations(fs http.FileSystem) (loadError error) { 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 name := filepath.Base(fileName) id := strings.TrimSuffix(name, filepath.Ext(name)) filePath := filepath.Join(consts.I18nFolder, name) // Load translation from json file - file, err := fs.Open(filePath) + file, err := fsys.Open(filePath) if err != nil { return } diff --git a/server/nativeapi/translations_test.go b/server/nativeapi/translations_test.go index c10a44999..8e4f9aaf4 100644 --- a/server/nativeapi/translations_test.go +++ b/server/nativeapi/translations_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "net/http" + "os" "path/filepath" "github.com/navidrome/navidrome/consts" @@ -37,7 +38,7 @@ var _ = Describe("Translations", func() { Describe("loadTranslation", func() { It("loads a translation file correctly", func() { - fs := http.Dir("ui/src") + fs := os.DirFS("ui/src") tr, err := loadTranslation(fs, "en.json") Expect(err).To(BeNil()) Expect(tr.ID).To(Equal("en")) diff --git a/utils/merge_fs.go b/utils/merge_fs.go index 03907aacb..5e8890b50 100644 --- a/utils/merge_fs.go +++ b/utils/merge_fs.go @@ -1,64 +1,58 @@ package utils import ( - "fmt" + "errors" "io" - "net/http" - "os" + "io/fs" "sort" ) -// NewMergeFS returns a simple implementation of a merged FS (http.FileSystem), that can combine a base FS with a -// overlay FS. The semantics are: -// - 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 -// TODO Replace http.FileSystem with new fs.FS interface -func NewMergeFS(base, overlay http.FileSystem) http.FileSystem { - return &mergeFS{ - base: base, - overlay: overlay, - } +// MergeFS implements a simple merged fs.FS, that can combine a Base FS with an Overlay FS. The semantics are: +// - Files from the Overlay FS will override files 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 +type MergeFS struct { + Base fs.FS + Overlay fs.FS } -type mergeFS struct { - base http.FileSystem - overlay http.FileSystem -} - -func (m mergeFS) Open(name string) (http.File, error) { - f, err := m.overlay.Open(name) +func (m MergeFS) Open(name string) (fs.File, error) { + file, err := m.Overlay.Open(name) if err != nil { - return m.base.Open(name) + return m.Base.Open(name) } - info, err := f.Stat() + info, err := file.Stat() if err != nil { - _ = f.Close() - return m.base.Open(name) + _ = file.Close() + return nil, err + } + overlayDirFile, ok := file.(fs.ReadDirFile) + if !info.IsDir() || !ok { + return file, nil } - if !info.IsDir() { - return f, nil - } - - baseDir, _ := m.base.Open(name) + baseDir, _ := m.Base.Open(name) defer func() { _ = 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) { - merged := map[string]os.FileInfo{} +func (m MergeFS) mergeDirs(name string, info fs.FileInfo, baseDir fs.ReadDirFile, overlayDir fs.ReadDirFile) (fs.File, error) { + merged := map[string]fs.DirEntry{} - baseFiles, err := baseDir.Readdir(-1) + baseFiles, err := baseDir.ReadDir(-1) if err != nil { return nil, err } 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 { overlayFiles = nil } @@ -71,7 +65,7 @@ func (m mergeFS) mergeDirs(name string, info os.FileInfo, baseDir http.File, ove merged[f.Name()] = f } - var entries []os.FileInfo + var entries []fs.DirEntry for _, i := range merged { entries = append(entries, i) } @@ -86,32 +80,27 @@ func (m mergeFS) mergeDirs(name string, info os.FileInfo, baseDir http.File, ove type mergedDir struct { name string - info os.FileInfo - entries []os.FileInfo + info fs.FileInfo + entries []fs.DirEntry 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 { return nil, io.EOF } if count <= 0 || 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 - return e, nil + return entries, nil } func (d *mergedDir) Close() error { return nil } -func (d *mergedDir) Stat() (os.FileInfo, error) { return d.info, nil } -func (d *mergedDir) Read(p []byte) (n int, err error) { - return 0, fmt.Errorf("cannot Read from directory %s", d.name) -} -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) +func (d *mergedDir) Stat() (fs.FileInfo, error) { return d.info, nil } +func (d *mergedDir) Read([]byte) (n int, err error) { + return 0, &fs.PathError{Op: "read", Path: d.name, Err: errors.New("is a directory")} } diff --git a/utils/merge_fs_test.go b/utils/merge_fs_test.go index 43edfcfe8..cefd9c998 100644 --- a/utils/merge_fs_test.go +++ b/utils/merge_fs_test.go @@ -1,9 +1,8 @@ package utils_test import ( - "io" + "io/fs" "io/ioutil" - "net/http" "os" "path/filepath" @@ -12,19 +11,19 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("mergeFS", func() { +var _ = Describe("MergeFS", func() { var baseName, overlayName string - var baseDir, overlayDir, mergedDir http.FileSystem + var baseDir, overlayDir, mergedDir fs.FS BeforeEach(func() { baseName, _ = ioutil.TempDir("", "merge_fs_base_test") overlayName, _ = ioutil.TempDir("", "merge_fs_overlay_test") - baseDir = http.Dir(baseName) - overlayDir = http.Dir(overlayName) - mergedDir = utils.NewMergeFS(baseDir, overlayDir) + baseDir = os.DirFS(baseName) + overlayDir = os.DirFS(overlayName) + 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") file, err := mergedDir.Open("a.json") Expect(err).To(BeNil()) @@ -47,13 +46,13 @@ var _ = Describe("mergeFS", func() { 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") dir, err := mergedDir.Open(".") Expect(err).To(BeNil()) - list, err := dir.Readdir(-1) + list, err := dir.(fs.ReadDirFile).ReadDir(-1) Expect(err).To(BeNil()) Expect(list).To(HaveLen(1)) @@ -67,7 +66,7 @@ var _ = Describe("mergeFS", func() { dir, err := mergedDir.Open(".") Expect(err).To(BeNil()) - list, err := dir.Readdir(-1) + list, err := dir.(fs.ReadDirFile).ReadDir(-1) Expect(err).To(BeNil()) Expect(list).To(HaveLen(2)) @@ -83,17 +82,14 @@ var _ = Describe("mergeFS", func() { dir, err := mergedDir.Open(".") Expect(err).To(BeNil()) - list, _ := dir.Readdir(2) + list, _ := dir.(fs.ReadDirFile).ReadDir(2) Expect(list).To(HaveLen(2)) Expect(list[0].Name()).To(Equal("1111")) Expect(list[1].Name()).To(Equal("2222")) - Expect(dir.Seek(0, io.SeekStart)).To(Equal(int64(0))) - - list, _ = dir.Readdir(2) - Expect(list).To(HaveLen(2)) - Expect(list[0].Name()).To(Equal("1111")) - Expect(list[1].Name()).To(Equal("2222")) + list, _ = dir.(fs.ReadDirFile).ReadDir(2) + Expect(list).To(HaveLen(1)) + Expect(list[0].Name()).To(Equal("3333")) }) })