Skip to content

Commit

Permalink
refactor: use CurrentStatus in a few places
Browse files Browse the repository at this point in the history
ircbot: use CurrentStatus stream to do song now starting announcements

website: use CurrentStatus stream for now playing and streamer updates

storage/mariadb: implement Status.Store with INSERT - UPDATE IF EXISTS
  • Loading branch information
Wessie committed Feb 24, 2024
1 parent 0c5d7de commit 66bc44e
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 127 deletions.
37 changes: 30 additions & 7 deletions ircbot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
}
}
4 changes: 2 additions & 2 deletions manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions radio.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions radio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
5 changes: 2 additions & 3 deletions storage/mariadb/search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
111 changes: 45 additions & 66 deletions storage/mariadb/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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;
`

Expand Down
52 changes: 52 additions & 0 deletions storage/test/status.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
41 changes: 25 additions & 16 deletions templates/default/home.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,12 @@
</div>
<div sse-swap="metadata">
{{template "nowplaying" .Status}}
</div>
<div class="columns">
<div class="column is-6">
<p class="has-text-centered">Listeners: <span id="listener-count">{{.Status.Listeners}}</span></p>
</div>
<div class="column is-6">
<p class="has-text-centered"><span id="progress-current" data-start="{{.Status.SongInfo.Start.UnixMilli}}">00:00</span> / <span id="progress-max">00:00<span></p>
</div>


</div>
</div>
<div class="column is-3">
<div id="dj-image" class="md-5">
<figure class="image is-square">
<img src="/api/dj-image/{{.Status.User.DJ.Image}}">
</figure>
<h4 class="title has-text-centered">{{.Status.User.DJ.Name}}</h4>
</div>
<div class="column is-3" sse-swap="streamer">
{{template "streamer" .Status.User}}
</div>
</div>
<div class="columns">
Expand Down Expand Up @@ -92,7 +81,15 @@ Home {{printjson .}}
<p id="metadata" class="title is-2 has-text-centered" style="cursor: pointer;">{{.Song.Metadata}}</p>
<p class="subtitle is-4 has-text-centered" style="font-size: 14px;">{{if .Song.DatabaseTrack}}{{.Song.Tags}}{{end}}</p>
</h2>
<progress id="current-song-progress" class="progress is-large" value="0" max="{{.Song.Length.Seconds}}"></progress>
<progress id="current-song-progress" class="progress is-large mb-0" value="0" max="{{.Song.Length.Seconds}}"></progress>
<div class="columns">
<div class="column is-6">
<p class="has-text-centered">Listeners: <span id="listener-count">{{.Listeners}}</span></p>
</div>
<div class="column is-6">
<p class="has-text-centered"><span id="progress-current" data-start="{{.SongInfo.Start.UnixMilli}}">00:00</span> / <span id="progress-max">{{.Song.Length | MediaDuration}}<span></p>
</div>
</div>
{{end}}
{{define "lastplayed"}}
<p class="title is-4 has-text-centered">Last Played</p>
Expand All @@ -119,4 +116,16 @@ Home {{printjson .}}
{{end}}
</ul>
</div>
{{end}}
{{define "streamer"}}
<div class="card has-background-white-bis p-4 m-2">
<div class="card-image">
<figure class="image is-square ml-0 mr-0">
<img src="/api/dj-image/{{.DJ.Image}}"/>
</figure>
</div>
<div class="card-content has-text-centered pt-4 pb-0">
<div class="title is-4 word-break-ellipsis">{{.DJ.Name}}</div>
</div>
</div>
{{end}}
Loading

0 comments on commit 66bc44e

Please sign in to comment.