refactor: multiple syntax updates for Go 1.26
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
545a9ecc3c
commit
efe9291db0
51 changed files with 143 additions and 251 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -37,5 +37,6 @@ AGENTS.md
|
||||||
*.wasm
|
*.wasm
|
||||||
*.ndp
|
*.ndp
|
||||||
openspec/
|
openspec/
|
||||||
|
.agents
|
||||||
go.work*
|
go.work*
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/djherbis/times"
|
"github.com/djherbis/times"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/metadata"
|
"github.com/navidrome/navidrome/model/metadata"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -91,8 +90,7 @@ var _ = Describe("Extractor", func() {
|
||||||
info.FileInfo = testFileInfo{FileInfo: fileInfo}
|
info.FileInfo = testFileInfo{FileInfo: fileInfo}
|
||||||
|
|
||||||
metadata := metadata.New(path, info)
|
metadata := metadata.New(path, info)
|
||||||
mf := metadata.ToMediaFile(1, "folderID")
|
return new(metadata.ToMediaFile(1, "folderID"))
|
||||||
return &mf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
|
|
@ -109,7 +107,7 @@ var _ = Describe("Extractor", func() {
|
||||||
Expect(mf.RGAlbumPeak).To(Equal(albumPeak))
|
Expect(mf.RGAlbumPeak).To(Equal(albumPeak))
|
||||||
},
|
},
|
||||||
Entry("mp3 with no replaygain", "no_replaygain.mp3", nil, nil, nil, nil),
|
Entry("mp3 with no replaygain", "no_replaygain.mp3", nil, nil, nil, nil),
|
||||||
Entry("mp3 with no zero replaygain", "zero_replaygain.mp3", gg.P(0.0), gg.P(1.0), gg.P(0.0), gg.P(1.0)),
|
Entry("mp3 with no zero replaygain", "zero_replaygain.mp3", new(0.0), new(1.0), new(0.0), new(1.0)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -120,8 +118,8 @@ var _ = Describe("Extractor", func() {
|
||||||
DisplayTitle: "",
|
DisplayTitle: "",
|
||||||
Lang: code,
|
Lang: code,
|
||||||
Line: []model.Line{
|
Line: []model.Line{
|
||||||
{Start: gg.P(int64(0)), Value: "This is"},
|
{Start: new(int64(0)), Value: "This is"},
|
||||||
{Start: gg.P(int64(2500)), Value: secondLine},
|
{Start: new(int64(2500)), Value: secondLine},
|
||||||
},
|
},
|
||||||
Offset: nil,
|
Offset: nil,
|
||||||
Synced: true,
|
Synced: true,
|
||||||
|
|
|
||||||
4
core/external/provider.go
vendored
4
core/external/provider.go
vendored
|
|
@ -153,7 +153,7 @@ func (e *provider) populateAlbumInfo(ctx context.Context, album auxAlbum) (auxAl
|
||||||
return album, err
|
return album, err
|
||||||
}
|
}
|
||||||
|
|
||||||
album.ExternalInfoUpdatedAt = P(time.Now())
|
album.ExternalInfoUpdatedAt = new(time.Now())
|
||||||
album.ExternalUrl = info.URL
|
album.ExternalUrl = info.URL
|
||||||
|
|
||||||
if info.Description != "" {
|
if info.Description != "" {
|
||||||
|
|
@ -269,7 +269,7 @@ func (e *provider) populateArtistInfo(ctx context.Context, artist auxArtist) (au
|
||||||
return artist, ctx.Err()
|
return artist, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
artist.ExternalInfoUpdatedAt = P(time.Now())
|
artist.ExternalInfoUpdatedAt = new(time.Now())
|
||||||
err := e.ds.Artist(ctx).UpdateExternalInfo(&artist.Artist)
|
err := e.ds.Artist(ctx).UpdateExternalInfo(&artist.Artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error trying to update artist external information", "id", artist.ID, "name", artistName,
|
log.Error(ctx, "Error trying to update artist external information", "id", artist.ID, "name", artistName,
|
||||||
|
|
|
||||||
6
core/external/provider_artistimage_test.go
vendored
6
core/external/provider_artistimage_test.go
vendored
|
|
@ -272,12 +272,11 @@ var _ = Describe("Provider - ArtistImage", func() {
|
||||||
|
|
||||||
It("returns cached URL and does not call agent when info is not expired", func() {
|
It("returns cached URL and does not call agent when info is not expired", func() {
|
||||||
// Arrange: artist has a cached image URL with recent ExternalInfoUpdatedAt
|
// Arrange: artist has a cached image URL with recent ExternalInfoUpdatedAt
|
||||||
recentTime := time.Now().Add(-1 * time.Minute)
|
|
||||||
cachedArtist := &model.Artist{
|
cachedArtist := &model.Artist{
|
||||||
ID: "artist-cached",
|
ID: "artist-cached",
|
||||||
Name: "Cached Artist",
|
Name: "Cached Artist",
|
||||||
LargeImageUrl: "http://example.com/cached-large.jpg",
|
LargeImageUrl: "http://example.com/cached-large.jpg",
|
||||||
ExternalInfoUpdatedAt: &recentTime,
|
ExternalInfoUpdatedAt: new(time.Now().Add(-1 * time.Minute)),
|
||||||
}
|
}
|
||||||
mockArtistRepo.On("Get", "artist-cached").Return(cachedArtist, nil).Maybe()
|
mockArtistRepo.On("Get", "artist-cached").Return(cachedArtist, nil).Maybe()
|
||||||
expectedURL, _ := url.Parse("http://example.com/cached-large.jpg")
|
expectedURL, _ := url.Parse("http://example.com/cached-large.jpg")
|
||||||
|
|
@ -304,12 +303,11 @@ var _ = Describe("Provider - ArtistImage", func() {
|
||||||
It("returns stale URL and enqueues refresh when info is expired", func() {
|
It("returns stale URL and enqueues refresh when info is expired", func() {
|
||||||
// Arrange
|
// Arrange
|
||||||
conf.Server.DevArtistInfoTimeToLive = 1 * time.Nanosecond
|
conf.Server.DevArtistInfoTimeToLive = 1 * time.Nanosecond
|
||||||
expiredTime := time.Now().Add(-1 * time.Hour)
|
|
||||||
staleArtist := &model.Artist{
|
staleArtist := &model.Artist{
|
||||||
ID: "artist-expired",
|
ID: "artist-expired",
|
||||||
Name: "Expired Artist",
|
Name: "Expired Artist",
|
||||||
LargeImageUrl: "http://example.com/expired-large.jpg",
|
LargeImageUrl: "http://example.com/expired-large.jpg",
|
||||||
ExternalInfoUpdatedAt: &expiredTime,
|
ExternalInfoUpdatedAt: new(time.Now().Add(-1 * time.Hour)),
|
||||||
}
|
}
|
||||||
mockArtistRepo.On("Get", "artist-expired").Return(staleArtist, nil).Maybe()
|
mockArtistRepo.On("Get", "artist-expired").Return(staleArtist, nil).Maybe()
|
||||||
expectedURL, _ := url.Parse("http://example.com/expired-large.jpg")
|
expectedURL, _ := url.Parse("http://example.com/expired-large.jpg")
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
@ -90,7 +89,7 @@ var _ = Describe("Provider - UpdateAlbumInfo", func() {
|
||||||
ExternalUrl: "http://cached.com/album",
|
ExternalUrl: "http://cached.com/album",
|
||||||
Description: "Cached Desc",
|
Description: "Cached Desc",
|
||||||
LargeImageUrl: "http://cached.com/large.jpg",
|
LargeImageUrl: "http://cached.com/large.jpg",
|
||||||
ExternalInfoUpdatedAt: gg.P(now.Add(-conf.Server.DevAlbumInfoTimeToLive / 2)),
|
ExternalInfoUpdatedAt: new(now.Add(-conf.Server.DevAlbumInfoTimeToLive / 2)),
|
||||||
}
|
}
|
||||||
mockAlbumRepo.SetData(model.Albums{*originalAlbum})
|
mockAlbumRepo.SetData(model.Albums{*originalAlbum})
|
||||||
|
|
||||||
|
|
@ -113,7 +112,7 @@ var _ = Describe("Provider - UpdateAlbumInfo", func() {
|
||||||
ExternalUrl: "http://expired.com/album",
|
ExternalUrl: "http://expired.com/album",
|
||||||
Description: "Expired Desc",
|
Description: "Expired Desc",
|
||||||
LargeImageUrl: "http://expired.com/large.jpg",
|
LargeImageUrl: "http://expired.com/large.jpg",
|
||||||
ExternalInfoUpdatedAt: gg.P(expiredTime),
|
ExternalInfoUpdatedAt: new(expiredTime),
|
||||||
}
|
}
|
||||||
mockAlbumRepo.SetData(model.Albums{*originalAlbum})
|
mockAlbumRepo.SetData(model.Albums{*originalAlbum})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
@ -137,7 +136,7 @@ var _ = Describe("Provider - UpdateArtistInfo", func() {
|
||||||
ExternalUrl: "http://cached.url",
|
ExternalUrl: "http://cached.url",
|
||||||
Biography: "Cached Bio",
|
Biography: "Cached Bio",
|
||||||
LargeImageUrl: "http://cached_large.jpg",
|
LargeImageUrl: "http://cached_large.jpg",
|
||||||
ExternalInfoUpdatedAt: gg.P(now.Add(-conf.Server.DevArtistInfoTimeToLive / 2)),
|
ExternalInfoUpdatedAt: new(now.Add(-conf.Server.DevArtistInfoTimeToLive / 2)),
|
||||||
SimilarArtists: model.Artists{
|
SimilarArtists: model.Artists{
|
||||||
{ID: "ar-similar-present", Name: "Similar Present"},
|
{ID: "ar-similar-present", Name: "Similar Present"},
|
||||||
{ID: "ar-similar-absent", Name: "Similar Absent"},
|
{ID: "ar-similar-absent", Name: "Similar Absent"},
|
||||||
|
|
@ -174,7 +173,7 @@ var _ = Describe("Provider - UpdateArtistInfo", func() {
|
||||||
originalArtist := &model.Artist{
|
originalArtist := &model.Artist{
|
||||||
ID: "ar-expired",
|
ID: "ar-expired",
|
||||||
Name: "Expired Artist",
|
Name: "Expired Artist",
|
||||||
ExternalInfoUpdatedAt: gg.P(expiredTime),
|
ExternalInfoUpdatedAt: new(expiredTime),
|
||||||
SimilarArtists: model.Artists{
|
SimilarArtists: model.Artists{
|
||||||
{ID: "ar-exp-similar", Name: "Expired Similar"},
|
{ID: "ar-exp-similar", Name: "Expired Similar"},
|
||||||
},
|
},
|
||||||
|
|
@ -205,7 +204,7 @@ var _ = Describe("Provider - UpdateArtistInfo", func() {
|
||||||
originalArtist := &model.Artist{
|
originalArtist := &model.Artist{
|
||||||
ID: "ar-similar-test",
|
ID: "ar-similar-test",
|
||||||
Name: "Similar Test Artist",
|
Name: "Similar Test Artist",
|
||||||
ExternalInfoUpdatedAt: gg.P(now.Add(-conf.Server.DevArtistInfoTimeToLive / 2)),
|
ExternalInfoUpdatedAt: new(now.Add(-conf.Server.DevArtistInfoTimeToLive / 2)),
|
||||||
SimilarArtists: model.Artists{
|
SimilarArtists: model.Artists{
|
||||||
{ID: "ar-sim-present", Name: "Similar Present"},
|
{ID: "ar-sim-present", Name: "Similar Present"},
|
||||||
{ID: "", Name: "Similar Absent Raw"},
|
{ID: "", Name: "Similar Absent Raw"},
|
||||||
|
|
|
||||||
|
|
@ -326,8 +326,7 @@ func (j *ffCmd) start(ctx context.Context) error {
|
||||||
|
|
||||||
func (j *ffCmd) wait() {
|
func (j *ffCmd) wait() {
|
||||||
if err := j.cmd.Wait(); err != nil {
|
if err := j.cmd.Wait(); err != nil {
|
||||||
var exitErr *exec.ExitError
|
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||||
if errors.As(err, &exitErr) {
|
|
||||||
errMsg := fmt.Sprintf("%s exited with non-zero status code: %d", j.args[0], exitErr.ExitCode())
|
errMsg := fmt.Sprintf("%s exited with non-zero status code: %d", j.args[0], exitErr.ExitCode())
|
||||||
if stderrOutput := strings.TrimSpace(j.stderr.String()); stderrOutput != "" {
|
if stderrOutput := strings.TrimSpace(j.stderr.String()); stderrOutput != "" {
|
||||||
errMsg += ": " + stderrOutput
|
errMsg += ": " + stderrOutput
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/metadata"
|
"github.com/navidrome/navidrome/model/metadata"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type InspectOutput struct {
|
type InspectOutput struct {
|
||||||
|
|
@ -44,7 +43,7 @@ func Inspect(filePath string, libraryId int, folderId string) (*InspectOutput, e
|
||||||
result := &InspectOutput{
|
result := &InspectOutput{
|
||||||
File: filePath,
|
File: filePath,
|
||||||
RawTags: tags[file].Tags,
|
RawTags: tags[file].Tags,
|
||||||
MappedTags: P(md.ToMediaFile(libraryId, folderId)),
|
MappedTags: new(md.ToMediaFile(libraryId, folderId)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
"github.com/navidrome/navidrome/utils"
|
"github.com/navidrome/navidrome/utils"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -32,15 +31,15 @@ var _ = Describe("sources", func() {
|
||||||
Lang: "eng",
|
Lang: "eng",
|
||||||
Line: []model.Line{
|
Line: []model.Line{
|
||||||
{
|
{
|
||||||
Start: gg.P(int64(18800)),
|
Start: new(int64(18800)),
|
||||||
Value: "We're no strangers to love",
|
Value: "We're no strangers to love",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Start: gg.P(int64(22801)),
|
Start: new(int64(22801)),
|
||||||
Value: "You know the rules and so do I",
|
Value: "You know the rules and so do I",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Offset: gg.P(int64(-100)),
|
Offset: new(int64(-100)),
|
||||||
Synced: true,
|
Synced: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -74,15 +73,15 @@ var _ = Describe("sources", func() {
|
||||||
Lang: "eng",
|
Lang: "eng",
|
||||||
Line: []model.Line{
|
Line: []model.Line{
|
||||||
{
|
{
|
||||||
Start: gg.P(int64(18800)),
|
Start: new(int64(18800)),
|
||||||
Value: "We're no strangers to love",
|
Value: "We're no strangers to love",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Start: gg.P(int64(22801)),
|
Start: new(int64(22801)),
|
||||||
Value: "You know the rules and so do I",
|
Value: "You know the rules and so do I",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Offset: gg.P(int64(-100)),
|
Offset: new(int64(-100)),
|
||||||
Synced: true,
|
Synced: true,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
@ -122,7 +121,7 @@ var _ = Describe("sources", func() {
|
||||||
// The critical assertion: even with BOM, synced should be true
|
// The critical assertion: even with BOM, synced should be true
|
||||||
Expect(lyrics[0].Synced).To(BeTrue(), "Lyrics with BOM marker should be recognized as synced")
|
Expect(lyrics[0].Synced).To(BeTrue(), "Lyrics with BOM marker should be recognized as synced")
|
||||||
Expect(lyrics[0].Line).To(HaveLen(1))
|
Expect(lyrics[0].Line).To(HaveLen(1))
|
||||||
Expect(lyrics[0].Line[0].Start).To(Equal(gg.P(int64(0))))
|
Expect(lyrics[0].Line[0].Start).To(Equal(new(int64(0))))
|
||||||
Expect(lyrics[0].Line[0].Value).To(ContainSubstring("作曲"))
|
Expect(lyrics[0].Line[0].Value).To(ContainSubstring("作曲"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -137,9 +136,9 @@ var _ = Describe("sources", func() {
|
||||||
// UTF-16 should be properly converted to UTF-8
|
// UTF-16 should be properly converted to UTF-8
|
||||||
Expect(lyrics[0].Synced).To(BeTrue(), "UTF-16 encoded lyrics should be recognized as synced")
|
Expect(lyrics[0].Synced).To(BeTrue(), "UTF-16 encoded lyrics should be recognized as synced")
|
||||||
Expect(lyrics[0].Line).To(HaveLen(2))
|
Expect(lyrics[0].Line).To(HaveLen(2))
|
||||||
Expect(lyrics[0].Line[0].Start).To(Equal(gg.P(int64(18800))))
|
Expect(lyrics[0].Line[0].Start).To(Equal(new(int64(18800))))
|
||||||
Expect(lyrics[0].Line[0].Value).To(Equal("We're no strangers to love"))
|
Expect(lyrics[0].Line[0].Value).To(Equal("We're no strangers to love"))
|
||||||
Expect(lyrics[0].Line[1].Start).To(Equal(gg.P(int64(22801))))
|
Expect(lyrics[0].Line[1].Start).To(Equal(new(int64(22801))))
|
||||||
Expect(lyrics[0].Line[1].Value).To(Equal("You know the rules and so do I"))
|
Expect(lyrics[0].Line[1].Value).To(Equal("You know the rules and so do I"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,7 @@ func (j *Executor) start(ctx context.Context) error {
|
||||||
|
|
||||||
func (j *Executor) wait() {
|
func (j *Executor) wait() {
|
||||||
if err := j.cmd.Wait(); err != nil {
|
if err := j.cmd.Wait(); err != nil {
|
||||||
var exitErr *exec.ExitError
|
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||||
if errors.As(err, &exitErr) {
|
|
||||||
_ = j.out.CloseWithError(fmt.Errorf("%s exited with non-zero status code: %d", j.args[0], exitErr.ExitCode()))
|
_ = j.out.CloseWithError(fmt.Errorf("%s exited with non-zero status code: %d", j.args[0], exitErr.ExitCode()))
|
||||||
} else {
|
} else {
|
||||||
_ = j.out.CloseWithError(fmt.Errorf("waiting %s cmd: %w", j.args[0], err))
|
_ = j.out.CloseWithError(fmt.Errorf("waiting %s cmd: %w", j.args[0], err))
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,7 @@ func (s *playlists) parseNSP(_ context.Context, pls *model.Playlist, reader io.R
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(input, nsp)
|
err = json.Unmarshal(input, nsp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var syntaxErr *json.SyntaxError
|
if syntaxErr, ok := errors.AsType[*json.SyntaxError](err); ok {
|
||||||
if errors.As(err, &syntaxErr) {
|
|
||||||
line, col := getPositionFromOffset(input, syntaxErr.Offset)
|
line, col := getPositionFromOffset(input, syntaxErr.Offset)
|
||||||
return fmt.Errorf("JSON syntax error in SmartPlaylist at line %d, column %d: %w", line, col, err)
|
return fmt.Errorf("JSON syntax error in SmartPlaylist at line %d, column %d: %w", line, col, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,29 +144,25 @@ var _ = Describe("Playlists", func() {
|
||||||
|
|
||||||
It("allows owner to update their playlist", func() {
|
It("allows owner to update their playlist", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||||
newName := "Updated Name"
|
err := ps.Update(ctx, "pls-1", new("Updated Name"), nil, nil, nil, nil)
|
||||||
err := ps.Update(ctx, "pls-1", &newName, nil, nil, nil, nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("allows admin to update any playlist", func() {
|
It("allows admin to update any playlist", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "admin-1", IsAdmin: true})
|
ctx = request.WithUser(ctx, model.User{ID: "admin-1", IsAdmin: true})
|
||||||
newName := "Updated Name"
|
err := ps.Update(ctx, "pls-other", new("Updated Name"), nil, nil, nil, nil)
|
||||||
err := ps.Update(ctx, "pls-other", &newName, nil, nil, nil, nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("denies non-owner, non-admin from updating", func() {
|
It("denies non-owner, non-admin from updating", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "other-user", IsAdmin: false})
|
ctx = request.WithUser(ctx, model.User{ID: "other-user", IsAdmin: false})
|
||||||
newName := "Updated Name"
|
err := ps.Update(ctx, "pls-1", new("Updated Name"), nil, nil, nil, nil)
|
||||||
err := ps.Update(ctx, "pls-1", &newName, nil, nil, nil, nil)
|
|
||||||
Expect(err).To(MatchError(model.ErrNotAuthorized))
|
Expect(err).To(MatchError(model.ErrNotAuthorized))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns error when playlist not found", func() {
|
It("returns error when playlist not found", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||||
newName := "Updated Name"
|
err := ps.Update(ctx, "nonexistent", new("Updated Name"), nil, nil, nil, nil)
|
||||||
err := ps.Update(ctx, "nonexistent", &newName, nil, nil, nil, nil)
|
|
||||||
Expect(err).To(Equal(model.ErrNotFound))
|
Expect(err).To(Equal(model.ErrNotFound))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -184,8 +180,7 @@ var _ = Describe("Playlists", func() {
|
||||||
|
|
||||||
It("allows metadata updates on a smart playlist", func() {
|
It("allows metadata updates on a smart playlist", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||||
newName := "Updated Smart"
|
err := ps.Update(ctx, "pls-smart", new("Updated Smart"), nil, nil, nil, nil)
|
||||||
err := ps.Update(ctx, "pls-smart", &newName, nil, nil, nil, nil)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ var _ = Describe("REST Adapter", func() {
|
||||||
It("clears server-managed fields to prevent injection via REST API", func() {
|
It("clears server-managed fields to prevent injection via REST API", func() {
|
||||||
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
ctx = request.WithUser(ctx, model.User{ID: "user-1", IsAdmin: false})
|
||||||
repo = ps.NewRepository(ctx).(rest.Persistable)
|
repo = ps.NewRepository(ctx).(rest.Persistable)
|
||||||
now := time.Now()
|
|
||||||
pls := &model.Playlist{
|
pls := &model.Playlist{
|
||||||
Name: "Legit Playlist",
|
Name: "Legit Playlist",
|
||||||
Comment: "A comment",
|
Comment: "A comment",
|
||||||
|
|
@ -73,7 +72,7 @@ var _ = Describe("REST Adapter", func() {
|
||||||
Sync: true,
|
Sync: true,
|
||||||
UploadedImage: "injected-image-path",
|
UploadedImage: "injected-image-path",
|
||||||
ExternalImageURL: "http://evil.example.com/ssrf",
|
ExternalImageURL: "http://evil.example.com/ssrf",
|
||||||
EvaluatedAt: &now,
|
EvaluatedAt: new(time.Now()),
|
||||||
}
|
}
|
||||||
_, err := repo.Save(pls)
|
_, err := repo.Save(pls)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
|
||||||
|
|
@ -1133,8 +1133,7 @@ func (f *fakeScrobbler) PlaybackReport(ctx context.Context, info PlaybackSession
|
||||||
if f.Error != nil {
|
if f.Error != nil {
|
||||||
return f.Error
|
return f.Error
|
||||||
}
|
}
|
||||||
uid := info.UserId
|
f.userID.Store(new(info.UserId))
|
||||||
f.userID.Store(&uid)
|
|
||||||
f.LastPlaybackReport.Store(&info)
|
f.LastPlaybackReport.Store(&info)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error
|
||||||
if !expiresAt.IsZero() && expiresAt.Before(time.Now()) {
|
if !expiresAt.IsZero() && expiresAt.Before(time.Now()) {
|
||||||
return nil, model.ErrExpired
|
return nil, model.ErrExpired
|
||||||
}
|
}
|
||||||
share.LastVisitedAt = P(time.Now())
|
share.LastVisitedAt = new(time.Now())
|
||||||
share.VisitCount++
|
share.VisitCount++
|
||||||
|
|
||||||
err = repo.(rest.Persistable).Update(id, share, "last_visited_at", "visit_count")
|
err = repo.(rest.Persistable).Update(id, share, "last_visited_at", "visit_count")
|
||||||
|
|
@ -95,7 +95,7 @@ func (r *shareRepositoryWrapper) Save(entity any) (string, error) {
|
||||||
}
|
}
|
||||||
s.ID = id
|
s.ID = id
|
||||||
if V(s.ExpiresAt).IsZero() {
|
if V(s.ExpiresAt).IsZero() {
|
||||||
s.ExpiresAt = P(time.Now().Add(conf.Server.DefaultShareExpiration))
|
s.ExpiresAt = new(time.Now().Add(conf.Server.DefaultShareExpiration))
|
||||||
}
|
}
|
||||||
|
|
||||||
firstId := strings.SplitN(s.ResourceIDs, ",", 2)[0]
|
firstId := strings.SplitN(s.ResourceIDs, ",", 2)[0]
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ import (
|
||||||
var _ = Describe("ArtworkID", func() {
|
var _ = Describe("ArtworkID", func() {
|
||||||
Describe("NewArtworkID()", func() {
|
Describe("NewArtworkID()", func() {
|
||||||
It("creates a valid parseable ArtworkID", func() {
|
It("creates a valid parseable ArtworkID", func() {
|
||||||
now := time.Now()
|
id := model.NewArtworkID(model.KindAlbumArtwork, "1234", new(time.Now()))
|
||||||
id := model.NewArtworkID(model.KindAlbumArtwork, "1234", &now)
|
|
||||||
parsedId, err := model.ParseArtworkID(id.String())
|
parsedId, err := model.ParseArtworkID(id.String())
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(parsedId.Kind).To(Equal(id.Kind))
|
Expect(parsedId.Kind).To(Equal(id.Kind))
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@ import (
|
||||||
|
|
||||||
var _ = Describe("ToLyrics", func() {
|
var _ = Describe("ToLyrics", func() {
|
||||||
It("should parse tags with spaces", func() {
|
It("should parse tags with spaces", func() {
|
||||||
num := int64(1551)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[lang: eng ]\n[offset: 1551 ]\n[ti: A title ]\n[ar: An artist ]\n[00:00.00]Hi there")
|
lyrics, err := ToLyrics("xxx", "[lang: eng ]\n[offset: 1551 ]\n[ti: A title ]\n[ar: An artist ]\n[00:00.00]Hi there")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Lang).To(Equal("eng"))
|
Expect(lyrics.Lang).To(Equal("eng"))
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.DisplayArtist).To(Equal("An artist"))
|
Expect(lyrics.DisplayArtist).To(Equal("An artist"))
|
||||||
Expect(lyrics.DisplayTitle).To(Equal("A title"))
|
Expect(lyrics.DisplayTitle).To(Equal("A title"))
|
||||||
Expect(lyrics.Offset).To(Equal(&num))
|
Expect(lyrics.Offset).To(Equal(new(int64(1551))))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should ignore bad offset", func() {
|
It("Should ignore bad offset", func() {
|
||||||
|
|
@ -25,39 +24,36 @@ var _ = Describe("ToLyrics", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should accept lines with no text and weird times", func() {
|
It("should accept lines with no text and weird times", func() {
|
||||||
a, b, c, d := int64(0), int64(10040), int64(40000), int64(1000*60*60)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[00:00.00]Hi there\n\n\n[00:10.040]\n[00:40]Test\n[01:00:00]late")
|
lyrics, err := ToLyrics("xxx", "[00:00.00]Hi there\n\n\n[00:10.040]\n[00:40]Test\n[01:00:00]late")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "Hi there"},
|
{Start: new(int64(0)), Value: "Hi there"},
|
||||||
{Start: &b, Value: ""},
|
{Start: new(int64(10040)), Value: ""},
|
||||||
{Start: &c, Value: "Test"},
|
{Start: new(int64(40000)), Value: "Test"},
|
||||||
{Start: &d, Value: "late"},
|
{Start: new(int64(1000 * 60 * 60)), Value: "late"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should support multiple timestamps per line", func() {
|
It("Should support multiple timestamps per line", func() {
|
||||||
a, b, c, d := int64(0), int64(10000), int64(13*60*1000), int64(1000*60*60*51)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[00:00.00] [00:10.00]Repeated\n[13:00][51:00:00.00]")
|
lyrics, err := ToLyrics("xxx", "[00:00.00] [00:10.00]Repeated\n[13:00][51:00:00.00]")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "Repeated"},
|
{Start: new(int64(0)), Value: "Repeated"},
|
||||||
{Start: &b, Value: "Repeated"},
|
{Start: new(int64(10000)), Value: "Repeated"},
|
||||||
{Start: &c, Value: ""},
|
{Start: new(int64(13 * 60 * 1000)), Value: ""},
|
||||||
{Start: &d, Value: ""},
|
{Start: new(int64(1000 * 60 * 60 * 51)), Value: ""},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Should support parsing multiline string", func() {
|
It("Should support parsing multiline string", func() {
|
||||||
a, b := int64(0), int64(10*60*1000+1)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[00:00.00]This is\na multiline \n\n [:0] string\n[10:00.001]This is\nalso one")
|
lyrics, err := ToLyrics("xxx", "[00:00.00]This is\na multiline \n\n [:0] string\n[10:00.001]This is\nalso one")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "This is\na multiline\n\n[:0] string"},
|
{Start: new(int64(0)), Value: "This is\na multiline\n\n[:0] string"},
|
||||||
{Start: &b, Value: "This is\nalso one"},
|
{Start: new(int64(10*60*1000 + 1)), Value: "This is\nalso one"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -71,49 +67,45 @@ var _ = Describe("ToLyrics", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Allows timestamp in middle of line if also at beginning", func() {
|
It("Allows timestamp in middle of line if also at beginning", func() {
|
||||||
a, b := int64(0), int64(1000)
|
|
||||||
lyrics, err := ToLyrics("xxx", " [00:00] This is [00:00:00] be a synced file\n [00:01]Line 2")
|
lyrics, err := ToLyrics("xxx", " [00:00] This is [00:00:00] be a synced file\n [00:01]Line 2")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "This is [00:00:00] be a synced file"},
|
{Start: new(int64(0)), Value: "This is [00:00:00] be a synced file"},
|
||||||
{Start: &b, Value: "Line 2"},
|
{Start: new(int64(1000)), Value: "Line 2"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Ignores lines in synchronized lyric prior to first timestamp", func() {
|
It("Ignores lines in synchronized lyric prior to first timestamp", func() {
|
||||||
a := int64(0)
|
|
||||||
lyrics, err := ToLyrics("xxx", "This is some prelude\nThat doesn't\nmatter\n[00:00]Text")
|
lyrics, err := ToLyrics("xxx", "This is some prelude\nThat doesn't\nmatter\n[00:00]Text")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "Text"},
|
{Start: new(int64(0)), Value: "Text"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Handles all possible ms cases", func() {
|
It("Handles all possible ms cases", func() {
|
||||||
a, b, c := int64(1), int64(10), int64(100)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[00:00.001]a\n[00:00.01]b\n[00:00.1]c")
|
lyrics, err := ToLyrics("xxx", "[00:00.001]a\n[00:00.01]b\n[00:00.1]c")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "a"},
|
{Start: new(int64(1)), Value: "a"},
|
||||||
{Start: &b, Value: "b"},
|
{Start: new(int64(10)), Value: "b"},
|
||||||
{Start: &c, Value: "c"},
|
{Start: new(int64(100)), Value: "c"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Properly sorts repeated lyrics out of order", func() {
|
It("Properly sorts repeated lyrics out of order", func() {
|
||||||
a, b, c, d, e := int64(0), int64(10000), int64(40000), int64(13*60*1000), int64(1000*60*60*51)
|
|
||||||
lyrics, err := ToLyrics("xxx", "[00:00.00] [13:00]Repeated\n[00:10.00][51:00:00.00]Test\n[00:40.00]Not repeated")
|
lyrics, err := ToLyrics("xxx", "[00:00.00] [13:00]Repeated\n[00:10.00][51:00:00.00]Test\n[00:40.00]Not repeated")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(lyrics.Synced).To(BeTrue())
|
Expect(lyrics.Synced).To(BeTrue())
|
||||||
Expect(lyrics.Line).To(Equal([]Line{
|
Expect(lyrics.Line).To(Equal([]Line{
|
||||||
{Start: &a, Value: "Repeated"},
|
{Start: new(int64(0)), Value: "Repeated"},
|
||||||
{Start: &b, Value: "Test"},
|
{Start: new(int64(10000)), Value: "Test"},
|
||||||
{Start: &c, Value: "Not repeated"},
|
{Start: new(int64(40000)), Value: "Not repeated"},
|
||||||
{Start: &d, Value: "Repeated"},
|
{Start: new(int64(13 * 60 * 1000)), Value: "Repeated"},
|
||||||
{Start: &e, Value: "Test"},
|
{Start: new(int64(1000 * 60 * 60 * 51)), Value: "Test"},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/metadata"
|
"github.com/navidrome/navidrome/model/metadata"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -108,8 +107,8 @@ var _ = Describe("ToMediaFile", func() {
|
||||||
|
|
||||||
expected := model.LyricList{
|
expected := model.LyricList{
|
||||||
{Lang: "eng", Line: []model.Line{
|
{Lang: "eng", Line: []model.Line{
|
||||||
{Value: "This is", Start: P(int64(0))},
|
{Value: "This is", Start: new(int64(0))},
|
||||||
{Value: "English SYLT", Start: P(int64(2500))},
|
{Value: "English SYLT", Start: new(int64(2500))},
|
||||||
}, Synced: true},
|
}, Synced: true},
|
||||||
{Lang: "xxx", Line: []model.Line{{Value: "Lyrics"}}, Synced: false},
|
{Lang: "xxx", Line: []model.Line{{Value: "Lyrics"}}, Synced: false},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/metadata"
|
"github.com/navidrome/navidrome/model/metadata"
|
||||||
"github.com/navidrome/navidrome/utils"
|
"github.com/navidrome/navidrome/utils"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -274,8 +273,8 @@ var _ = Describe("Metadata", func() {
|
||||||
mf := createMF("replaygain_track_gain", tagValue)
|
mf := createMF("replaygain_track_gain", tagValue)
|
||||||
Expect(mf.RGTrackGain).To(Equal(expected))
|
Expect(mf.RGTrackGain).To(Equal(expected))
|
||||||
},
|
},
|
||||||
Entry("0", "0", gg.P(0.0)),
|
Entry("0", "0", new(0.0)),
|
||||||
Entry("1.2dB", "1.2dB", gg.P(1.2)),
|
Entry("1.2dB", "1.2dB", new(1.2)),
|
||||||
Entry("Infinity", "Infinity", nil),
|
Entry("Infinity", "Infinity", nil),
|
||||||
Entry("Invalid value", "INVALID VALUE", nil),
|
Entry("Invalid value", "INVALID VALUE", nil),
|
||||||
Entry("NaN", "NaN", nil),
|
Entry("NaN", "NaN", nil),
|
||||||
|
|
@ -285,9 +284,9 @@ var _ = Describe("Metadata", func() {
|
||||||
mf := createMF("replaygain_track_peak", tagValue)
|
mf := createMF("replaygain_track_peak", tagValue)
|
||||||
Expect(mf.RGTrackPeak).To(Equal(expected))
|
Expect(mf.RGTrackPeak).To(Equal(expected))
|
||||||
},
|
},
|
||||||
Entry("0", "0", gg.P(0.0)),
|
Entry("0", "0", new(0.0)),
|
||||||
Entry("1.0", "1.0", gg.P(1.0)),
|
Entry("1.0", "1.0", new(1.0)),
|
||||||
Entry("0.5", "0.5", gg.P(0.5)),
|
Entry("0.5", "0.5", new(0.5)),
|
||||||
Entry("Invalid dB suffix", "0.7dB", nil),
|
Entry("Invalid dB suffix", "0.7dB", nil),
|
||||||
Entry("Infinity", "Infinity", nil),
|
Entry("Infinity", "Infinity", nil),
|
||||||
Entry("Invalid value", "INVALID VALUE", nil),
|
Entry("Invalid value", "INVALID VALUE", nil),
|
||||||
|
|
@ -299,8 +298,8 @@ var _ = Describe("Metadata", func() {
|
||||||
Expect(mf.RGTrackGain).To(Equal(expected))
|
Expect(mf.RGTrackGain).To(Equal(expected))
|
||||||
|
|
||||||
},
|
},
|
||||||
Entry("0", "0", gg.P(5.0)),
|
Entry("0", "0", new(5.0)),
|
||||||
Entry("-3776", "-3776", gg.P(-9.75)),
|
Entry("-3776", "-3776", new(-9.75)),
|
||||||
Entry("Infinity", "Infinity", nil),
|
Entry("Infinity", "Infinity", nil),
|
||||||
Entry("Invalid value", "INVALID VALUE", nil),
|
Entry("Invalid value", "INVALID VALUE", nil),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/utils"
|
"github.com/navidrome/navidrome/utils"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
)
|
)
|
||||||
|
|
@ -219,7 +218,7 @@ func (r *artistRepository) Exists(id string) (bool, error) {
|
||||||
|
|
||||||
func (r *artistRepository) Put(a *model.Artist, colsToUpdate ...string) error {
|
func (r *artistRepository) Put(a *model.Artist, colsToUpdate ...string) error {
|
||||||
dba := &dbArtist{Artist: a}
|
dba := &dbArtist{Artist: a}
|
||||||
dba.CreatedAt = P(time.Now())
|
dba.CreatedAt = new(time.Now())
|
||||||
dba.UpdatedAt = dba.CreatedAt
|
dba.UpdatedAt = dba.CreatedAt
|
||||||
_, err := r.put(dba.ID, dba, colsToUpdate...)
|
_, err := r.put(dba.ID, dba, colsToUpdate...)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ type genreRepository struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGenreRepository(ctx context.Context, db dbx.Builder) model.GenreRepository {
|
func NewGenreRepository(ctx context.Context, db dbx.Builder) model.GenreRepository {
|
||||||
genreFilter := model.TagGenre
|
|
||||||
return &genreRepository{
|
return &genreRepository{
|
||||||
baseTagRepository: newBaseTagRepository(ctx, db, &genreFilter),
|
baseTagRepository: newBaseTagRepository(ctx, db, new(model.TagGenre)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/pocketbase/dbx"
|
"github.com/pocketbase/dbx"
|
||||||
|
|
@ -103,7 +102,7 @@ var (
|
||||||
songAntenna = mf(model.MediaFile{ID: "1004", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk",
|
songAntenna = mf(model.MediaFile{ID: "1004", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk",
|
||||||
AlbumID: "103",
|
AlbumID: "103",
|
||||||
Path: p("kraft/radio/antenna.mp3"),
|
Path: p("kraft/radio/antenna.mp3"),
|
||||||
RGAlbumGain: gg.P(1.0), RGAlbumPeak: gg.P(2.0), RGTrackGain: gg.P(3.0), RGTrackPeak: gg.P(4.0),
|
RGAlbumGain: new(1.0), RGAlbumPeak: new(2.0), RGTrackGain: new(3.0), RGTrackPeak: new(4.0),
|
||||||
})
|
})
|
||||||
songAntennaWithLyrics = mf(model.MediaFile{
|
songAntennaWithLyrics = mf(model.MediaFile{
|
||||||
ID: "1005",
|
ID: "1005",
|
||||||
|
|
@ -162,8 +161,6 @@ func p(path string) string {
|
||||||
return filepath.FromSlash(path)
|
return filepath.FromSlash(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize test DB
|
|
||||||
// TODO Load this data setup from file(s)
|
|
||||||
var _ = BeforeSuite(func() {
|
var _ = BeforeSuite(func() {
|
||||||
conn := GetDBXBuilder()
|
conn := GetDBXBuilder()
|
||||||
ctx := log.NewContext(context.TODO())
|
ctx := log.NewContext(context.TODO())
|
||||||
|
|
@ -187,8 +184,7 @@ var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
alr := NewAlbumRepository(ctx, conn).(*albumRepository)
|
alr := NewAlbumRepository(ctx, conn).(*albumRepository)
|
||||||
for i := range testAlbums {
|
for i := range testAlbums {
|
||||||
a := testAlbums[i]
|
err := alr.Put(new(testAlbums[i]))
|
||||||
err := alr.Put(&a)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -196,8 +192,7 @@ var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
arr := NewArtistRepository(ctx, conn)
|
arr := NewArtistRepository(ctx, conn)
|
||||||
for i := range testArtists {
|
for i := range testArtists {
|
||||||
a := testArtists[i]
|
err := arr.Put(new(testArtists[i]))
|
||||||
err := arr.Put(&a)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -243,8 +238,7 @@ var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
rar := NewRadioRepository(ctx, conn)
|
rar := NewRadioRepository(ctx, conn)
|
||||||
for i := range testRadios {
|
for i := range testRadios {
|
||||||
r := testRadios[i]
|
err := rar.Put(new(testRadios[i]))
|
||||||
err := rar.Put(&r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,7 @@ func (r *playQueueRepository) Retrieve(userId string) (*model.PlayQueue, error)
|
||||||
sel := r.newSelect().Columns("*").Where(Eq{"user_id": userId})
|
sel := r.newSelect().Columns("*").Where(Eq{"user_id": userId})
|
||||||
var res playQueue
|
var res playQueue
|
||||||
err := r.queryOne(sel, &res)
|
err := r.queryOne(sel, &res)
|
||||||
q := r.toModel(&res)
|
return new(r.toModel(&res)), err
|
||||||
return &q, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *playQueueRepository) fromModel(q *model.PlayQueue) playQueue {
|
func (r *playQueueRepository) fromModel(q *model.PlayQueue) playQueue {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,7 @@ var _ = Describe("RadioRepository", func() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range testRadios {
|
for i := range testRadios {
|
||||||
r := testRadios[i]
|
err := repo.Put(new(testRadios[i]))
|
||||||
err := repo.Put(&r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,7 @@ var _ = Describe("KVStoreService", func() {
|
||||||
conf.Server.DataFolder = conf.NewDir(tmpDir)
|
conf.Server.DataFolder = conf.NewDir(tmpDir)
|
||||||
|
|
||||||
// Create service with 1KB limit for testing
|
// Create service with 1KB limit for testing
|
||||||
maxSize := "1KB"
|
service, err = newKVStoreService(ctx, "test_plugin", &KVStorePermission{MaxSize: new("1KB")})
|
||||||
service, err = newKVStoreService(ctx, "test_plugin", &KVStorePermission{MaxSize: &maxSize})
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -253,8 +252,7 @@ var _ = Describe("KVStoreService", func() {
|
||||||
// Close and reopen the service (simulating restart)
|
// Close and reopen the service (simulating restart)
|
||||||
Expect(service.Close()).To(Succeed())
|
Expect(service.Close()).To(Succeed())
|
||||||
|
|
||||||
maxSize := "1KB"
|
service2, err := newKVStoreService(ctx, "test_plugin", &KVStorePermission{MaxSize: new("1KB")})
|
||||||
service2, err := newKVStoreService(ctx, "test_plugin", &KVStorePermission{MaxSize: &maxSize})
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
defer service2.Close()
|
defer service2.Close()
|
||||||
|
|
||||||
|
|
@ -452,8 +450,7 @@ var _ = Describe("KVStoreService", func() {
|
||||||
closeCtx, closeCancel := context.WithCancel(ctx)
|
closeCtx, closeCancel := context.WithCancel(ctx)
|
||||||
defer closeCancel()
|
defer closeCancel()
|
||||||
|
|
||||||
maxSize := "1KB"
|
svc, err := newKVStoreService(closeCtx, "test_close_race", &KVStorePermission{MaxSize: new("1KB")})
|
||||||
svc, err := newKVStoreService(closeCtx, "test_close_race", &KVStorePermission{MaxSize: &maxSize})
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
// Insert an expired key so cleanup has work to do
|
// Insert an expired key so cleanup has work to do
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
|
|
||||||
Describe("GetLibrary", func() {
|
Describe("GetLibrary", func() {
|
||||||
It("should return library metadata without filesystem permission", func() {
|
It("should return library metadata without filesystem permission", func() {
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, nil, true).(*libraryServiceImpl)
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, nil, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
lib := &model.Library{
|
lib := &model.Library{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
|
|
@ -67,8 +66,7 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return library metadata with filesystem permission", func() {
|
It("should return library metadata with filesystem permission", func() {
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: true}, nil, true).(*libraryServiceImpl)
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: true}, nil, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
lib := &model.Library{
|
lib := &model.Library{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
|
|
@ -93,8 +91,7 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return error for non-existent library", func() {
|
It("should return error for non-existent library", func() {
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test")}, nil, true).(*libraryServiceImpl)
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason}, nil, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
mockLibRepo := ds.Library(ctx).(*tests.MockLibraryRepo)
|
mockLibRepo := ds.Library(ctx).(*tests.MockLibraryRepo)
|
||||||
mockLibRepo.SetData(model.Libraries{})
|
mockLibRepo.SetData(model.Libraries{})
|
||||||
|
|
@ -107,8 +104,7 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
|
|
||||||
Describe("GetAllLibraries", func() {
|
Describe("GetAllLibraries", func() {
|
||||||
It("should return all libraries without filesystem permission", func() {
|
It("should return all libraries without filesystem permission", func() {
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, nil, true).(*libraryServiceImpl)
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, nil, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -130,8 +126,7 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return all libraries with filesystem permission", func() {
|
It("should return all libraries with filesystem permission", func() {
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: true}, nil, true).(*libraryServiceImpl)
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: true}, nil, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -152,10 +147,8 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Library Access Filtering", func() {
|
Describe("Library Access Filtering", func() {
|
||||||
It("should only return libraries in the allowed list", func() {
|
It("should only return libraries in the allowed list", func() { // Only allow library ID 2
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
||||||
// Only allow library ID 2
|
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -173,10 +166,8 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
Expect(results[0].Name).To(Equal("Jazz"))
|
Expect(results[0].Name).To(Equal("Jazz"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return error when getting a library not in the allowed list", func() {
|
It("should return error when getting a library not in the allowed list", func() { // Only allow library ID 2
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
||||||
// Only allow library ID 2
|
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -192,10 +183,8 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
Expect(err.Error()).To(ContainSubstring("not accessible"))
|
Expect(err.Error()).To(ContainSubstring("not accessible"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should allow access to a library in the allowed list", func() {
|
It("should allow access to a library in the allowed list", func() { // Only allow library ID 2
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
||||||
// Only allow library ID 2
|
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, []int{2}, false).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -211,10 +200,8 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
Expect(result.Name).To(Equal("Jazz"))
|
Expect(result.Name).To(Equal("Jazz"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return empty list when no libraries are allowed and allLibraries is false", func() {
|
It("should return empty list when no libraries are allowed and allLibraries is false", func() { // No libraries allowed
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, []int{}, false).(*libraryServiceImpl)
|
||||||
// No libraries allowed
|
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, []int{}, false).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
@ -229,10 +216,8 @@ var _ = Describe("LibraryService", Ordered, func() {
|
||||||
Expect(results).To(HaveLen(0))
|
Expect(results).To(HaveLen(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should return all libraries when allLibraries is true regardless of allowed list", func() {
|
It("should return all libraries when allLibraries is true regardless of allowed list", func() { // allLibraries=true should ignore the allowed list
|
||||||
reason := "test"
|
service = newLibraryService(ds, &LibraryPermission{Reason: new("test"), Filesystem: false}, []int{1}, true).(*libraryServiceImpl)
|
||||||
// allLibraries=true should ignore the allowed list
|
|
||||||
service = newLibraryService(ds, &LibraryPermission{Reason: &reason, Filesystem: false}, []int{1}, true).(*libraryServiceImpl)
|
|
||||||
|
|
||||||
libs := model.Libraries{
|
libs := model.Libraries{
|
||||||
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
{ID: 1, Name: "Rock", Path: "/music/rock", TotalSongs: 100},
|
||||||
|
|
|
||||||
|
|
@ -302,8 +302,7 @@ func (s *webSocketServiceImpl) readLoop(ctx context.Context, connectionID string
|
||||||
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
|
||||||
closeCode := websocket.CloseNoStatusReceived
|
closeCode := websocket.CloseNoStatusReceived
|
||||||
closeReason := ""
|
closeReason := ""
|
||||||
var ce *websocket.CloseError
|
if ce, ok := errors.AsType[*websocket.CloseError](err); ok {
|
||||||
if errors.As(err, &ce) {
|
|
||||||
closeCode = ce.Code
|
closeCode = ce.Code
|
||||||
closeReason = ce.Text
|
closeReason = ce.Text
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,10 @@ var _ = Describe("Manifest", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns true when threads feature has a reason", func() {
|
It("returns true when threads feature has a reason", func() {
|
||||||
reason := "Required for concurrent processing"
|
|
||||||
m := &Manifest{
|
m := &Manifest{
|
||||||
Experimental: &Experimental{
|
Experimental: &Experimental{
|
||||||
Threads: &ThreadsFeature{
|
Threads: &ThreadsFeature{
|
||||||
Reason: &reason,
|
Reason: new("Required for concurrent processing"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,12 +135,11 @@ var _ = Describe("ndpPackage", func() {
|
||||||
Describe("readManifest", func() {
|
Describe("readManifest", func() {
|
||||||
It("should read only the manifest without loading wasm", func() {
|
It("should read only the manifest without loading wasm", func() {
|
||||||
ndpPath := filepath.Join(tmpDir, "test.ndp")
|
ndpPath := filepath.Join(tmpDir, "test.ndp")
|
||||||
desc := "A test plugin"
|
|
||||||
manifest := &Manifest{
|
manifest := &Manifest{
|
||||||
Name: "Test Plugin",
|
Name: "Test Plugin",
|
||||||
Author: "Test Author",
|
Author: "Test Author",
|
||||||
Version: "1.0.0",
|
Version: "1.0.0",
|
||||||
Description: &desc,
|
Description: new("A test plugin"),
|
||||||
}
|
}
|
||||||
wasmBytes := make([]byte, 1024*1024) // 1MB of zeros
|
wasmBytes := make([]byte, 1024*1024) // 1MB of zeros
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/server/events"
|
"github.com/navidrome/navidrome/server/events"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
"github.com/navidrome/navidrome/utils/pl"
|
"github.com/navidrome/navidrome/utils/pl"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
@ -38,7 +37,7 @@ func New(rootCtx context.Context, ds model.DataStore, cw artwork.CacheWarmer, br
|
||||||
devExternalScanner: conf.Server.DevExternalScanner,
|
devExternalScanner: conf.Server.DevExternalScanner,
|
||||||
}
|
}
|
||||||
if !c.devExternalScanner {
|
if !c.devExternalScanner {
|
||||||
c.limiter = P(rate.Sometimes{Interval: conf.Server.DevActivityPanelUpdateRate})
|
c.limiter = new(rate.Sometimes{Interval: conf.Server.DevActivityPanelUpdateRate})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,7 @@ func (s *scannerExternal) scan(ctx context.Context, fullScan bool, targets []mod
|
||||||
|
|
||||||
func (s *scannerExternal) wait(cmd *exec.Cmd, out *io.PipeWriter) {
|
func (s *scannerExternal) wait(cmd *exec.Cmd, out *io.PipeWriter) {
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
var exitErr *exec.ExitError
|
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||||
if errors.As(err, &exitErr) {
|
|
||||||
_ = out.CloseWithError(fmt.Errorf("%s exited with non-zero status code: %w", cmd, exitErr))
|
_ = out.CloseWithError(fmt.Errorf("%s exited with non-zero status code: %w", cmd, exitErr))
|
||||||
} else {
|
} else {
|
||||||
_ = out.CloseWithError(fmt.Errorf("waiting %s cmd: %w", cmd, err))
|
_ = out.CloseWithError(fmt.Errorf("waiting %s cmd: %w", cmd, err))
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ func createAdmin(ds model.DataStore) func(w http.ResponseWriter, r *http.Request
|
||||||
|
|
||||||
func createAdminUser(ctx context.Context, ds model.DataStore, username, password string) error {
|
func createAdminUser(ctx context.Context, ds model.DataStore, username, password string) error {
|
||||||
log.Warn(ctx, "Creating initial user", "user", username)
|
log.Warn(ctx, "Creating initial user", "user", username)
|
||||||
now := time.Now()
|
|
||||||
caser := cases.Title(language.Und)
|
caser := cases.Title(language.Und)
|
||||||
initialUser := model.User{
|
initialUser := model.User{
|
||||||
ID: id.NewRandom(),
|
ID: id.NewRandom(),
|
||||||
|
|
@ -144,7 +143,7 @@ func createAdminUser(ctx context.Context, ds model.DataStore, username, password
|
||||||
Email: "",
|
Email: "",
|
||||||
NewPassword: password,
|
NewPassword: password,
|
||||||
IsAdmin: true,
|
IsAdmin: true,
|
||||||
LastLoginAt: &now,
|
LastLoginAt: new(time.Now()),
|
||||||
}
|
}
|
||||||
err := ds.User(ctx).Put(&initialUser)
|
err := ds.User(ctx).Put(&initialUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ func (api *Router) uploadArtistImage() http.HandlerFunc {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ar.UploadedImage = filename
|
ar.UploadedImage = filename
|
||||||
now := time.Now()
|
ar.UpdatedAt = new(time.Now())
|
||||||
ar.UpdatedAt = &now
|
|
||||||
return api.ds.Artist(ctx).Put(ar, "uploaded_image", "updated_at")
|
return api.ds.Artist(ctx).Put(ar, "uploaded_image", "updated_at")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -65,8 +64,7 @@ func (api *Router) deleteArtistImage() http.HandlerFunc {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ar.UploadedImage = ""
|
ar.UploadedImage = ""
|
||||||
now := time.Now()
|
ar.UpdatedAt = new(time.Now())
|
||||||
ar.UpdatedAt = &now
|
|
||||||
return api.ds.Artist(ctx).Put(ar, "uploaded_image", "updated_at")
|
return api.ds.Artist(ctx).Put(ar, "uploaded_image", "updated_at")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -32,7 +31,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
Describe("POST /queue", func() {
|
Describe("POST /queue", func() {
|
||||||
It("saves the queue", func() {
|
It("saves the queue", func() {
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(1), Position: gg.P(int64(10))}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"}), Current: new(1), Position: new(int64(10))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
||||||
ctx := request.WithUser(req.Context(), user)
|
ctx := request.WithUser(req.Context(), user)
|
||||||
|
|
@ -50,7 +49,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("saves an empty queue", func() {
|
It("saves an empty queue", func() {
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{}), Current: gg.P(0), Position: gg.P(int64(0))}
|
payload := updateQueuePayload{Ids: new([]string{}), Current: new(0), Position: new(int64(0))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -63,7 +62,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns bad request for invalid current index (negative)", func() {
|
It("returns bad request for invalid current index (negative)", func() {
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(-1), Position: gg.P(int64(10))}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"}), Current: new(-1), Position: new(int64(10))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -75,7 +74,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns bad request for invalid current index (too large)", func() {
|
It("returns bad request for invalid current index (too large)", func() {
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(2), Position: gg.P(int64(10))}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"}), Current: new(2), Position: new(int64(10))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -97,7 +96,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("returns internal server error when store fails", func() {
|
It("returns internal server error when store fails", func() {
|
||||||
repo.Err = true
|
repo.Err = true
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1"}), Current: gg.P(0), Position: gg.P(int64(10))}
|
payload := updateQueuePayload{Ids: new([]string{"s1"}), Current: new(0), Position: new(int64(10))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("POST", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -166,7 +165,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
Describe("PUT /queue", func() {
|
Describe("PUT /queue", func() {
|
||||||
It("updates the queue fields", func() {
|
It("updates the queue fields", func() {
|
||||||
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}, {ID: "s2"}, {ID: "s3"}}}
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}, {ID: "s2"}, {ID: "s3"}}}
|
||||||
payload := updateQueuePayload{Current: gg.P(2), Position: gg.P(int64(20))}
|
payload := updateQueuePayload{Current: new(2), Position: new(int64(20))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
ctx := request.WithUser(req.Context(), user)
|
ctx := request.WithUser(req.Context(), user)
|
||||||
|
|
@ -184,7 +183,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("updates only ids", func() {
|
It("updates only ids", func() {
|
||||||
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 1}
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 1}
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"})}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"})}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -198,7 +197,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("updates ids and current", func() {
|
It("updates ids and current", func() {
|
||||||
repo.Queue = &model.PlayQueue{UserID: user.ID}
|
repo.Queue = &model.PlayQueue{UserID: user.ID}
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"}), Current: gg.P(1)}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"}), Current: new(1)}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -213,7 +212,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("returns bad request when new ids invalidate current", func() {
|
It("returns bad request when new ids invalidate current", func() {
|
||||||
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 2}
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Current: 2}
|
||||||
payload := updateQueuePayload{Ids: gg.P([]string{"s1", "s2"})}
|
payload := updateQueuePayload{Ids: new([]string{"s1", "s2"})}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -225,7 +224,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("returns bad request when current out of bounds", func() {
|
It("returns bad request when current out of bounds", func() {
|
||||||
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}}}
|
repo.Queue = &model.PlayQueue{UserID: user.ID, Items: model.MediaFiles{{ID: "s1"}}}
|
||||||
payload := updateQueuePayload{Current: gg.P(3)}
|
payload := updateQueuePayload{Current: new(3)}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
@ -246,7 +245,7 @@ var _ = Describe("Queue Endpoints", func() {
|
||||||
|
|
||||||
It("returns internal server error when store fails", func() {
|
It("returns internal server error when store fails", func() {
|
||||||
repo.Err = true
|
repo.Err = true
|
||||||
payload := updateQueuePayload{Position: gg.P(int64(10))}
|
payload := updateQueuePayload{Position: new(int64(10))}
|
||||||
body, _ := json.Marshal(payload)
|
body, _ := json.Marshal(payload)
|
||||||
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
req := httptest.NewRequest("PUT", "/queue", bytes.NewReader(body))
|
||||||
req = req.WithContext(request.WithUser(req.Context(), user))
|
req = req.WithContext(request.WithUser(req.Context(), user))
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/core/stream"
|
"github.com/navidrome/navidrome/core/stream"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/tests"
|
"github.com/navidrome/navidrome/tests"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -89,7 +88,7 @@ var _ = Describe("encodeMediafileShare", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("includes the share ID in the token", func() {
|
It("includes the share ID in the token", func() {
|
||||||
exp := P(time.Now().Add(time.Hour))
|
exp := new(time.Now().Add(time.Hour))
|
||||||
s := model.Share{ID: "shareABC", Format: "mp3", MaxBitRate: 320, ExpiresAt: exp}
|
s := model.Share{ID: "shareABC", Format: "mp3", MaxBitRate: 320, ExpiresAt: exp}
|
||||||
token := encodeMediafileShare(s, "mf-999")
|
token := encodeMediafileShare(s, "mf-999")
|
||||||
info, err := decodeStreamInfo(token)
|
info, err := decodeStreamInfo(token)
|
||||||
|
|
@ -164,8 +163,7 @@ var _ = Describe("handleStream", func() {
|
||||||
|
|
||||||
It("returns 410 when share has been set to expired", func() {
|
It("returns 410 when share has been set to expired", func() {
|
||||||
shareRepo.ID = "share123"
|
shareRepo.ID = "share123"
|
||||||
expired := time.Now().Add(-time.Hour)
|
shareRepo.Entity = &model.Share{ID: "share123", ExpiresAt: new(time.Now().Add(-time.Hour))}
|
||||||
shareRepo.Entity = &model.Share{ID: "share123", ExpiresAt: &expired}
|
|
||||||
|
|
||||||
claims := auth.Claims{ID: "mf-123", ShareID: "share123"}
|
claims := auth.Claims{ID: "mf-123", ShareID: "share123"}
|
||||||
token, _ := auth.CreatePublicToken(claims)
|
token, _ := auth.CreatePublicToken(claims)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
@ -136,7 +135,7 @@ var _ = Describe("sendResponse", func() {
|
||||||
It("should return a fail response", func() {
|
It("should return a fail response", func() {
|
||||||
payload.Song = &responses.Child{OpenSubsonicChild: &responses.OpenSubsonicChild{}}
|
payload.Song = &responses.Child{OpenSubsonicChild: &responses.OpenSubsonicChild{}}
|
||||||
// An +Inf value will cause an error when marshalling to JSON
|
// An +Inf value will cause an error when marshalling to JSON
|
||||||
payload.Song.ReplayGain = responses.ReplayGain{TrackGain: gg.P(math.Inf(1))}
|
payload.Song.ReplayGain = responses.ReplayGain{TrackGain: new(math.Inf(1))}
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
q.Add("f", "json")
|
q.Add("f", "json")
|
||||||
r.URL.RawQuery = q.Encode()
|
r.URL.RawQuery = q.Encode()
|
||||||
|
|
|
||||||
|
|
@ -256,8 +256,7 @@ func (api *Router) GetSong(r *http.Request) (*responses.Subsonic, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
child := childFromMediaFile(ctx, *mf)
|
response.Song = new(childFromMediaFile(ctx, *mf))
|
||||||
response.Song = &child
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
"github.com/navidrome/navidrome/utils/number"
|
"github.com/navidrome/navidrome/utils/number"
|
||||||
"github.com/navidrome/navidrome/utils/req"
|
"github.com/navidrome/navidrome/utils/req"
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
|
|
@ -217,7 +216,7 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
|
||||||
child.Path = fakePath(mf)
|
child.Path = fakePath(mf)
|
||||||
}
|
}
|
||||||
child.DiscNumber = int32(mf.DiscNumber)
|
child.DiscNumber = int32(mf.DiscNumber)
|
||||||
child.Created = P(mf.BirthTime)
|
child.Created = new(mf.BirthTime)
|
||||||
child.AlbumId = mf.AlbumID
|
child.AlbumId = mf.AlbumID
|
||||||
child.ArtistId = mf.ArtistID
|
child.ArtistId = mf.ArtistID
|
||||||
child.Type = "music"
|
child.Type = "music"
|
||||||
|
|
@ -346,7 +345,7 @@ func childFromAlbum(ctx context.Context, al model.Album) responses.Child {
|
||||||
child.Year = int32(cmp.Or(al.MaxOriginalYear, al.MaxYear))
|
child.Year = int32(cmp.Or(al.MaxOriginalYear, al.MaxYear))
|
||||||
child.Genre = al.Genre
|
child.Genre = al.Genre
|
||||||
child.CoverArt = al.CoverArtID().String()
|
child.CoverArt = al.CoverArtID().String()
|
||||||
child.Created = P(albumCreatedAt(al))
|
child.Created = new(albumCreatedAt(al))
|
||||||
child.Parent = al.AlbumArtistID
|
child.Parent = al.AlbumArtistID
|
||||||
child.ArtistId = al.AlbumArtistID
|
child.ArtistId = al.AlbumArtistID
|
||||||
child.Duration = int32(al.Duration)
|
child.Duration = int32(al.Duration)
|
||||||
|
|
@ -442,7 +441,7 @@ func buildAlbumID3(ctx context.Context, album model.Album) responses.AlbumID3 {
|
||||||
dir.PlayCount = album.PlayCount
|
dir.PlayCount = album.PlayCount
|
||||||
dir.Year = int32(cmp.Or(album.MaxOriginalYear, album.MaxYear))
|
dir.Year = int32(cmp.Or(album.MaxOriginalYear, album.MaxYear))
|
||||||
dir.Genre = album.Genre
|
dir.Genre = album.Genre
|
||||||
dir.Created = P(albumCreatedAt(album))
|
dir.Created = new(albumCreatedAt(album))
|
||||||
if album.Starred {
|
if album.Starred {
|
||||||
dir.Starred = album.StarredAt
|
dir.Starred = album.StarredAt
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,6 @@ var _ = Describe("MediaRetrievalController", func() {
|
||||||
response, err := router.GetLyricsBySongId(r)
|
response, err := router.GetLyricsBySongId(r)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
offset := int64(-100)
|
|
||||||
compareResponses(response.LyricsList, responses.LyricsList{
|
compareResponses(response.LyricsList, responses.LyricsList{
|
||||||
StructuredLyrics: responses.StructuredLyrics{
|
StructuredLyrics: responses.StructuredLyrics{
|
||||||
{
|
{
|
||||||
|
|
@ -312,7 +311,7 @@ var _ = Describe("MediaRetrievalController", func() {
|
||||||
Value: "You know the rules and so do I",
|
Value: "You know the rules and so do I",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Offset: &offset,
|
Offset: new(int64(-100)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/model/request"
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
"github.com/navidrome/navidrome/utils/req"
|
"github.com/navidrome/navidrome/utils/req"
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
)
|
)
|
||||||
|
|
@ -169,7 +168,7 @@ func buildOSPlaylist(ctx context.Context, p model.Playlist) *responses.OpenSubso
|
||||||
pls.Readonly = true
|
pls.Readonly = true
|
||||||
|
|
||||||
if p.EvaluatedAt != nil {
|
if p.EvaluatedAt != nil {
|
||||||
pls.ValidUntil = P(p.EvaluatedAt.Add(conf.Server.SmartPlaylistRefreshDelay))
|
pls.ValidUntil = new(p.EvaluatedAt.Add(conf.Server.SmartPlaylistRefreshDelay))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user, ok := request.UserFrom(ctx)
|
user, ok := request.UserFrom(ctx)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/navidrome/navidrome/consts"
|
"github.com/navidrome/navidrome/consts"
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
. "github.com/navidrome/navidrome/server/subsonic/responses"
|
. "github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -94,11 +93,10 @@ var _ = Describe("Responses", func() {
|
||||||
Context("with data", func() {
|
Context("with data", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
artists := make([]Artist, 1)
|
artists := make([]Artist, 1)
|
||||||
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
|
|
||||||
artists[0] = Artist{
|
artists[0] = Artist{
|
||||||
Id: "111",
|
Id: "111",
|
||||||
Name: "aaa",
|
Name: "aaa",
|
||||||
Starred: &t,
|
Starred: new(time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)),
|
||||||
UserRating: 3,
|
UserRating: 3,
|
||||||
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
||||||
}
|
}
|
||||||
|
|
@ -133,11 +131,10 @@ var _ = Describe("Responses", func() {
|
||||||
Context("with data", func() {
|
Context("with data", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
artists := make([]ArtistID3, 1)
|
artists := make([]ArtistID3, 1)
|
||||||
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
|
|
||||||
artists[0] = ArtistID3{
|
artists[0] = ArtistID3{
|
||||||
Id: "111",
|
Id: "111",
|
||||||
Name: "aaa",
|
Name: "aaa",
|
||||||
Starred: &t,
|
Starred: new(time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)),
|
||||||
UserRating: 3,
|
UserRating: 3,
|
||||||
AlbumCount: 2,
|
AlbumCount: 2,
|
||||||
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
||||||
|
|
@ -158,11 +155,10 @@ var _ = Describe("Responses", func() {
|
||||||
Context("with OpenSubsonic data", func() {
|
Context("with OpenSubsonic data", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
artists := make([]ArtistID3, 1)
|
artists := make([]ArtistID3, 1)
|
||||||
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
|
|
||||||
artists[0] = ArtistID3{
|
artists[0] = ArtistID3{
|
||||||
Id: "111",
|
Id: "111",
|
||||||
Name: "aaa",
|
Name: "aaa",
|
||||||
Starred: &t,
|
Starred: new(time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)),
|
||||||
UserRating: 3,
|
UserRating: 3,
|
||||||
AlbumCount: 2,
|
AlbumCount: 2,
|
||||||
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
ArtistImageUrl: "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png",
|
||||||
|
|
@ -211,12 +207,11 @@ var _ = Describe("Responses", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
response.Directory = &Directory{Id: "1", Name: "N"}
|
response.Directory = &Directory{Id: "1", Name: "N"}
|
||||||
child := make([]Child, 2)
|
child := make([]Child, 2)
|
||||||
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
|
|
||||||
child[0] = Child{
|
child[0] = Child{
|
||||||
Id: "1", IsDir: true, Title: "title", Album: "album", Artist: "artist", Track: 1,
|
Id: "1", IsDir: true, Title: "title", Album: "album", Artist: "artist", Track: 1,
|
||||||
Year: 1985, Genre: "Rock", CoverArt: "1", Size: 8421341, ContentType: "audio/flac",
|
Year: 1985, Genre: "Rock", CoverArt: "1", Size: 8421341, ContentType: "audio/flac",
|
||||||
Suffix: "flac", TranscodedContentType: "audio/mpeg", TranscodedSuffix: "mp3",
|
Suffix: "flac", TranscodedContentType: "audio/mpeg", TranscodedSuffix: "mp3",
|
||||||
Duration: 146, BitRate: 320, Starred: &t,
|
Duration: 146, BitRate: 320, Starred: new(time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)),
|
||||||
}
|
}
|
||||||
child[0].OpenSubsonicChild = &OpenSubsonicChild{
|
child[0].OpenSubsonicChild = &OpenSubsonicChild{
|
||||||
Genres: []ItemGenre{{Name: "rock"}, {Name: "progressive"}},
|
Genres: []ItemGenre{{Name: "rock"}, {Name: "progressive"}},
|
||||||
|
|
@ -225,7 +220,7 @@ var _ = Describe("Responses", func() {
|
||||||
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
|
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
|
||||||
Moods: []string{"happy", "sad"},
|
Moods: []string{"happy", "sad"},
|
||||||
Groupings: []string{"Soundtrack", "Live"},
|
Groupings: []string{"Soundtrack", "Live"},
|
||||||
ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)},
|
ReplayGain: ReplayGain{TrackGain: new(1.0), AlbumGain: new(2.0), TrackPeak: new(3.0), AlbumPeak: new(4.0), BaseGain: new(5.0), FallbackGain: new(6.0)},
|
||||||
DisplayArtist: "artist 1 & artist 2",
|
DisplayArtist: "artist 1 & artist 2",
|
||||||
Artists: []ArtistID3Ref{
|
Artists: []ArtistID3Ref{
|
||||||
{Id: "1", Name: "artist1"},
|
{Id: "1", Name: "artist1"},
|
||||||
|
|
@ -246,7 +241,7 @@ var _ = Describe("Responses", func() {
|
||||||
ExplicitStatus: "clean",
|
ExplicitStatus: "clean",
|
||||||
}
|
}
|
||||||
child[1].OpenSubsonicChild = &OpenSubsonicChild{
|
child[1].OpenSubsonicChild = &OpenSubsonicChild{
|
||||||
ReplayGain: ReplayGain{TrackGain: gg.P(0.0), AlbumGain: gg.P(0.0), TrackPeak: gg.P(0.0), AlbumPeak: gg.P(0.0), BaseGain: gg.P(0.0), FallbackGain: gg.P(0.0)},
|
ReplayGain: ReplayGain{TrackGain: new(0.0), AlbumGain: new(0.0), TrackPeak: new(0.0), AlbumPeak: new(0.0), BaseGain: new(0.0), FallbackGain: new(0.0)},
|
||||||
}
|
}
|
||||||
response.Directory.Child = child
|
response.Directory.Child = child
|
||||||
})
|
})
|
||||||
|
|
@ -322,7 +317,7 @@ var _ = Describe("Responses", func() {
|
||||||
Isrc: []string{"ISRC-1"},
|
Isrc: []string{"ISRC-1"},
|
||||||
Moods: []string{"happy", "sad"},
|
Moods: []string{"happy", "sad"},
|
||||||
Groupings: []string{"Soundtrack", "Live"},
|
Groupings: []string{"Soundtrack", "Live"},
|
||||||
ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)},
|
ReplayGain: ReplayGain{TrackGain: new(1.0), AlbumGain: new(2.0), TrackPeak: new(3.0), AlbumPeak: new(4.0), BaseGain: new(5.0), FallbackGain: new(6.0)},
|
||||||
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
|
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
|
||||||
DisplayArtist: "artist1 & artist2",
|
DisplayArtist: "artist1 & artist2",
|
||||||
Artists: []ArtistID3Ref{
|
Artists: []ArtistID3Ref{
|
||||||
|
|
@ -342,7 +337,7 @@ var _ = Describe("Responses", func() {
|
||||||
ExplicitStatus: "clean",
|
ExplicitStatus: "clean",
|
||||||
}
|
}
|
||||||
songs[1].OpenSubsonicChild = &OpenSubsonicChild{
|
songs[1].OpenSubsonicChild = &OpenSubsonicChild{
|
||||||
ReplayGain: ReplayGain{TrackGain: gg.P(0.0), AlbumGain: gg.P(0.0), TrackPeak: gg.P(0.0), AlbumPeak: gg.P(0.0), BaseGain: gg.P(0.0), FallbackGain: gg.P(0.0)},
|
ReplayGain: ReplayGain{TrackGain: new(0.0), AlbumGain: new(0.0), TrackPeak: new(0.0), AlbumPeak: new(0.0), BaseGain: new(0.0), FallbackGain: new(0.0)},
|
||||||
}
|
}
|
||||||
response.AlbumWithSongsID3.AlbumID3 = album
|
response.AlbumWithSongsID3.AlbumID3 = album
|
||||||
response.AlbumWithSongsID3.Song = songs
|
response.AlbumWithSongsID3.Song = songs
|
||||||
|
|
@ -804,7 +799,7 @@ var _ = Describe("Responses", func() {
|
||||||
Context("with data", func() {
|
Context("with data", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
response.PlayQueueByIndex.Username = "user1"
|
response.PlayQueueByIndex.Username = "user1"
|
||||||
response.PlayQueueByIndex.CurrentIndex = gg.P(0)
|
response.PlayQueueByIndex.CurrentIndex = new(0)
|
||||||
response.PlayQueueByIndex.Position = 243
|
response.PlayQueueByIndex.Position = 243
|
||||||
response.PlayQueueByIndex.Changed = time.Time{}
|
response.PlayQueueByIndex.Changed = time.Time{}
|
||||||
response.PlayQueueByIndex.ChangedBy = "a_client"
|
response.PlayQueueByIndex.ChangedBy = "a_client"
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,10 @@ func (api *Router) CreateShare(r *http.Request) (*responses.Subsonic, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
description, _ := p.String("description")
|
description, _ := p.String("description")
|
||||||
expires := p.TimeOr("expires", time.Time{})
|
|
||||||
|
|
||||||
repo := api.share.NewRepository(r.Context())
|
repo := api.share.NewRepository(r.Context())
|
||||||
share := &model.Share{
|
share := &model.Share{
|
||||||
Description: description,
|
Description: description,
|
||||||
ExpiresAt: &expires,
|
ExpiresAt: new(p.TimeOr("expires", time.Time{})),
|
||||||
ResourceIDs: strings.Join(ids, ","),
|
ResourceIDs: strings.Join(ids, ","),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,13 +88,11 @@ func (api *Router) UpdateShare(r *http.Request) (*responses.Subsonic, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
description, _ := p.String("description")
|
description, _ := p.String("description")
|
||||||
expires := p.TimeOr("expires", time.Time{})
|
|
||||||
|
|
||||||
repo := api.share.NewRepository(r.Context())
|
repo := api.share.NewRepository(r.Context())
|
||||||
share := &model.Share{
|
share := &model.Share{
|
||||||
ID: id,
|
ID: id,
|
||||||
Description: description,
|
Description: description,
|
||||||
ExpiresAt: &expires,
|
ExpiresAt: new(p.TimeOr("expires", time.Time{})),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.(rest.Persistable).Update(id, share)
|
err = repo.(rest.Persistable).Update(id, share)
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,7 @@ func (api *Router) GetUser(r *http.Request) (*responses.Subsonic, error) {
|
||||||
return nil, newError(responses.ErrorAuthorizationFail)
|
return nil, newError(responses.ErrorAuthorizationFail)
|
||||||
}
|
}
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
user := buildUserResponse(loggedUser)
|
response.User = new(buildUserResponse(loggedUser))
|
||||||
response.User = &user
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
"github.com/navidrome/navidrome/utils/gg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateMockUserRepo() *MockedUserRepo {
|
func CreateMockUserRepo() *MockedUserRepo {
|
||||||
|
|
@ -84,7 +83,7 @@ func (u *MockedUserRepo) GetAll(options ...model.QueryOptions) (model.Users, err
|
||||||
func (u *MockedUserRepo) UpdateLastLoginAt(id string) error {
|
func (u *MockedUserRepo) UpdateLastLoginAt(id string) error {
|
||||||
for _, usr := range u.Data {
|
for _, usr := range u.Data {
|
||||||
if usr.ID == id {
|
if usr.ID == id {
|
||||||
usr.LastLoginAt = gg.P(time.Now())
|
usr.LastLoginAt = new(time.Now())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +93,7 @@ func (u *MockedUserRepo) UpdateLastLoginAt(id string) error {
|
||||||
func (u *MockedUserRepo) UpdateLastAccessAt(id string) error {
|
func (u *MockedUserRepo) UpdateLastAccessAt(id string) error {
|
||||||
for _, usr := range u.Data {
|
for _, usr := range u.Data {
|
||||||
if usr.ID == id {
|
if usr.ID == id {
|
||||||
usr.LastAccessAt = gg.P(time.Now())
|
usr.LastAccessAt = new(time.Now())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
utils/cache/cached_http_client.go
vendored
3
utils/cache/cached_http_client.go
vendored
|
|
@ -75,8 +75,7 @@ func (c *HTTPClient) serializeReq(req *http.Request) string {
|
||||||
}
|
}
|
||||||
if req.Body != nil {
|
if req.Body != nil {
|
||||||
bodyData, _ := io.ReadAll(req.Body)
|
bodyData, _ := io.ReadAll(req.Body)
|
||||||
bodyStr := base64.StdEncoding.EncodeToString(bodyData)
|
data.Body = new(base64.StdEncoding.EncodeToString(bodyData))
|
||||||
data.Body = &bodyStr
|
|
||||||
}
|
}
|
||||||
j, _ := json.Marshal(&data)
|
j, _ := json.Marshal(&data)
|
||||||
return string(j)
|
return string(j)
|
||||||
|
|
|
||||||
3
utils/cache/simple_cache.go
vendored
3
utils/cache/simple_cache.go
vendored
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jellydator/ttlcache/v3"
|
"github.com/jellydator/ttlcache/v3"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SimpleCache[K comparable, V any] interface {
|
type SimpleCache[K comparable, V any] interface {
|
||||||
|
|
@ -119,7 +118,7 @@ func (c *simpleCache[K, V]) GetWithLoader(key K, loader func(key K) (V, time.Dur
|
||||||
func (c *simpleCache[K, V]) evictExpired() {
|
func (c *simpleCache[K, V]) evictExpired() {
|
||||||
if c.evictionDeadline.Load() == nil || c.evictionDeadline.Load().Before(time.Now()) {
|
if c.evictionDeadline.Load() == nil || c.evictionDeadline.Load().Before(time.Now()) {
|
||||||
c.data.DeleteExpired()
|
c.data.DeleteExpired()
|
||||||
c.evictionDeadline.Store(P(time.Now().Add(evictionTimeout)))
|
c.evictionDeadline.Store(new(time.Now().Add(evictionTimeout)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package chrono
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Meter is a simple stopwatch
|
// Meter is a simple stopwatch
|
||||||
|
|
@ -13,7 +11,7 @@ type Meter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Meter) Start() {
|
func (m *Meter) Start() {
|
||||||
m.mark = P(time.Now())
|
m.mark = new(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Meter) Stop() time.Duration {
|
func (m *Meter) Stop() time.Duration {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
// Package gg implements simple "extensions" to Go language. Based on https://github.com/icza/gog
|
// Package gg implements simple "extensions" to Go language. Based on https://github.com/icza/gog
|
||||||
package gg
|
package gg
|
||||||
|
|
||||||
// P returns a pointer to the input value
|
|
||||||
func P[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// V returns the value of the input pointer, or a zero value if the input pointer is nil.
|
// V returns the value of the input pointer, or a zero value if the input pointer is nil.
|
||||||
func V[T any](p *T) T {
|
func V[T any](p *T) T {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,9 @@ func TestGG(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("GG", func() {
|
var _ = Describe("GG", func() {
|
||||||
Describe("P", func() {
|
|
||||||
It("returns a pointer to the input value", func() {
|
|
||||||
v := 123
|
|
||||||
Expect(gg.P(123)).To(Equal(&v))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("returns nil if the input value is zero", func() {
|
|
||||||
v := 0
|
|
||||||
Expect(gg.P(0)).To(Equal(&v))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("V", func() {
|
Describe("V", func() {
|
||||||
It("returns the value of the input pointer", func() {
|
It("returns the value of the input pointer", func() {
|
||||||
v := 123
|
Expect(gg.V(new(123))).To(Equal(123))
|
||||||
Expect(gg.V(&v)).To(Equal(123))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns a zero value if the input pointer is nil", func() {
|
It("returns a zero value if the input pointer is nil", func() {
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,7 @@ func (r *Values) String(param string) (string, error) {
|
||||||
func (r *Values) StringPtr(param string) *string {
|
func (r *Values) StringPtr(param string) *string {
|
||||||
var v *string
|
var v *string
|
||||||
if _, exists := r.URL.Query()[param]; exists {
|
if _, exists := r.URL.Query()[param]; exists {
|
||||||
s := r.URL.Query().Get(param)
|
v = new(r.URL.Query().Get(param))
|
||||||
v = &s
|
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
@ -48,8 +47,7 @@ func (r *Values) BoolPtr(param string) *bool {
|
||||||
var v *bool
|
var v *bool
|
||||||
if _, exists := r.URL.Query()[param]; exists {
|
if _, exists := r.URL.Query()[param]; exists {
|
||||||
s := r.URL.Query().Get(param)
|
s := r.URL.Query().Get(param)
|
||||||
b := strings.Contains("/true/on/1/", "/"+strings.ToLower(s)+"/")
|
v = new(strings.Contains("/true/on/1/", "/"+strings.ToLower(s)+"/"))
|
||||||
v = &b
|
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue