From 1ab47d6fbee83f2dd00256bf5cd9ad33c2448202 Mon Sep 17 00:00:00 2001 From: sentriz Date: Tue, 22 Mar 2022 19:36:33 +0000 Subject: [PATCH] feat(subsonic): update play stats when scrobbling closes: #207 Co-authored-by: Brian Doherty --- server/ctrlsubsonic/handlers_common.go | 4 ++ server/ctrlsubsonic/handlers_raw.go | 67 +++++++++++++++++++------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/server/ctrlsubsonic/handlers_common.go b/server/ctrlsubsonic/handlers_common.go index 08b9e653..674013bc 100644 --- a/server/ctrlsubsonic/handlers_common.go +++ b/server/ctrlsubsonic/handlers_common.go @@ -55,6 +55,10 @@ func (c *Controller) ServeScrobble(r *http.Request) *spec.Response { optStamp := params.GetOrTime("time", time.Now()) 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 for _, scrobbler := range c.Scrobblers { if err := scrobbler.Scrobble(user, track, optStamp, optSubmission); err != nil { diff --git a/server/ctrlsubsonic/handlers_raw.go b/server/ctrlsubsonic/handlers_raw.go index 65b8e4b7..f50ae26e 100644 --- a/server/ctrlsubsonic/handlers_raw.go +++ b/server/ctrlsubsonic/handlers_raw.go @@ -11,6 +11,7 @@ import ( "time" "github.com/disintegration/imaging" + "github.com/jinzhu/gorm" "go.senan.xyz/gonic/server/ctrlsubsonic/params" "go.senan.xyz/gonic/server/ctrlsubsonic/spec" @@ -26,42 +27,64 @@ import ( // b) return a non-nil spec.Response // _but not both_ -func streamGetTransPref(dbc *db.DB, userID int, client string) db.TranscodePreference { - pref := db.TranscodePreference{} - dbc. +func streamGetTransPref(dbc *db.DB, userID int, client string) (*db.TranscodePreference, error) { + var pref db.TranscodePreference + err := dbc. Where("user_id=?", userID). Where("client COLLATE NOCASE IN (?)", []string{"*", client}). Order("client DESC"). // ensure "*" is last if it's there - First(&pref) - return pref + First(&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) { - track := db.Track{} + var track db.Track err := dbc. Preload("Album"). First(&track, trackID). 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) { - podcast := db.PodcastEpisode{} - err := dbc.First(&podcast, podcastID).Error - return &podcast, err + var podcast db.PodcastEpisode + if err := dbc.First(&podcast, podcastID).Error; err != nil { + 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{ AlbumID: albumID, UserID: userID, } - dbc. + err := dbc. Where(play). - First(&play) - play.Time = time.Now() // for getAlbumList?type=recent - play.Count++ // for getAlbumList?type=frequent - dbc.Save(&play) + First(&play). + Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + 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 ( @@ -242,10 +265,18 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R user := r.Context().Value(CtxUser).(*db.User) 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 { log.Printf("serving raw `%s`\n", audioFile.AudioFilename()) w.Header().Set("Content-Type", audioFile.MIME())