Restructurised Statistics.
This commit is contained in:
parent
51a0ef167c
commit
f266e4fcf7
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user