123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- package main
- import (
- "database/sql"
- "fmt"
- "log"
- "os"
- "strconv"
- "strings"
- "time"
- _ "github.com/mattn/go-sqlite3"
- )
- //
- // Tables:
- //
- // settings(username, setting, value)
- // scores(username, when, date, hand, won, run, score)
- // monthly(month, username, days, hands_won, bestrun, score)
- //
- // Make sure the go-sqlite3 is here. Keep the db related things together.
- type DBData struct {
- DB *sql.DB
- User string
- LockValue string
- }
- func (db *DBData) Open(filename string) {
- var err error
- db.DB, err = sql.Open("sqlite3", filename)
- if err != nil {
- log.Panicln("Error Opening Sqlite3:", err)
- }
- // Create the tables (if needed)
- db.Create()
- }
- func (db *DBData) Close() {
- db.DB.Close()
- }
- const LOCK_USERNAME = ""
- const LOCK_SETTING = "LOCK"
- const LOCK_CLEAR = "0"
- const LOCK_STALE = 60
- func (db *DBData) Create() {
- var SQLLines []string = []string{
- "CREATE TABLE IF NOT EXISTS settings( " +
- "username TEXT, setting TEXT, value TEXT, " +
- "PRIMARY KEY(username, setting));",
- "CREATE TABLE IF NOT EXISTS scores( " +
- "username TEXT, `when` INTEGER, date INTEGER, " +
- "hand INTEGER, won INTEGER, run INTEGER, " +
- " score INTEGER, " +
- "PRIMARY KEY(username, date, hand));",
- "CREATE TABLE IF NOT EXISTS monthly( " +
- "month INTEGER, username TEXT, days INTEGER, " +
- "hands_won INTEGER, bestrun INTEGER, " +
- "score INTEGER, " +
- "PRIMARY KEY(month, username) );",
- }
- for _, sql := range SQLLines {
- _, err := db.DB.Exec(sql)
- if err != nil {
- log.Panicln("DBData.Create:", err)
- }
- }
- // Make sure our settings LOCK value exists
- db.DB.Exec(
- "INSERT INTO settings(username, setting, value) "+
- "VALUES(?,?,?);",
- LOCK_USERNAME, LOCK_SETTING, LOCK_CLEAR)
- // An error here is OK. It means the setting already exists.
- }
- // Lock the database (for Monthly maint)
- func (db *DBData) Lock(timeout int) bool {
- var now time.Time = time.Now()
- var value string = fmt.Sprintf("%d,%d", os.Getpid(), now.Unix())
- RetryLock:
- _, err := db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
- value, LOCK_USERNAME, LOCK_SETTING, LOCK_CLEAR)
- if err != nil {
- log.Printf("Lock(%s) : %s\n", value, err)
- }
- row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
- LOCK_USERNAME, LOCK_SETTING)
- var result string
- err = row.Scan(&result)
- if err != nil {
- log.Println("Lock Scan", err)
- }
- // I need to do further checking here -- to make sure the lock isn't stale.
- if result == value {
- // Success! We've got the lock!
- log.Printf("Lock [%s]\n", value)
- db.LockValue = value
- return true
- }
- // We failed. There's already a lock present. Check if it is stale.
- var lockSet string
- var pos int
- var howOld time.Duration
- pos = strings.Index(result, ",")
- if pos != -1 {
- lockSet = result[pos+1:]
- var unixtime int64
- unixtime, err = strconv.ParseInt(lockSet, 10, 64)
- if err == nil {
- var lockTime time.Time = time.Unix(unixtime, 0)
- howOld = now.Sub(lockTime)
- log.Printf("Lock %s [%0.2f seconds old]\n", result, howOld.Seconds())
- if howOld.Seconds() > LOCK_STALE {
- // Lock expired
- log.Printf("Lock %s [%0.2f seconds old] -- Expiring\n", result, howOld.Seconds())
- db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
- LOCK_CLEAR, LOCK_USERNAME, LOCK_SETTING, result)
- goto RetryLock
- }
- }
- } else {
- log.Printf("Can't find , in [%s]\n", result)
- }
- time.Sleep(time.Duration(50) * time.Millisecond)
- howOld = time.Until(now)
- if int(-howOld.Seconds()) > timeout {
- return false
- }
- goto RetryLock
- }
- func (db *DBData) Unlock() bool {
- if db.LockValue == "" {
- // Nothing to unlock -- we didn't get the lock.
- return true
- }
- _, err := db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
- LOCK_CLEAR, LOCK_USERNAME, LOCK_SETTING, db.LockValue)
- if err != nil {
- log.Printf("Unlock(%s) : %s\n", db.LockValue, err)
- }
- row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
- LOCK_USERNAME, LOCK_SETTING)
- var result string
- err = row.Scan(&result)
- if err != nil {
- log.Println("Unlock Scan", err)
- }
- if result != LOCK_CLEAR {
- log.Printf("Unlock Failed (LockValue %s) Result %s (should be %s)\n", db.LockValue, result, LOCK_CLEAR)
- } else {
- log.Println("Unlocked.")
- }
- return result == LOCK_CLEAR
- }
- func (db *DBData) GetSetting(setting string, ifMissing string) string {
- row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
- db.User, setting)
- var value string
- // log.Printf("row: %#v\n", row)
- err := row.Scan(&value)
- if err != nil {
- return ifMissing
- }
- return value
- }
- func (db *DBData) SetSetting(setting string, value string) {
- _, err := db.DB.Exec("REPLACE INTO settings(username, setting, value) VALUES(?,?,?);",
- db.User, setting, value)
- if err != nil {
- log.Panicln("Query SetSetting", err)
- }
- // log.Printf("SetSetting %s %s = %s\n", db.User, setting, value)
- }
- func (db *DBData) SaveScore(when YMD, date YMD, hand int, won int, run int, score int) {
- _, err := db.DB.Exec("INSERT INTO scores(username, `when`, date, hand, won, run, score) VALUES(?,?,?,?,?,?,?);",
- db.User, int(when), int(date), hand, won, run, score)
- if err != nil {
- log.Println("SaveScore", err)
- // When we play a hand we've already played:
- // panic: SaveScore UNIQUE constraint failed: scores.username, scores.date, scores.hand
- }
- }
- func (db *DBData) HandsPlayedOnDay(day YMD) int {
- row := db.DB.QueryRow("SELECT COUNT(*) FROM scores WHERE username=? AND date=?;",
- db.User, int(day))
- var value int
- err := row.Scan(&value)
- if err != nil {
- return 0
- }
- return value
- }
- /*
- WhenPlayed = GetPlayed (as far as I can tell...)
- */
- func (db *DBData) WhenPlayed() map[YMD]int {
- var result map[YMD]int = make(map[YMD]int)
- rows, err := db.DB.Query("SELECT date, COUNT(hand) FROM scores WHERE username=? GROUP BY date;",
- db.User)
- if err != nil {
- log.Println("GetPlayed", err)
- return result
- }
- defer rows.Close()
- for rows.Next() {
- var date YMD
- var hands int
- if err := rows.Scan(&date, &hands); err != nil {
- log.Println("GetPlayed Scan", err)
- return result
- }
- result[date] = hands
- }
- if err = rows.Err(); err != nil {
- log.Println("GetPlayed rows.Err", err)
- }
- return result
- }
- type MonthUser struct {
- Date YM
- Username string
- }
- type MonthStats struct {
- Days int
- Hands_Won int
- Run int
- Score int
- }
- func (db *DBData) ExpireScores(month_first YMD) {
- // step 1: acquire lock
- l := db.Lock(5)
- if !l {
- log.Println("Lock failed")
- return
- }
- defer db.Unlock()
- var Monthly map[MonthUser]MonthStats = make(map[MonthUser]MonthStats)
- rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won), MAX(run) "+
- "FROM scores WHERE date < ? "+
- "GROUP BY date, username ORDER BY date, SUM(score) DESC;",
- month_first)
- if err != nil {
- log.Println("ExpireScores", err)
- return
- }
- defer rows.Close()
- for rows.Next() {
- var mu MonthUser
- var score int
- var won int
- var run int
- var scoreDate YMD
- if err := rows.Scan(&scoreDate, &mu.Username, &score, &won, &run); err != nil {
- log.Println("ExpireScores Scan", err)
- return
- }
- mu.Date = scoreDate.YM()
- // FirstOfMonthDateUnix(&mu.Date)
- if mumap, found := Monthly[mu]; found {
- // entry found
- mumap.Days++
- mumap.Score += score
- mumap.Hands_Won += won
- if run > mumap.Run {
- mumap.Run = run
- }
- Monthly[mu] = mumap
- } else {
- // new entry
- var ms MonthStats = MonthStats{Days: 1,
- Score: score,
- Hands_Won: won,
- Run: run,
- }
- Monthly[mu] = ms
- }
- }
- rows.Close()
- if len(Monthly) == 0 {
- // Nothing to do
- return
- }
- // Begin transaction
- tx, err := db.DB.Begin()
- if err != nil {
- log.Println("ExpireScores Begin", err)
- return
- }
- tx.Exec(
- "DELETE FROM scores WHERE date < ?;",
- month_first)
- for mu, ms := range Monthly {
- _, err := tx.Exec("INSERT INTO monthly(month, username, days, hands_won, bestrun, score) "+
- "VALUES(?,?,?,?,?,?);",
- mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Run, ms.Score)
- if err != nil {
- log.Println("ExpireScores Insert", err)
- tx.Rollback()
- return
- }
- }
- // End transaction / Commit
- err = tx.Commit()
- if err != nil {
- log.Println("ExpireScores Commit", err)
- }
- }
- type ScoresDetails struct {
- User string
- Date int64
- Hand int
- Won int
- Score int
- }
- func (db *DBData) GetScoresOnDay(date int64) []ScoresDetails {
- var result []ScoresDetails
- rows, err := db.DB.Query("SELECT username, date, hand, won, score FROM SCORES "+
- "WHERE date=? ORDER BY username, hand;",
- date)
- if err != nil {
- log.Println("GetScoresOnDay", date, err)
- return result
- }
- defer rows.Close()
- for rows.Next() {
- var sd ScoresDetails
- if err := rows.Scan(&sd.User, &sd.Date, &sd.Hand, &sd.Won, &sd.Score); err != nil {
- log.Println("GetScoresOnDay Scan", err)
- return result
- }
- result = append(result, sd)
- }
- if err = rows.Err(); err != nil {
- log.Println("GetScoresOnDay rows.Err", err)
- }
- return result
- }
- type ScoresData struct {
- Date YMD
- User string
- Score int
- Run int
- Won int
- }
- func (db *DBData) GetScores(limit int) []ScoresData {
- var result []ScoresData
- rows, err := db.DB.Query("SELECT date, username, SUM(score), MAX(run), SUM(won) "+
- "FROM SCORES GROUP BY date, username ORDER BY SUM(score) DESC LIMIT ?;",
- limit)
- if err != nil {
- log.Println("GetScores", err)
- return result
- }
- defer rows.Close()
- for rows.Next() {
- var sd ScoresData
- if err := rows.Scan(&sd.Date, &sd.User, &sd.Score, &sd.Run, &sd.Won); err != nil {
- log.Println("GetScores Scan", err)
- return result
- }
- result = append(result, sd)
- }
- if err = rows.Err(); err != nil {
- log.Println("GetScores rows.Err", err)
- }
- return result
- }
- type MonthlyData struct {
- Date YMD
- User string
- Days int
- Hands_Won int
- Best_Run int
- Score int
- }
- func (db *DBData) GetMonthlyScores(limit int) []MonthlyData {
- var result []MonthlyData
- rows, err := db.DB.Query("SELECT month, username, days, hands_won, bestrun, score "+
- "FROM monthly ORDER BY score DESC LIMIT ?;",
- limit)
- if err != nil {
- log.Println("GetMonthlyScores", err)
- return result
- }
- defer rows.Close()
- for rows.Next() {
- var md MonthlyData
- if err := rows.Scan(&md.Date, &md.User, &md.Days, &md.Hands_Won, &md.Best_Run, &md.Score); err != nil {
- log.Println("GetMonthlyScores Scan", err)
- return result
- }
- result = append(result, md)
- }
- if err = rows.Err(); err != nil {
- log.Println("GetMonthlyScores rows.Err", err)
- }
- return result
- }
|