diff --git a/mocks/radio.gen.go b/mocks/radio.gen.go index 8d47f89..74000d2 100644 --- a/mocks/radio.gen.go +++ b/mocks/radio.gen.go @@ -3878,7 +3878,7 @@ var _ radio.TrackStorage = &TrackStorageMock{} // InsertFunc: func(song radio.Song) (radio.TrackID, error) { // panic("mock out the Insert method") // }, -// NeedReplacementFunc: func() ([]radio.TrackID, error) { +// NeedReplacementFunc: func() ([]radio.Song, error) { // panic("mock out the NeedReplacement method") // }, // QueueCandidatesFunc: func() ([]radio.TrackID, error) { @@ -3928,7 +3928,7 @@ type TrackStorageMock struct { InsertFunc func(song radio.Song) (radio.TrackID, error) // NeedReplacementFunc mocks the NeedReplacement method. - NeedReplacementFunc func() ([]radio.TrackID, error) + NeedReplacementFunc func() ([]radio.Song, error) // QueueCandidatesFunc mocks the QueueCandidates method. QueueCandidatesFunc func() ([]radio.TrackID, error) @@ -4222,7 +4222,7 @@ func (mock *TrackStorageMock) InsertCalls() []struct { } // NeedReplacement calls NeedReplacementFunc. -func (mock *TrackStorageMock) NeedReplacement() ([]radio.TrackID, error) { +func (mock *TrackStorageMock) NeedReplacement() ([]radio.Song, error) { if mock.NeedReplacementFunc == nil { panic("TrackStorageMock.NeedReplacementFunc: method is nil but TrackStorage.NeedReplacement was just called") } diff --git a/radio.go b/radio.go index 8b2bd50..071b4d1 100644 --- a/radio.go +++ b/radio.go @@ -854,8 +854,8 @@ type TrackStorage interface { Delete(TrackID) error // Unusable returns all tracks that are deemed unusable by the streamer Unusable() ([]Song, error) - // NeedReplacement returns the IDs that need a replacement - NeedReplacement() ([]TrackID, error) + // NeedReplacement returns the song that need a replacement + NeedReplacement() ([]Song, error) // Insert inserts a new track, errors if ID or TrackID is set Insert(song Song) (TrackID, error) // UpdateMetadata updates track metadata only (artist/title/album/tags/filepath/needreplacement) diff --git a/search/storage.go b/search/storage.go index 7223afe..ac6a461 100644 --- a/search/storage.go +++ b/search/storage.go @@ -57,7 +57,7 @@ type partialTrackStorage interface { Get(radio.TrackID) (*radio.Song, error) All() ([]radio.Song, error) Unusable() ([]radio.Song, error) - NeedReplacement() ([]radio.TrackID, error) + NeedReplacement() ([]radio.Song, error) BeforeLastRequested(before time.Time) ([]radio.Song, error) QueueCandidates() ([]radio.TrackID, error) } diff --git a/storage/mariadb/status.go b/storage/mariadb/status.go index a6eb9a0..da21df8 100644 --- a/storage/mariadb/status.go +++ b/storage/mariadb/status.go @@ -31,7 +31,6 @@ func (ss StatusStorage) Store(status radio.Status) error { end_time, trackid, thread, - requesting, djname ) VALUES ( 1, @@ -43,7 +42,6 @@ func (ss StatusStorage) Store(status radio.Status) error { UNIX_TIMESTAMP(:songinfo.end), :song.trackid, :thread, - :requestsenabled, :streamername ) ON DUPLICATE KEY UPDATE djid=:user.dj.id, @@ -54,7 +52,6 @@ func (ss StatusStorage) Store(status radio.Status) error { end_time=UNIX_TIMESTAMP(:songinfo.end), trackid=:song.trackid, thread=:thread, - requesting=:requestsenabled, djname=:streamername, lastset=NOW(); ` @@ -81,7 +78,6 @@ func (ss StatusStorage) Load() (*radio.Status, error) { from_unixtime(end_time) AS 'songinfo.end', trackid AS 'song.trackid', thread, - IF(requesting = 1, 'true', 'false') AS requestsenabled, djname AS streamername FROM streamstatus diff --git a/storage/mariadb/track.go b/storage/mariadb/track.go index 4284f76..22e4cea 100644 --- a/storage/mariadb/track.go +++ b/storage/mariadb/track.go @@ -618,21 +618,26 @@ type TrackStorage struct { handle handle } -var trackNeedReplacementQuery = ` +var trackNeedReplacementQuery = expand(` SELECT - tracks.id AS trackid + {trackColumns}, + {maybeSongColumns}, + {lastplayedSelect}, + NOW() AS synctime FROM tracks +LEFT JOIN + esong ON esong.hash = tracks.hash WHERE tracks.need_reupload = 1; -` +`) -func (ts TrackStorage) NeedReplacement() ([]radio.TrackID, error) { +func (ts TrackStorage) NeedReplacement() ([]radio.Song, error) { const op errors.Op = "mariadb/TrackStorage.NeedReplacement" handle, deferFn := ts.handle.span(op) defer deferFn() - var songs []radio.TrackID + var songs []radio.Song err := sqlx.Select(handle, &songs, trackNeedReplacementQuery) if err != nil { diff --git a/storage/test/track.go b/storage/test/track.go index 5ccf6c5..997dc9f 100644 --- a/storage/test/track.go +++ b/storage/test/track.go @@ -1,12 +1,16 @@ package storagetest import ( + "reflect" "slices" "strconv" "testing" "time" radio "github.com/R-a-dio/valkyrie" + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/arbitrary" + "github.com/leanovate/gopter/gen" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -237,3 +241,70 @@ func (suite *Suite) TestSongFavoritesOf(t *testing.T) { require.NoError(t, err) require.Len(t, db, 0) } + +func (suite *Suite) TestTrackNeedReplacement(t *testing.T) { + s := suite.Storage(t) + ts := s.Track(suite.ctx) + + var entries []radio.Song + for i := 0; i < 50; i++ { + song := generateTrack() + song.NeedReplacement = i%2 == 0 + tid, err := ts.Insert(song) + require.NoError(t, err) + song.TrackID = tid + entries = append(entries, song) + } + + ret, err := ts.NeedReplacement() + require.NoError(t, err) + + assert.Len(t, ret, 25) + for _, song := range ret { + assert.True(t, song.NeedReplacement, "should have NeedReplacement set") + exists := slices.ContainsFunc(entries, func(a radio.Song) bool { + return a.EqualTo(song) + }) + assert.True(t, exists, "song should be one we inserted") + } +} + +func generateTrack() radio.Song { + a := arbitrary.DefaultArbitraries() + generator := gen.Struct(reflect.TypeFor[radio.Song](), map[string]gopter.Gen{ + "ID": genForType[radio.SongID](a), + "Length": genDuration(), + "LastPlayed": genTime(), + "LastPlayedBy": gen.PtrOf(genUser()), + "DatabaseTrack": gen.StructPtr(reflect.TypeFor[radio.DatabaseTrack](), map[string]gopter.Gen{ + "Artist": gen.AlphaString(), + "Title": gen.AlphaString(), + "Album": gen.AlphaString(), + "Tags": gen.AlphaString(), + "FilePath": gen.AlphaString(), + }), + }) + + song := OneOff[radio.Song](generator) + song.Hydrate() + return song +} + +func genForType[T any](a *arbitrary.Arbitraries) gopter.Gen { + return a.GenForType(reflect.TypeFor[T]()) +} + +func genDuration() gopter.Gen { + g := gen.Int64Range(0, int64(time.Hour*24)) + return genAsType[int64, time.Duration](g) +} + +func genAsType[F, T any](g gopter.Gen) gopter.Gen { + g = g.WithShrinker(nil) + return gopter.Gen(func(gp *gopter.GenParameters) *gopter.GenResult { + res := g(gp) + v := res.Result.(F) + vt := reflect.ValueOf(v).Convert(reflect.TypeFor[T]()).Interface() + return gopter.NewGenResult(vt, nil) + }).WithShrinker(nil) +} diff --git a/website/public/submit.go b/website/public/submit.go index e7d97d7..aa5533f 100644 --- a/website/public/submit.go +++ b/website/public/submit.go @@ -273,11 +273,11 @@ type SubmissionForm struct { // "cooldown": indicates the user was not permitted to upload yet, they need to wait longer Errors map[string]string // form fields - OriginalFilename string // name="track" The filename of the uploaded file - Daypass string // name="daypass" - Comment string // name="comment" - Replacement *radio.TrackID // name="replacement" - NeedReplacementList []radio.TrackID // possible values for Replacement field + OriginalFilename string // name="track" The filename of the uploaded file + Daypass string // name="daypass" + Comment string // name="comment" + Replacement *radio.TrackID // name="replacement" + NeedReplacementList []radio.Song // possible songs for Replacement field // after processing fields diff --git a/website/public/submit_test.go b/website/public/submit_test.go index dceea90..8930bcd 100644 --- a/website/public/submit_test.go +++ b/website/public/submit_test.go @@ -46,7 +46,7 @@ func TestPostSubmit(t *testing.T) { } storage.TrackFunc = func(contextMoqParam context.Context) radio.TrackStorage { return &mocks.TrackStorageMock{ - NeedReplacementFunc: func() ([]radio.TrackID, error) { + NeedReplacementFunc: func() ([]radio.Song, error) { return nil, nil }, }