The other soundtrack statistics are capped at 100, so a client that shows every soundtrack (the MusicPlayer statistics tab) had no way to get them all. This one has no limit and is sorted by name.
172 lines
6.1 KiB
Go
172 lines
6.1 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"music-server/internal/db/repository"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// SoundtrackWithSongs represents a soundtrack with its songs for statistics
|
|
type SoundtrackWithSongs struct {
|
|
SoundtrackID int32 `json:"soundtrack_id"`
|
|
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
|
|
SoundtrackName string `json:"soundtrack_name"`
|
|
SoundtrackPlayed int32 `json:"soundtrack_played"`
|
|
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played,omitempty"`
|
|
Songs []SongInfoForStats `json:"songs"`
|
|
}
|
|
|
|
// SongInfoForStats represents a song with soundtrack info for statistics.
|
|
// The field order matches the generated query rows so they can be converted directly.
|
|
type SongInfoForStats struct {
|
|
SoundtrackID int32 `json:"soundtrack_id"`
|
|
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
|
|
SongUuid uuid.UUID `json:"song_uuid"`
|
|
SoundtrackName string `json:"soundtrack_name"`
|
|
SongName string `json:"song_name"`
|
|
Path string `json:"path"`
|
|
TimesPlayed int32 `json:"times_played"`
|
|
FileName *string `json:"file_name,omitempty"`
|
|
}
|
|
|
|
// StatisticsSummary holds overall statistics
|
|
type StatisticsSummary struct {
|
|
TotalSoundtracks int64 `json:"total_soundtracks"`
|
|
PlayedSoundtracks int64 `json:"played_soundtracks"`
|
|
NeverPlayedSoundtracks int64 `json:"never_played_soundtracks"`
|
|
TotalSoundtrackPlays int64 `json:"total_soundtrack_plays"`
|
|
AvgSoundtrackPlays float64 `json:"avg_soundtrack_plays"`
|
|
MaxSoundtrackPlays int64 `json:"max_soundtrack_plays"`
|
|
MinSoundtrackPlays int64 `json:"min_soundtrack_plays"`
|
|
}
|
|
|
|
// StatisticsService reads play statistics.
|
|
type StatisticsService struct {
|
|
q repository.Querier
|
|
}
|
|
|
|
// NewStatisticsService creates a StatisticsService.
|
|
func NewStatisticsService(q repository.Querier) *StatisticsService {
|
|
return &StatisticsService{q: q}
|
|
}
|
|
|
|
// soundtrackRow covers the generated rows that share the soundtrack-with-songs shape.
|
|
type soundtrackRow interface {
|
|
repository.GetMostPlayedSoundtracksWithSongsRow |
|
|
repository.GetLeastPlayedSoundtracksWithSongsRow |
|
|
repository.GetLastPlayedSoundtracksRow |
|
|
repository.GetOldestPlayedSoundtracksRow |
|
|
repository.GetAllSoundtracksWithSongsRow
|
|
}
|
|
|
|
func toSoundtracks[R soundtrackRow](rows []R, err error) ([]SoundtrackWithSongs, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := []SoundtrackWithSongs{}
|
|
for _, r := range rows {
|
|
row := repository.GetMostPlayedSoundtracksWithSongsRow(r)
|
|
result = append(result, SoundtrackWithSongs{
|
|
SoundtrackID: row.SoundtrackID,
|
|
SoundtrackUuid: row.SoundtrackUuid,
|
|
SoundtrackName: row.SoundtrackName,
|
|
SoundtrackPlayed: row.SoundtrackPlayed,
|
|
SoundtrackLastPlayed: row.SoundtrackLastPlayed,
|
|
Songs: parseSongs(row.Songs),
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type songRow interface {
|
|
repository.GetMostPlayedSongsWithSoundtrackRow | repository.GetLeastPlayedSongsWithSoundtrackRow
|
|
}
|
|
|
|
func toSongs[R songRow](rows []R, err error) ([]SongInfoForStats, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := []SongInfoForStats{}
|
|
for _, r := range rows {
|
|
result = append(result, SongInfoForStats(repository.GetMostPlayedSongsWithSoundtrackRow(r)))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func parseSongs(data []byte) []SongInfoForStats {
|
|
songs := []SongInfoForStats{}
|
|
if data != nil {
|
|
if err := json.Unmarshal(data, &songs); err != nil {
|
|
return []SongInfoForStats{}
|
|
}
|
|
}
|
|
return songs
|
|
}
|
|
|
|
// AllSoundtracks returns every soundtrack with its songs, sorted by name
|
|
func (s *StatisticsService) AllSoundtracks(ctx context.Context) ([]SoundtrackWithSongs, error) {
|
|
return toSoundtracks(s.q.GetAllSoundtracksWithSongs(ctx))
|
|
}
|
|
|
|
// MostPlayedSoundtracks returns the top N most played soundtracks with their songs
|
|
func (s *StatisticsService) MostPlayedSoundtracks(ctx context.Context, limit int32) ([]SoundtrackWithSongs, error) {
|
|
return toSoundtracks(s.q.GetMostPlayedSoundtracksWithSongs(ctx, limit))
|
|
}
|
|
|
|
// LeastPlayedSoundtracks returns the top N least played soundtracks with their songs
|
|
func (s *StatisticsService) LeastPlayedSoundtracks(ctx context.Context, limit int32) ([]SoundtrackWithSongs, error) {
|
|
return toSoundtracks(s.q.GetLeastPlayedSoundtracksWithSongs(ctx, limit))
|
|
}
|
|
|
|
// LastPlayedSoundtracks returns the most recently played soundtracks
|
|
func (s *StatisticsService) LastPlayedSoundtracks(ctx context.Context, limit int32) ([]SoundtrackWithSongs, error) {
|
|
return toSoundtracks(s.q.GetLastPlayedSoundtracks(ctx, limit))
|
|
}
|
|
|
|
// OldestPlayedSoundtracks returns the least recently played soundtracks
|
|
func (s *StatisticsService) OldestPlayedSoundtracks(ctx context.Context, limit int32) ([]SoundtrackWithSongs, error) {
|
|
return toSoundtracks(s.q.GetOldestPlayedSoundtracks(ctx, limit))
|
|
}
|
|
|
|
// NeverPlayedSoundtracks returns soundtracks that have never been played
|
|
func (s *StatisticsService) NeverPlayedSoundtracks(ctx context.Context) ([]SoundtrackWithSongs, error) {
|
|
rows, err := s.q.GetNeverPlayedSoundtracks(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := []SoundtrackWithSongs{}
|
|
for _, row := range rows {
|
|
result = append(result, SoundtrackWithSongs{
|
|
SoundtrackID: row.SoundtrackID,
|
|
SoundtrackUuid: row.SoundtrackUuid,
|
|
SoundtrackName: row.SoundtrackName,
|
|
SoundtrackPlayed: row.SoundtrackPlayed,
|
|
Songs: parseSongs(row.Songs),
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// MostPlayedSongs returns the top N most played songs with their soundtrack info
|
|
func (s *StatisticsService) MostPlayedSongs(ctx context.Context, limit int32) ([]SongInfoForStats, error) {
|
|
return toSongs(s.q.GetMostPlayedSongsWithSoundtrack(ctx, limit))
|
|
}
|
|
|
|
// LeastPlayedSongs returns the top N least played songs with their soundtrack info
|
|
func (s *StatisticsService) LeastPlayedSongs(ctx context.Context, limit int32) ([]SongInfoForStats, error) {
|
|
return toSongs(s.q.GetLeastPlayedSongsWithSoundtrack(ctx, limit))
|
|
}
|
|
|
|
// Summary returns overall statistics
|
|
func (s *StatisticsService) Summary(ctx context.Context) (StatisticsSummary, error) {
|
|
row, err := s.q.GetStatisticsSummary(ctx)
|
|
if err != nil {
|
|
return StatisticsSummary{}, err
|
|
}
|
|
return StatisticsSummary(row), nil
|
|
}
|