feat(subsonic): update play stats when scrobbling

closes: #207

Co-authored-by: Brian Doherty <brian.r.doherty@gmail.com>
This commit is contained in:
sentriz
2022-03-22 19:36:33 +00:00
parent 59c404749f
commit 1ab47d6fbe
2 changed files with 53 additions and 18 deletions

View File

@@ -55,6 +55,10 @@ func (c *Controller) ServeScrobble(r *http.Request) *spec.Response {
optStamp := params.GetOrTime("time", time.Now()) optStamp := params.GetOrTime("time", time.Now())
optSubmission := params.GetOrBool("submission", true) optSubmission := params.GetOrBool("submission", true)
if err := streamUpdateStats(c.DB, user.ID, track.Album.ID, optStamp); err != nil {
return spec.NewError(0, "error updating stats: %v", err)
}
var scrobbleErrs multierr.Err var scrobbleErrs multierr.Err
for _, scrobbler := range c.Scrobblers { for _, scrobbler := range c.Scrobblers {
if err := scrobbler.Scrobble(user, track, optStamp, optSubmission); err != nil { if err := scrobbler.Scrobble(user, track, optStamp, optSubmission); err != nil {

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/jinzhu/gorm"
"go.senan.xyz/gonic/server/ctrlsubsonic/params" "go.senan.xyz/gonic/server/ctrlsubsonic/params"
"go.senan.xyz/gonic/server/ctrlsubsonic/spec" "go.senan.xyz/gonic/server/ctrlsubsonic/spec"
@@ -26,42 +27,64 @@ import (
// b) return a non-nil spec.Response // b) return a non-nil spec.Response
// _but not both_ // _but not both_
func streamGetTransPref(dbc *db.DB, userID int, client string) db.TranscodePreference { func streamGetTransPref(dbc *db.DB, userID int, client string) (*db.TranscodePreference, error) {
pref := db.TranscodePreference{} var pref db.TranscodePreference
dbc. err := dbc.
Where("user_id=?", userID). Where("user_id=?", userID).
Where("client COLLATE NOCASE IN (?)", []string{"*", client}). Where("client COLLATE NOCASE IN (?)", []string{"*", client}).
Order("client DESC"). // ensure "*" is last if it's there Order("client DESC"). // ensure "*" is last if it's there
First(&pref) First(&pref).
return pref Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return &pref, nil
}
if err != nil {
return nil, fmt.Errorf("find transcode preference: %w", err)
}
return &pref, nil
} }
func streamGetTrack(dbc *db.DB, trackID int) (*db.Track, error) { func streamGetTrack(dbc *db.DB, trackID int) (*db.Track, error) {
track := db.Track{} var track db.Track
err := dbc. err := dbc.
Preload("Album"). Preload("Album").
First(&track, trackID). First(&track, trackID).
Error Error
return &track, err if err != nil {
return nil, fmt.Errorf("find track: %w", err)
}
return &track, nil
} }
func streamGetPodcast(dbc *db.DB, podcastID int) (*db.PodcastEpisode, error) { func streamGetPodcast(dbc *db.DB, podcastID int) (*db.PodcastEpisode, error) {
podcast := db.PodcastEpisode{} var podcast db.PodcastEpisode
err := dbc.First(&podcast, podcastID).Error if err := dbc.First(&podcast, podcastID).Error; err != nil {
return &podcast, err return nil, fmt.Errorf("find podcast: %w", err)
}
return &podcast, nil
} }
func streamUpdateStats(dbc *db.DB, userID, albumID int) { func streamUpdateStats(dbc *db.DB, userID, albumID int, playTime time.Time) error {
play := db.Play{ play := db.Play{
AlbumID: albumID, AlbumID: albumID,
UserID: userID, UserID: userID,
} }
dbc. err := dbc.
Where(play). Where(play).
First(&play) First(&play).
play.Time = time.Now() // for getAlbumList?type=recent Error
play.Count++ // for getAlbumList?type=frequent if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
dbc.Save(&play) return fmt.Errorf("find stat: %w", err)
}
play.Count++ // for getAlbumList?type=frequent
if playTime.After(play.Time) {
play.Time = playTime // for getAlbumList?type=recent
}
if err := dbc.Save(&play).Error; err != nil {
return fmt.Errorf("save stat: %w", err)
}
return nil
} }
const ( const (
@@ -242,10 +265,18 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
user := r.Context().Value(CtxUser).(*db.User) user := r.Context().Value(CtxUser).(*db.User)
if track, ok := audioFile.(*db.Track); ok && track.Album != nil { if track, ok := audioFile.(*db.Track); ok && track.Album != nil {
defer streamUpdateStats(c.DB, user.ID, track.Album.ID) defer func() {
if err := streamUpdateStats(c.DB, user.ID, track.Album.ID, time.Now()); err != nil {
log.Printf("error updating listen stats: %v", err)
}
}()
}
pref, err := streamGetTransPref(c.DB, user.ID, params.GetOr("c", ""))
if err != nil {
return spec.NewError(0, "failed to get transcode stream preference: %v", err)
} }
pref := streamGetTransPref(c.DB, user.ID, params.GetOr("c", ""))
onInvalidProfile := func() error { onInvalidProfile := func() error {
log.Printf("serving raw `%s`\n", audioFile.AudioFilename()) log.Printf("serving raw `%s`\n", audioFile.AudioFilename())
w.Header().Set("Content-Type", audioFile.MIME()) w.Header().Set("Content-Type", audioFile.MIME())