mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
* refactor(build): remove CPP taglib adapter Remove the CGO-based TagLib adapter (adapters/taglib/) and all cross-taglib build infrastructure. The WASM-based go-taglib adapter (adapters/gotaglib/) is now the sole metadata extractor. - Delete adapters/taglib/ (CPP/CGO wrapper) - Delete .github/actions/download-taglib/ - Remove CROSS_TAGLIB_VERSION, CGO_CFLAGS_ALLOW, and all taglib-related references from Dockerfile, Makefile, CI pipeline, and devcontainer * fix(scanner): gracefully fallback to default extractor instead of crashing Replace log.Fatal with a graceful fallback when the configured scanner extractor is not found. Instead of terminating the process, the code now warns and falls back to the default taglib extractor using the existing consts.DefaultScannerExtractor constant. A fatal log is retained only for the case where the default extractor itself is not registered, which indicates a broken build. * test(scanner): cover default extractor fallback and suppress redundant warn Address review feedback on the extractor fallback in newLocalStorage: - Only log the "using default" warning when the configured extractor differs from the default, so a broken build (default extractor itself missing) logs only the fatal — not a misleading "falling back" warn followed immediately by the fatal. - Add a unit test that registers a mock under consts.DefaultScannerExtractor, sets the configured extractor to an unknown name, and asserts the local storage is constructed using the default extractor's constructor.
99 lines
2.6 KiB
Go
99 lines
2.6 KiB
Go
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/consts"
|
|
"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 {
|
|
if conf.Server.Scanner.Extractor != consts.DefaultScannerExtractor {
|
|
log.Warn("Extractor not found, using default", "extractor", conf.Server.Scanner.Extractor, "default", consts.DefaultScannerExtractor)
|
|
}
|
|
newExtractor = extractors[consts.DefaultScannerExtractor]
|
|
if newExtractor == nil {
|
|
log.Fatal("Default extractor not registered", "extractor", consts.DefaultScannerExtractor)
|
|
}
|
|
}
|
|
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 { //nolint:gosec
|
|
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)
|
|
}
|