diff --git a/ircbot/main.go b/ircbot/main.go index 5be72ef3..3c08daa5 100644 --- a/ircbot/main.go +++ b/ircbot/main.go @@ -201,12 +201,25 @@ func WaitForStatus(ctx context.Context, manager radio.ManagerService, announce r close(noRetry) var retry <-chan time.Time = noRetry + var lastSong radio.Song + for { + // if we lost connection or are just starting out we retry the connection + // only way to exit this loop is by the context being canceled + select { + case <-ctx.Done(): + return errors.E(op, ctx.Err()) + case <-retry: + } + + // connect to the status stream stream, err := manager.CurrentStatus(ctx) if err != nil { - return errors.E(op, err) + zerolog.Ctx(ctx).Error().Err(err).Msg("failed to connect to manager") + // if it fails we retry in a short period + retry = time.After(time.Second * 5) + continue } - defer stream.Close() for { select { @@ -218,15 +231,25 @@ func WaitForStatus(ctx context.Context, manager radio.ManagerService, announce r status, err := stream.Next() if err != nil { + // stream error means we have to get a new stream and should + // break out of this inner loop retry = time.After(time.Second * 5) - continue + break } - err = announce.AnnounceSong(ctx, status) - if err != nil { - retry = time.After(time.Second * 5) - continue + // if song is different from last we announce a Now Starting + if !lastSong.EqualTo(status.Song) { + err = announce.AnnounceSong(ctx, status) + if err != nil { + zerolog.Ctx(ctx).Error().Err(err).Msg("failed to announce song") + } else { + lastSong = status.Song + } } } + + // if we leave the inner loop it means our stream broke so we're getting a new one + // soon, clean up this current one + stream.Close() } } diff --git a/manager/main.go b/manager/main.go index 5a449b62..70625477 100644 --- a/manager/main.go +++ b/manager/main.go @@ -162,8 +162,8 @@ func (m *Manager) loadStreamStatus(ctx context.Context) (*radio.Status, error) { status.Song = *song } } - if status.StreamerName != "" { - user, err := m.Storage.User(ctx).LookupName(status.StreamerName) + if status.User.DJ.ID != 0 { + user, err := m.Storage.User(ctx).GetByDJID(status.User.DJ.ID) if err != nil { m.logger.Warn().Err(err).Msg("retrieving database user") } else { diff --git a/radio.go b/radio.go index 7dc3fd38..6e313383 100644 --- a/radio.go +++ b/radio.go @@ -67,6 +67,18 @@ type Status struct { RequestsEnabled bool } +func (s *Status) IsZero() bool { + ok := s.User.ID == 0 && + s.Song.ID == 0 && + s.SongInfo == (SongInfo{}) && + s.StreamerName == "" && + s.Listeners == 0 && + s.Thread == "" && + (!s.Song.HasTrack() || s.Song.TrackID == 0) + + return ok +} + // Copy makes a deep-copy of the status object func (s Status) Copy() Status { c := s diff --git a/radio_test.go b/radio_test.go index 0d9c74af..ad86ee27 100644 --- a/radio_test.go +++ b/radio_test.go @@ -75,6 +75,13 @@ func TestSongRequestable(t *testing.T) { p.TestingRun(t) } +func TestStatusIsZero(t *testing.T) { + var status Status + assert.True(t, status.IsZero()) + status.Song.DatabaseTrack = &DatabaseTrack{} + assert.True(t, status.IsZero()) +} + func TestCalculateCooldown(t *testing.T) { now := time.Now() diff --git a/storage/mariadb/search_test.go b/storage/mariadb/search_test.go index 152bb294..0e2ac422 100644 --- a/storage/mariadb/search_test.go +++ b/storage/mariadb/search_test.go @@ -12,6 +12,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type splitCase struct { @@ -85,9 +86,7 @@ func FuzzProcessQuery(f *testing.F) { setup := new(MariaDBSetup) s, err := setup.Setup(setupCtx) - if err != nil { - assert.NoError(f, err) - } + require.NoError(f, err) defer setup.TearDown(ctx) ss := s.(interface { diff --git a/storage/mariadb/status.go b/storage/mariadb/status.go index 6d7bc380..503178e5 100644 --- a/storage/mariadb/status.go +++ b/storage/mariadb/status.go @@ -16,72 +16,51 @@ type StatusStorage struct { func (ss StatusStorage) Store(status radio.Status) error { const op errors.Op = "mariadb/StatusStorage.Store" - // TODO(wessie): use INSERT INTO ... ON DUPLICATE UPDATE maybe? - // we either want to do an UPDATE or an INSERT if our row doesn't exist - var queries = []string{` - UPDATE - streamstatus - SET - djid=:user.dj.id, - np=:song.metadata, - listeners=:listeners, - bitrate=192000, - isafkstream=0, - isstreamdesk=0, - start_time=UNIX_TIMESTAMP(:songinfo.start), - end_time=UNIX_TIMESTAMP(:songinfo.end), - trackid=:song.trackid, - thread=:thread, - requesting=:requestsenabled, - djname=:streamername, - lastset=NOW() - WHERE - id=0; - `, ` - INSERT INTO + var query = ` + INSERT INTO streamstatus - ( - id, - djid, - np, - listeners, - isafkstream, - start_time, - end_time, - trackid, - thread, - requesting, - djname - ) VALUES ( - 0, - :user.dj.id, - :song.metadata, - :listeners, - 0, - UNIX_TIMESTAMP(:songinfo.start), - UNIX_TIMESTAMP(:songinfo.end), - :song.trackid, - :thread, - :requestsenabled, - :streamername - ); - `} - - // now try the UPDATE and if that fails do the same with an INSERT - for _, query := range queries { - res, err := sqlx.NamedExec(ss.handle, query, status) - if err != nil { - return errors.E(op, err) - } + ( + id, + djid, + np, + listeners, + isafkstream, + start_time, + end_time, + trackid, + thread, + requesting, + djname + ) VALUES ( + 1, + :user.dj.id, + :song.metadata, + :listeners, + 0, + UNIX_TIMESTAMP(:songinfo.start), + UNIX_TIMESTAMP(:songinfo.end), + :song.trackid, + :thread, + :requestsenabled, + :streamername + ) ON DUPLICATE KEY UPDATE + djid=:user.dj.id, + np=:song.metadata, + listeners=:listeners, + isafkstream=0, + start_time=UNIX_TIMESTAMP(:songinfo.start), + end_time=UNIX_TIMESTAMP(:songinfo.end), + trackid=:song.trackid, + thread=:thread, + requesting=:requestsenabled, + djname=:streamername, + lastset=NOW(); + ` - // check if we've successfully updated, otherwise we need to do an insert - if i, err := res.RowsAffected(); err != nil { - return errors.E(op, err) - } else if i > 0 { // success - return nil - } + _, err := sqlx.NamedExec(ss.handle, query, status) + if err != nil { + return errors.E(op, err) } - return nil } @@ -98,12 +77,12 @@ func (ss StatusStorage) Load() (*radio.Status, error) { from_unixtime(end_time) AS 'songinfo.end', trackid AS 'song.trackid', thread, - requesting AS requestsenabled, - djname AS 'streamername' + IF(requesting = 1, 'true', 'false') AS requestsenabled, + djname AS streamername FROM streamstatus WHERE - id=0 + id=1 LIMIT 1; ` diff --git a/storage/test/status.go b/storage/test/status.go new file mode 100644 index 00000000..841384ee --- /dev/null +++ b/storage/test/status.go @@ -0,0 +1,52 @@ +package storagetest + +import ( + "time" + + radio "github.com/R-a-dio/valkyrie" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func (suite *Suite) TestStatusStore() { + t := suite.T() + ss := suite.Storage.Status(suite.ctx) + + in := radio.Status{ + User: radio.User{ + DJ: radio.DJ{ + ID: 500, + }, + }, + Song: radio.Song{ + Metadata: "testing data", + DatabaseTrack: &radio.DatabaseTrack{ + TrackID: 1500, + }, + }, + SongInfo: radio.SongInfo{ + Start: time.Date(2000, time.April, 1, 5, 6, 7, 8, time.UTC), + End: time.Date(2010, time.February, 10, 15, 16, 17, 18, time.UTC), + }, + Listeners: 900, + StreamerName: "test", + Thread: "a cool thread", + RequestsEnabled: true, + } + + err := ss.Store(in) + require.NoError(t, err) + + out, err := ss.Load() + require.NoError(t, err) + assert.Condition(t, func() (success bool) { + return assert.Equal(t, in.User, out.User) && + assert.Equal(t, in.Song, out.Song) && + assert.WithinDuration(t, in.SongInfo.Start, out.SongInfo.Start, 0) && + assert.WithinDuration(t, in.SongInfo.End, out.SongInfo.End, 0) && + assert.Equal(t, in.Listeners, out.Listeners) && + assert.Equal(t, in.StreamerName, out.StreamerName) && + assert.Equal(t, in.Thread, out.Thread) && + assert.Equal(t, in.RequestsEnabled, out.RequestsEnabled) + }) +} diff --git a/templates/default/home.tmpl b/templates/default/home.tmpl index b3778871..ad5ba788 100644 --- a/templates/default/home.tmpl +++ b/templates/default/home.tmpl @@ -30,23 +30,12 @@
Listeners: {{.Status.Listeners}}
-00:00 / 00:00
-{{.Song.Metadata}}
{{if .Song.DatabaseTrack}}{{.Song.Tags}}{{end}}
- + +Listeners: {{.Listeners}}
+00:00 / {{.Song.Length | MediaDuration}}
+Last Played
@@ -119,4 +116,16 @@ Home {{printjson .}} {{end}}