update
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled

This commit is contained in:
2025-12-08 16:16:23 +01:00
commit c251f174ed
1349 changed files with 194301 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
package local
import (
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"sync/atomic"
"time"
"github.com/djherbis/times"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core/storage"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model/metadata"
)
// localStorage implements a Storage that reads the files from the local filesystem and uses registered extractors
// to extract the metadata and tags from the files.
type localStorage struct {
u url.URL
extractor Extractor
resolvedPath string
watching atomic.Bool
}
func newLocalStorage(u url.URL) storage.Storage {
newExtractor, ok := extractors[conf.Server.Scanner.Extractor]
if !ok || newExtractor == nil {
log.Fatal("Extractor not found", "path", conf.Server.Scanner.Extractor)
}
isWindowsPath := filepath.VolumeName(u.Host) != ""
if u.Scheme == storage.LocalSchemaID && isWindowsPath {
u.Path = filepath.Join(u.Host, u.Path)
}
resolvedPath, err := filepath.EvalSymlinks(u.Path)
if err != nil {
log.Warn("Error resolving path", "path", u.Path, "err", err)
resolvedPath = u.Path
}
return &localStorage{u: u, extractor: newExtractor(os.DirFS(u.Path), u.Path), resolvedPath: resolvedPath}
}
func (s *localStorage) FS() (storage.MusicFS, error) {
path := s.u.Path
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("%w: %s", err, path)
}
return &localFS{FS: os.DirFS(path), extractor: s.extractor}, nil
}
type localFS struct {
fs.FS
extractor Extractor
}
func (lfs *localFS) ReadTags(path ...string) (map[string]metadata.Info, error) {
res, err := lfs.extractor.Parse(path...)
if err != nil {
return nil, err
}
for path, v := range res {
if v.FileInfo == nil {
info, err := fs.Stat(lfs, path)
if err != nil {
return nil, err
}
v.FileInfo = localFileInfo{info}
res[path] = v
}
}
return res, nil
}
// localFileInfo is a wrapper around fs.FileInfo that adds a BirthTime method, to make it compatible
// with metadata.FileInfo
type localFileInfo struct {
fs.FileInfo
}
func (lfi localFileInfo) BirthTime() time.Time {
if ts := times.Get(lfi.FileInfo); ts.HasBirthTime() {
return ts.BirthTime()
}
return time.Now()
}
func init() {
storage.Register(storage.LocalSchemaID, newLocalStorage)
}