192 lines
3.9 KiB
Go
192 lines
3.9 KiB
Go
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"
|
|
)
|
|
|
|
const dbDateFormat = "2006-01-02 15:04:05.999"
|
|
|
|
var (
|
|
//go:embed queries/schema.sql
|
|
querySchema string
|
|
|
|
//go:embed queries/song_add.sql
|
|
querySongAdd string
|
|
|
|
//go:embed queries/history_add.sql
|
|
queryHistoryAdd string
|
|
|
|
//go:embed queries/last_n_songs.sql
|
|
queryLastNSongs string
|
|
//go:embed queries/most_popular_songs.sql
|
|
queryMostPopularSongs string
|
|
//go:embed queries/most_simultaneous_listeners.sql
|
|
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
|
|
}
|
|
|
|
func New(path string) (statistics.Statistics, error) {
|
|
db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?_journal=WAL&_mutex=full", path))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := initDBStatements(db); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SQLiteStatistics{db: db}, nil
|
|
}
|
|
|
|
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()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
row := tx.Stmt(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
|
|
}
|
|
|
|
res, err := tx.Stmt(stmtHistoryAdd).Exec(song.StartAt.UTC().Format(dbDateFormat),
|
|
songID, song.Listeners, song.PeakListeners)
|
|
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")
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
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 {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
rows, err := tx.Stmt(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(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 *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 {
|
|
return s.db.Close()
|
|
}
|