Restructurised Statistics.
This commit is contained in:
parent
51a0ef167c
commit
f266e4fcf7
@ -2,11 +2,9 @@ package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"dwelling-radio/internal/radio"
|
||||
"dwelling-radio/internal/statistics"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/pkg/errors"
|
||||
@ -32,52 +30,8 @@ var (
|
||||
queryMostSimultaneousListeners string
|
||||
)
|
||||
|
||||
var (
|
||||
stmtSongAdd *sql.Stmt
|
||||
stmtHistoryAdd *sql.Stmt
|
||||
stmtLastNSongs *sql.Stmt
|
||||
stmtMostPopularSongs *sql.Stmt
|
||||
stmtMostSimultaneousListeners *sql.Stmt
|
||||
)
|
||||
|
||||
type SQLiteStatistics struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
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
|
||||
statistics.BaseStatistics
|
||||
}
|
||||
|
||||
func New(path string) (statistics.Statistics, error) {
|
||||
@ -86,106 +40,51 @@ func New(path string) (statistics.Statistics, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := initDBStatements(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := &SQLiteStatistics{
|
||||
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 {
|
||||
if song == nil || song.Artist == "" || song.Title == "" {
|
||||
return errors.New("No song or an empty one was passed.")
|
||||
}
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
_, err = db.Exec(querySchema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
row := tx.Stmt(stmtSongAdd).QueryRow(song.Artist, song.Title)
|
||||
if row.Err() != nil {
|
||||
return row.Err()
|
||||
return nil, errors.Wrap(err,
|
||||
statistics.ErrPrepareStmt{Name: "initial schema"}.Error())
|
||||
}
|
||||
|
||||
var songID int64
|
||||
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)
|
||||
stats.BaseStatistics.StmtSongAdd, err = db.Prepare(querySongAdd)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ra, err := res.RowsAffected(); ra == 0 || err != nil {
|
||||
return errors.New("a song wasn't added to history, but there were no errors")
|
||||
return nil, errors.Wrap(err,
|
||||
statistics.ErrPrepareStmt{Name: "song_add"}.Error())
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStatistics) LastNSongs(n int64) ([]radio.Song, error) {
|
||||
if n == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
stats.BaseStatistics.StmtHistoryAdd, err = db.Prepare(queryHistoryAdd)
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err,
|
||||
statistics.ErrPrepareStmt{Name: "last N songs"}.Error())
|
||||
}
|
||||
|
||||
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(dbDateFormat, startAt)
|
||||
stats.BaseStatistics.StmtMostPopularSongs, err = db.Prepare(queryMostPopularSongs)
|
||||
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()
|
||||
|
||||
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
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (s *SQLiteStatistics) Close() error {
|
||||
return s.db.Close()
|
||||
return s.BaseStatistics.Close()
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
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"
|
||||
|
||||
@ -11,3 +18,121 @@ type Statistics interface {
|
||||
MostSimultaneousListeners() (radio.Song, 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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user