refactor: multiple syntax updates for Go 1.26

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2026-05-19 18:02:29 -03:00
parent 545a9ecc3c
commit efe9291db0
51 changed files with 143 additions and 251 deletions

3
.gitignore vendored
View file

@ -37,5 +37,6 @@ AGENTS.md
*.wasm *.wasm
*.ndp *.ndp
openspec/ openspec/
.agents
go.work* go.work*
.worktrees/ .worktrees/

View file

@ -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,

View file

@ -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,

View file

@ -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")

View file

@ -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})

View file

@ -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"},

View file

@ -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

View file

@ -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

View file

@ -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,
}, },
} }

View file

@ -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"))
}) })
}) })

View file

@ -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))

View file

@ -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)
} }

View file

@ -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())
}) })
}) })

View file

@ -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())

View file

@ -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
} }

View file

@ -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]

View file

@ -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))

View file

@ -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"},
})) }))
}) })
}) })

View file

@ -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},
} }

View file

@ -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),
) )

View file

@ -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

View file

@ -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)),
} }
} }

View file

@ -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)
} }

View file

@ -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 {

View file

@ -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)
} }

View file

@ -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

View file

@ -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},

View file

@ -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
} }

View file

@ -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"),
}, },
}, },
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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))

View file

@ -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 {

View file

@ -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")
}) })
} }

View file

@ -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))

View file

@ -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)

View file

@ -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()

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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)),
}, },
}, },
}) })

View file

@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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
} }

View file

@ -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
} }
} }

View file

@ -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)

View file

@ -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)))
} }
} }

View file

@ -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 {

View file

@ -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 {

View file

@ -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() {

View file

@ -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
} }