1
0

Restructurised Statistics.

This commit is contained in:
Alexander Andreev 2024-05-15 23:51:10 +04:00
parent 51a0ef167c
commit f266e4fcf7
Signed by: Arav
GPG Key ID: 25969B23DCB5CA34
2 changed files with 153 additions and 129 deletions

View File

@ -2,11 +2,9 @@ package sqlite
import ( import (
"database/sql" "database/sql"
"dwelling-radio/internal/radio"
"dwelling-radio/internal/statistics" "dwelling-radio/internal/statistics"
_ "embed" _ "embed"
"fmt" "fmt"
"time"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -32,52 +30,8 @@ var (
queryMostSimultaneousListeners string queryMostSimultaneousListeners string
) )
var (
stmtSongAdd *sql.Stmt
stmtHistoryAdd *sql.Stmt
stmtLastNSongs *sql.Stmt
stmtMostPopularSongs *sql.Stmt
stmtMostSimultaneousListeners *sql.Stmt
)
type SQLiteStatistics struct { type SQLiteStatistics struct {
db *sql.DB statistics.BaseStatistics
}
func initDBStatements(db *sql.DB) error {
db.Exec("PRAGMA foreign_keys = ON;")
_, err := db.Exec(querySchema)
if err != nil {
return errors.Wrap(err, "failed to init schema")
}
stmtSongAdd, err = db.Prepare(querySongAdd)
if err != nil {
return errors.Wrap(err, "failed to prepare querySongAdd")
}
stmtHistoryAdd, err = db.Prepare(queryHistoryAdd)
if err != nil {
return errors.Wrap(err, "failed to prepare queryHistoryAdd")
}
stmtLastNSongs, err = db.Prepare(queryLastNSongs)
if err != nil {
return errors.Wrap(err, "failed to prepare queryLastNSongs")
}
stmtMostPopularSongs, err = db.Prepare(queryMostPopularSongs)
if err != nil {
return errors.Wrap(err, "failed to prepare queryMostPopularSongs")
}
stmtMostSimultaneousListeners, err = db.Prepare(queryMostSimultaneousListeners)
if err != nil {
return errors.Wrap(err, "failed to prepare queryMostSimultaneousListeners")
}
return nil
} }
func New(path string) (statistics.Statistics, error) { func New(path string) (statistics.Statistics, error) {
@ -86,106 +40,51 @@ func New(path string) (statistics.Statistics, error) {
return nil, err return nil, err
} }
if err := initDBStatements(db); err != nil { stats := &SQLiteStatistics{
return nil, err BaseStatistics: statistics.BaseStatistics{
} Db: db, DbDateFormat: dbDateFormat}}
return &SQLiteStatistics{db: db}, nil db.Exec("PRAGMA foreign_keys = ON;")
}
func (s *SQLiteStatistics) Add(song *radio.Song) error { _, err = db.Exec(querySchema)
if song == nil || song.Artist == "" || song.Title == "" {
return errors.New("No song or an empty one was passed.")
}
tx, err := s.db.Begin()
if err != nil { if err != nil {
return err return nil, errors.Wrap(err,
} statistics.ErrPrepareStmt{Name: "initial schema"}.Error())
defer tx.Rollback()
row := tx.Stmt(stmtSongAdd).QueryRow(song.Artist, song.Title)
if row.Err() != nil {
return row.Err()
} }
var songID int64 stats.BaseStatistics.StmtSongAdd, err = db.Prepare(querySongAdd)
if err := row.Scan(&songID); err != nil {
return err
}
res, err := tx.Stmt(stmtHistoryAdd).Exec(song.StartAt.UTC().Format(dbDateFormat),
songID, song.Listeners, song.PeakListeners)
if err != nil { if err != nil {
return err return nil, errors.Wrap(err,
} else if ra, err := res.RowsAffected(); ra == 0 || err != nil { statistics.ErrPrepareStmt{Name: "song_add"}.Error())
return errors.New("a song wasn't added to history, but there were no errors")
} }
tx.Commit() stats.BaseStatistics.StmtHistoryAdd, err = db.Prepare(queryHistoryAdd)
return nil
}
func (s *SQLiteStatistics) LastNSongs(n int64) ([]radio.Song, error) {
if n == 0 {
return nil, nil
}
tx, err := s.db.Begin()
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err,
statistics.ErrPrepareStmt{Name: "history_add"}.Error())
} }
defer tx.Rollback()
rows, err := tx.Stmt(stmtLastNSongs).Query(n) stats.BaseStatistics.StmtLastNSongs, err = db.Prepare(queryLastNSongs)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err,
statistics.ErrPrepareStmt{Name: "last N songs"}.Error())
} }
songs := make([]radio.Song, n) stats.BaseStatistics.StmtMostPopularSongs, err = db.Prepare(queryMostPopularSongs)
i := 0
for rows.Next() {
var startAt string
if err := rows.Scan(&startAt, &songs[i].Artist, &songs[i].Title,
&songs[i].Listeners, &songs[i].PeakListeners); err != nil {
return nil, err
}
songs[i].StartAt, err = time.Parse(dbDateFormat, startAt)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrap(err,
statistics.ErrPrepareStmt{Name: "most popular song"}.Error())
} }
i++ stats.BaseStatistics.StmtMostSimultaneousListeners, err = db.Prepare(queryMostSimultaneousListeners)
if err != nil {
return nil, errors.Wrap(err,
statistics.ErrPrepareStmt{Name: "most simultaneous listeners"}.Error())
} }
tx.Commit() return stats, nil
if i == 0 {
return nil, nil
}
lst := make([]radio.Song, i)
copy(lst, songs[:])
return lst, nil
}
func (s *SQLiteStatistics) MostNPopularSongs(n int64) ([]radio.Song, error) {
if n == 0 {
return nil, nil
}
return nil, nil
}
func (s *SQLiteStatistics) MostSimultaneousListeners() (radio.Song, error) {
return radio.Song{}, nil
} }
func (s *SQLiteStatistics) Close() error { func (s *SQLiteStatistics) Close() error {
return s.db.Close() return s.BaseStatistics.Close()
} }

View File

@ -1,6 +1,13 @@
package statistics package statistics
import "dwelling-radio/internal/radio" import (
"database/sql"
"dwelling-radio/internal/radio"
"fmt"
"time"
"github.com/pkg/errors"
)
const MostListenedDateFormat string = "02 January 2006 at 15:04:05 MST" const MostListenedDateFormat string = "02 January 2006 at 15:04:05 MST"
@ -11,3 +18,121 @@ type Statistics interface {
MostSimultaneousListeners() (radio.Song, error) MostSimultaneousListeners() (radio.Song, error)
Close() error Close() error
} }
type ErrPrepareStmt struct {
Name string
}
func (e ErrPrepareStmt) Error() string {
return fmt.Sprintf("failed to prepare an SQL statement '%s'", e.Name)
}
var ErrNoSong = errors.New("no song was passed (a struct is nil or empty)")
var ErrSongNotAdded = errors.New("song was not added")
type BaseStatistics struct {
Db *sql.DB
DbDateFormat string
StmtHistoryAdd *sql.Stmt
StmtSongAdd *sql.Stmt
StmtLastNSongs *sql.Stmt
StmtMostPopularSongs *sql.Stmt
StmtMostSimultaneousListeners *sql.Stmt
}
func (s *BaseStatistics) Add(song *radio.Song) error {
if song == nil || song.Artist == "" || song.Title == "" {
return ErrNoSong
}
tx, err := s.Db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
row := tx.Stmt(s.StmtSongAdd).QueryRow(song.Artist, song.Title)
if row.Err() != nil {
return row.Err()
}
var songID int64
if err := row.Scan(&songID); err != nil {
return err
}
_, err = tx.Stmt(s.StmtHistoryAdd).Exec(song.StartAt.UTC().Format(s.DbDateFormat),
songID, song.Listeners, song.PeakListeners)
if err != nil {
return errors.Wrap(err, ErrSongNotAdded.Error())
}
tx.Commit()
return nil
}
func (s *BaseStatistics) LastNSongs(n int64) ([]radio.Song, error) {
if n == 0 {
return nil, nil
}
tx, err := s.Db.Begin()
if err != nil {
return nil, err
}
defer tx.Rollback()
rows, err := tx.Stmt(s.StmtLastNSongs).Query(n)
if err != nil {
return nil, err
}
songs := make([]radio.Song, n)
i := 0
for rows.Next() {
var startAt string
if err := rows.Scan(&startAt, &songs[i].Artist, &songs[i].Title,
&songs[i].Listeners, &songs[i].PeakListeners); err != nil {
return nil, err
}
songs[i].StartAt, err = time.Parse(s.DbDateFormat, startAt)
if err != nil {
return nil, err
}
i++
}
tx.Commit()
if i == 0 {
return nil, nil
}
lst := make([]radio.Song, i)
copy(lst, songs[:])
return lst, nil
}
func (s *BaseStatistics) MostNPopularSongs(n int64) ([]radio.Song, error) {
if n == 0 {
return nil, nil
}
return nil, nil
}
func (s *BaseStatistics) MostSimultaneousListeners() (radio.Song, error) {
return radio.Song{}, nil
}
func (s *BaseStatistics) Close() error {
return s.Db.Close()
}