Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
deluan committed May 18, 2024
1 parent 5cf59fd commit d8ec5a4
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 35 deletions.
18 changes: 13 additions & 5 deletions adapters/taglib/taglib.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package taglib

import (
"errors"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand All @@ -12,7 +14,9 @@ import (
"github.com/navidrome/navidrome/scanner/metadata"
)

type extractor struct{}
type extractor struct {
baseDir string
}

func (e extractor) Parse(files ...string) (map[string]tag.Properties, error) {
results := make(map[string]tag.Properties)
Expand All @@ -30,10 +34,11 @@ func (e extractor) Version() string {
return Version()
}

func (e *extractor) extractMetadata(filePath string) (*tag.Properties, error) {
tags, err := Read(filePath)
func (e extractor) extractMetadata(filePath string) (*tag.Properties, error) {
fullPath := filepath.Join(e.baseDir, filePath)
tags, err := Read(fullPath)
if err != nil {
log.Warn("extractor: Error reading metadata from file. Skipping", "filePath", filePath, err)
log.Warn("extractor: Error reading metadata from file. Skipping", "filePath", fullPath, err)
return nil, err
}

Expand Down Expand Up @@ -115,5 +120,8 @@ func parseTIPL(tags metadata.ParsedTags) {
var _ tag.Extractor = (*extractor)(nil)

func init() {
tag.RegisterExtractor("taglib", &extractor{})
tag.RegisterExtractor("taglib", func(_ fs.FS, baseDir string) tag.Extractor {
// ignores fs, as taglib extractor only works with local files
return &extractor{baseDir}
})
}
23 changes: 9 additions & 14 deletions model/tag/extractors.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package tag

import (
"fmt"
"io/fs"
"sync"

"github.com/navidrome/navidrome/model"
)

type Extractor interface {
Expand All @@ -16,26 +18,19 @@ type Properties struct {
HasPicture bool
}

type extractorConstructor func(fs.FS, string) Extractor

var (
extractors = map[string]Extractor{}
extractors = map[string]extractorConstructor{}
lock sync.RWMutex
)

func RegisterExtractor(id string, parser Extractor) {
func RegisterExtractor(id string, f extractorConstructor) {
lock.Lock()
defer lock.Unlock()
fmt.Println("!!!! Registering extractor", id, "version", parser.Version())
extractors[id] = parser
}

// TODO Remove unused annotation
// nolint: unused
func getExtractor(id string) Extractor {
lock.RLock()
defer lock.RUnlock()
return extractors[id]
extractors[id] = f
}

func Extract(files ...string) (map[string]Tags, error) {
func Extract(lib model.Library, files ...string) (map[string]Tags, error) {
panic("not implemented")
}
76 changes: 66 additions & 10 deletions scanner2/fakefs_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package scanner2_test
package scanner2

import (
"encoding/json"
"fmt"
"io/fs"
"testing"
"testing/fstest"
"time"

"github.com/navidrome/navidrome/model/tag"
"github.com/navidrome/navidrome/utils/random"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -39,10 +41,10 @@ func (ffs *FakeFS) Touch(path string, t ...time.Time) {
f.ModTime = t[0]
}

type tag map[string]any
type _tag map[string]any

func TestFakeFS(t *testing.T) {
sgtPeppers := template(tag{"albumartist": "The Beatles", "album": "Sgt. Pepper's Lonely Hearts Club Band", "year": 1967})
sgtPeppers := template(_tag{"albumartist": "The Beatles", "album": "Sgt. Pepper's Lonely Hearts Club Band", "year": 1967})
files := fstest.MapFS{
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/01 - Sgt. Pepper's Lonely Hearts Club Band.mp3": sgtPeppers(track(1, "Sgt. Pepper's Lonely Hearts Club Band")),
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/02 - With a Little Help from My Friends.mp3": sgtPeppers(track(2, "With a Little Help from My Friends")),
Expand All @@ -54,20 +56,20 @@ func TestFakeFS(t *testing.T) {
assert.NoError(t, fstest.TestFS(ffs, "The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/04 - Getting Better.mp3"))
}

func template(t tag) func(...tag) *fstest.MapFile {
return func(tags ...tag) *fstest.MapFile {
return file(append([]tag{t}, tags...)...)
func template(t _tag) func(..._tag) *fstest.MapFile {
return func(tags ..._tag) *fstest.MapFile {
return file(append([]_tag{t}, tags...)...)
}
}

func track(num int, title string) tag {
func track(num int, title string) _tag {
t := audioProperties("mp3", 320)
t["title"] = title
t["track"] = num
return t
}

func file(tags ...tag) *fstest.MapFile {
func file(tags ..._tag) *fstest.MapFile {
ts := audioProperties("mp3", 320)
for _, t := range tags {
for k, v := range t {
Expand All @@ -78,9 +80,9 @@ func file(tags ...tag) *fstest.MapFile {
return &fstest.MapFile{Data: data, ModTime: time.Now()}
}

func audioProperties(suffix string, bitrate int64) tag {
func audioProperties(suffix string, bitrate int64) _tag {
duration := random.Int64(300) + 120
return tag{
return _tag{
"suffix": suffix,
"bitrate": bitrate,
"duration": duration,
Expand All @@ -90,3 +92,57 @@ func audioProperties(suffix string, bitrate int64) tag {
"channels": 2,
}
}

func RegisterFakeExtractor(fakeFS fs.FS) {
tag.RegisterExtractor("fake", func(fs.FS, string) tag.Extractor {
return fakeExtractor{fs: fakeFS}
})
}

type fakeExtractor struct{ fs fs.FS }

func (e fakeExtractor) Parse(files ...string) (map[string]tag.Properties, error) {
result := make(map[string]tag.Properties)
for _, file := range files {
p, err := e.parseFile(file)
if err != nil {
return nil, err
}
result[file] = *p
}
return result, nil
}

func (e fakeExtractor) parseFile(filePath string) (*tag.Properties, error) {
contents, err := fs.ReadFile(e.fs, filePath)
if err != nil {
return nil, err
}
data := map[string]any{}
err = json.Unmarshal(contents, &data)
if err != nil {
return nil, err
}
p := tag.Properties{
Tags: map[string][]string{},
AudioProperties: tag.AudioProperties{},
HasPicture: data["has_picture"] == "true",
}
if d, ok := data["duration"].(int64); ok {
p.AudioProperties.Duration = time.Duration(d) * time.Second
}
p.AudioProperties.BitRate, _ = data["bitrate"].(int)
p.AudioProperties.BitDepth, _ = data["bitdepth"].(int)
p.AudioProperties.SampleRate, _ = data["samplerate"].(int)
p.AudioProperties.Channels, _ = data["channels"].(int)
for k, v := range data {
p.Tags[k] = []string{fmt.Sprintf("%v", v)}
}
return &p, nil
}

func (e fakeExtractor) Version() string {
return "0.0.0"
}

var _ tag.Extractor = (*fakeExtractor)(nil)
13 changes: 13 additions & 0 deletions scanner2/scanner_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package scanner2_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestScanner(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Scanner Suite")
}
56 changes: 56 additions & 0 deletions scanner2/scanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package scanner2

import (
"context"
"testing/fstest"

"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Scanner", func() {
var fs FakeFS
var files fstest.MapFS
var ctx context.Context
var libRepo tests.MockLibraryRepo
var ds tests.MockDataStore
var s scanner.Scanner
var lib model.Library

BeforeEach(func() {
log.SetLevel(log.LevelTrace)
ctx = context.Background()
files = fstest.MapFS{}
libRepo = tests.MockLibraryRepo{}
ds.MockedLibrary = &libRepo
s = GetInstance(ctx, &ds)
RegisterFakeExtractor(fs)
})

JustBeforeEach(func() {
libRepo.SetData(model.Libraries{lib})
fs = FakeFS{MapFS: files}
})

Describe("Scan", func() {
BeforeEach(func() {
lib = model.Library{Name: "The Beatles", Path: "/music/The%20Beatles"}
sgtPeppers := template(_tag{"albumartist": "The Beatles", "album": "Sgt. Pepper's Lonely Hearts Club Band", "year": 1967})
files = fstest.MapFS{
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/01 - Sgt. Pepper's Lonely Hearts Club Band.mp3": sgtPeppers(track(1, "Sgt. Pepper's Lonely Hearts Club Band")),
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/02 - With a Little Help from My Friends.mp3": sgtPeppers(track(2, "With a Little Help from My Friends")),
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/03 - Lucy in the Sky with Diamonds.mp3": sgtPeppers(track(3, "Lucy in the Sky with Diamonds")),
"The Beatles/1967 - Sgt. Pepper's Lonely Hearts Club Band/04 - Getting Better.mp3": sgtPeppers(track(4, "Getting Better")),
}
})

It("should scan all files", func() {
err := s.RescanAll(context.Background(), true)
Expect(err).ToNot(HaveOccurred())
})
})
})
28 changes: 28 additions & 0 deletions tests/mock_library_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tests

import (
"github.com/navidrome/navidrome/model"
"golang.org/x/exp/maps"
)

type MockLibraryRepo struct {
model.LibraryRepository
data map[int]model.Library
Err error
}

func (m *MockLibraryRepo) SetData(data model.Libraries) {
m.data = make(map[int]model.Library)
for _, d := range data {
m.data[d.ID] = d
}
}

func (m *MockLibraryRepo) GetAll(...model.QueryOptions) (model.Libraries, error) {
if m.Err != nil {
return nil, m.Err
}
return maps.Values(m.data), nil
}

var _ model.LibraryRepository = &MockLibraryRepo{}
19 changes: 13 additions & 6 deletions tests/mock_persistence.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
)

type MockDataStore struct {
MockedLibrary model.LibraryRepository
MockedFolder model.FolderRepository
MockedGenre model.GenreRepository
MockedAlbum model.AlbumRepository
MockedArtist model.ArtistRepository
Expand All @@ -22,9 +24,18 @@ type MockDataStore struct {
MockedRadioBuffer model.RadioRepository
}

func (db *MockDataStore) Library(context.Context) model.LibraryRepository {
if db.MockedLibrary == nil {
db.MockedLibrary = &MockLibraryRepo{}
}
return db.MockedLibrary
}

func (db *MockDataStore) Folder(context.Context) model.FolderRepository {
//TODO implement me
panic("implement me")
if db.MockedFolder != nil {
return db.MockedFolder
}
return struct{ model.FolderRepository }{}
}

func (db *MockDataStore) Album(context.Context) model.AlbumRepository {
Expand All @@ -48,10 +59,6 @@ func (db *MockDataStore) MediaFile(context.Context) model.MediaFileRepository {
return db.MockedMediaFile
}

func (db *MockDataStore) Library(context.Context) model.LibraryRepository {
return struct{ model.LibraryRepository }{}
}

func (db *MockDataStore) Genre(context.Context) model.GenreRepository {
if db.MockedGenre == nil {
db.MockedGenre = &MockedGenreRepo{}
Expand Down

0 comments on commit d8ec5a4

Please sign in to comment.