mirror of
https://github.com/navidrome/navidrome.git
synced 2026-07-09 17:18:45 +00:00
Some checks are pending
Pipeline: Test, Lint, Build / Get version info (push) Waiting to run
Pipeline: Test, Lint, Build / Lint Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (push) Waiting to run
Pipeline: Test, Lint, Build / Test Go code (Windows) (push) Waiting to run
Pipeline: Test, Lint, Build / Test JS code (push) Waiting to run
Pipeline: Test, Lint, Build / Lint i18n files (push) Waiting to run
Pipeline: Test, Lint, Build / Check Docker configuration (push) Waiting to run
Pipeline: Test, Lint, Build / Build (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-1 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-2 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-3 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-4 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-5 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Package/Release (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-6 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-7 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-8 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-9 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build-10 (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to GHCR (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Blocked by required conditions
Pipeline: Test, Lint, Build / Build Windows installers (push) Blocked by required conditions
* refactor(storage): extract LocalPathToURL from storage.For * test(storage): re-enable local storage tests on Windows (#5381) * test(storage): fix Windows drive-letter path expectation The 'should handle Windows drive letters correctly' test was gated behind the now-removed SkipOnWindows BeforeEach, so its expectation had never run. On Windows, newLocalStorage re-joins u.Host+u.Path via filepath.Join, which yields a backslash path (C:\music), not C:/music. Assert against filepath.Join so the expectation matches the OS-native result. * test(storage): probe Windows drive-letter path in LocalPathToURL Three review bots flagged that LocalPathToURL escapes the drive-letter colon (C: -> C%3A), which url.Parse rejects. There are no Windows bug reports, so add a Windows-gated test that exercises the real conversion on a drive-letter path and let CI decide whether the bug is real before changing production code.
79 lines
1.9 KiB
Go
79 lines
1.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/navidrome/navidrome/utils/slice"
|
|
)
|
|
|
|
const LocalSchemaID = "file"
|
|
|
|
type constructor func(url.URL) Storage
|
|
|
|
var (
|
|
registry = map[string]constructor{}
|
|
lock sync.RWMutex
|
|
)
|
|
|
|
func Register(schema string, c constructor) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
registry[schema] = c
|
|
}
|
|
|
|
// LocalPathToURL converts a bare OS filesystem path into an absolute file:// URL,
|
|
// applying the same slash-normalisation and per-component escaping the scanner
|
|
// relies on. It is the single source of truth for how a local path becomes a
|
|
// storage URL, shared by For and by storage tests.
|
|
func LocalPathToURL(osPath string) (url.URL, error) {
|
|
abs, _ := filepath.Abs(osPath)
|
|
abs = filepath.ToSlash(abs)
|
|
|
|
// Properly escape each path component using URL standards
|
|
pathParts := strings.Split(abs, "/")
|
|
escapedParts := slice.Map(pathParts, func(s string) string {
|
|
return url.PathEscape(s)
|
|
})
|
|
|
|
u, err := url.Parse(LocalSchemaID + "://" + strings.Join(escapedParts, "/"))
|
|
if err != nil {
|
|
return url.URL{}, err
|
|
}
|
|
return *u, nil
|
|
}
|
|
|
|
// For returns a Storage implementation for the given URI.
|
|
// It uses the schema part of the URI to find the correct registered
|
|
// Storage constructor.
|
|
// If the URI does not contain a schema, it is treated as a file:// URI.
|
|
func For(uri string) (Storage, error) {
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
parts := strings.Split(uri, "://")
|
|
|
|
var u *url.URL
|
|
// Paths without schema are treated as file:// and use the default LocalStorage implementation
|
|
if len(parts) < 2 {
|
|
parsed, err := LocalPathToURL(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
u = &parsed
|
|
} else {
|
|
parsed, err := url.Parse(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
u = parsed
|
|
}
|
|
|
|
c, ok := registry[u.Scheme]
|
|
if !ok {
|
|
return nil, errors.New("schema '" + u.Scheme + "' not registered")
|
|
}
|
|
return c(*u), nil
|
|
}
|