db.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. package main
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "log"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "time"
  10. _ "github.com/mattn/go-sqlite3"
  11. )
  12. //
  13. // Tables:
  14. //
  15. // settings(username, setting, value)
  16. // scores(username, when, date, hand, won, run, score)
  17. // monthly(month, username, days, hands_won, bestrun, score)
  18. //
  19. // Make sure the go-sqlite3 is here. Keep the db related things together.
  20. type DBData struct {
  21. DB *sql.DB
  22. User string
  23. LockValue string
  24. }
  25. func (db *DBData) Open(filename string) {
  26. var err error
  27. db.DB, err = sql.Open("sqlite3", filename)
  28. if err != nil {
  29. log.Panicln("Error Opening Sqlite3:", err)
  30. }
  31. // Create the tables (if needed)
  32. db.Create()
  33. }
  34. func (db *DBData) Close() {
  35. db.DB.Close()
  36. }
  37. const LOCK_USERNAME = ""
  38. const LOCK_SETTING = "LOCK"
  39. const LOCK_CLEAR = "0"
  40. const LOCK_STALE = 60
  41. func (db *DBData) Create() {
  42. var SQLLines []string = []string{
  43. "CREATE TABLE IF NOT EXISTS settings( " +
  44. "username TEXT, setting TEXT, value TEXT, " +
  45. "PRIMARY KEY(username, setting));",
  46. "CREATE TABLE IF NOT EXISTS scores( " +
  47. "username TEXT, `when` INTEGER, date INTEGER, " +
  48. "hand INTEGER, won INTEGER, run INTEGER, " +
  49. " score INTEGER, " +
  50. "PRIMARY KEY(username, date, hand));",
  51. "CREATE TABLE IF NOT EXISTS monthly( " +
  52. "month INTEGER, username TEXT, days INTEGER, " +
  53. "hands_won INTEGER, bestrun INTEGER, " +
  54. "score INTEGER, " +
  55. "PRIMARY KEY(month, username) );",
  56. }
  57. for _, sql := range SQLLines {
  58. _, err := db.DB.Exec(sql)
  59. if err != nil {
  60. log.Panicln("DBData.Create:", err)
  61. }
  62. }
  63. // Make sure our settings LOCK value exists
  64. db.DB.Exec(
  65. "INSERT INTO settings(username, setting, value) "+
  66. "VALUES(?,?,?);",
  67. LOCK_USERNAME, LOCK_SETTING, LOCK_CLEAR)
  68. // An error here is OK. It means the setting already exists.
  69. }
  70. // Lock the database (for Monthly maint)
  71. func (db *DBData) Lock(timeout int) bool {
  72. var now time.Time = time.Now()
  73. var value string = fmt.Sprintf("%d,%d", os.Getpid(), now.Unix())
  74. RetryLock:
  75. _, err := db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
  76. value, LOCK_USERNAME, LOCK_SETTING, LOCK_CLEAR)
  77. if err != nil {
  78. log.Printf("Lock(%s) : %s\n", value, err)
  79. }
  80. row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
  81. LOCK_USERNAME, LOCK_SETTING)
  82. var result string
  83. err = row.Scan(&result)
  84. if err != nil {
  85. log.Println("Lock Scan", err)
  86. }
  87. // I need to do further checking here -- to make sure the lock isn't stale.
  88. if result == value {
  89. // Success! We've got the lock!
  90. log.Printf("Lock [%s]\n", value)
  91. db.LockValue = value
  92. return true
  93. }
  94. // We failed. There's already a lock present. Check if it is stale.
  95. var lockSet string
  96. var pos int
  97. var howOld time.Duration
  98. pos = strings.Index(result, ",")
  99. if pos != -1 {
  100. lockSet = result[pos+1:]
  101. var unixtime int64
  102. unixtime, err = strconv.ParseInt(lockSet, 10, 64)
  103. if err == nil {
  104. var lockTime time.Time = time.Unix(unixtime, 0)
  105. howOld = now.Sub(lockTime)
  106. log.Printf("Lock %s [%0.2f seconds old]\n", result, howOld.Seconds())
  107. if howOld.Seconds() > LOCK_STALE {
  108. // Lock expired
  109. log.Printf("Lock %s [%0.2f seconds old] -- Expiring\n", result, howOld.Seconds())
  110. db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
  111. LOCK_CLEAR, LOCK_USERNAME, LOCK_SETTING, result)
  112. goto RetryLock
  113. }
  114. }
  115. } else {
  116. log.Printf("Can't find , in [%s]\n", result)
  117. }
  118. time.Sleep(time.Duration(50) * time.Millisecond)
  119. howOld = time.Until(now)
  120. if int(-howOld.Seconds()) > timeout {
  121. return false
  122. }
  123. goto RetryLock
  124. }
  125. func (db *DBData) Unlock() bool {
  126. if db.LockValue == "" {
  127. // Nothing to unlock -- we didn't get the lock.
  128. return true
  129. }
  130. _, err := db.DB.Exec("UPDATE settings SET value=? WHERE username=? AND setting=? AND value=?;",
  131. LOCK_CLEAR, LOCK_USERNAME, LOCK_SETTING, db.LockValue)
  132. if err != nil {
  133. log.Printf("Unlock(%s) : %s\n", db.LockValue, err)
  134. }
  135. row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
  136. LOCK_USERNAME, LOCK_SETTING)
  137. var result string
  138. err = row.Scan(&result)
  139. if err != nil {
  140. log.Println("Unlock Scan", err)
  141. }
  142. if result != LOCK_CLEAR {
  143. log.Printf("Unlock Failed (LockValue %s) Result %s (should be %s)\n", db.LockValue, result, LOCK_CLEAR)
  144. } else {
  145. log.Println("Unlocked.")
  146. }
  147. return result == LOCK_CLEAR
  148. }
  149. func (db *DBData) GetSetting(setting string, ifMissing string) string {
  150. row := db.DB.QueryRow("SELECT value FROM settings WHERE username=? AND setting=?;",
  151. db.User, setting)
  152. var value string
  153. // log.Printf("row: %#v\n", row)
  154. err := row.Scan(&value)
  155. if err != nil {
  156. return ifMissing
  157. }
  158. return value
  159. }
  160. func (db *DBData) SetSetting(setting string, value string) {
  161. _, err := db.DB.Exec("REPLACE INTO settings(username, setting, value) VALUES(?,?,?);",
  162. db.User, setting, value)
  163. if err != nil {
  164. log.Panicln("Query SetSetting", err)
  165. }
  166. // log.Printf("SetSetting %s %s = %s\n", db.User, setting, value)
  167. }
  168. func (db *DBData) SaveScore(when YMD, date YMD, hand int, won int, run int, score int) {
  169. _, err := db.DB.Exec("INSERT INTO scores(username, `when`, date, hand, won, run, score) VALUES(?,?,?,?,?,?,?);",
  170. db.User, int(when), int(date), hand, won, run, score)
  171. if err != nil {
  172. log.Println("SaveScore", err)
  173. // When we play a hand we've already played:
  174. // panic: SaveScore UNIQUE constraint failed: scores.username, scores.date, scores.hand
  175. }
  176. }
  177. func (db *DBData) HandsPlayedOnDay(day YMD) int {
  178. row := db.DB.QueryRow("SELECT COUNT(*) FROM scores WHERE username=? AND date=?;",
  179. db.User, int(day))
  180. var value int
  181. err := row.Scan(&value)
  182. if err != nil {
  183. return 0
  184. }
  185. return value
  186. }
  187. /*
  188. WhenPlayed = GetPlayed (as far as I can tell...)
  189. */
  190. func (db *DBData) WhenPlayed() map[YMD]int {
  191. var result map[YMD]int = make(map[YMD]int)
  192. rows, err := db.DB.Query("SELECT date, COUNT(hand) FROM scores WHERE username=? GROUP BY date;",
  193. db.User)
  194. if err != nil {
  195. log.Println("GetPlayed", err)
  196. return result
  197. }
  198. defer rows.Close()
  199. for rows.Next() {
  200. var date YMD
  201. var hands int
  202. if err := rows.Scan(&date, &hands); err != nil {
  203. log.Println("GetPlayed Scan", err)
  204. return result
  205. }
  206. result[date] = hands
  207. }
  208. if err = rows.Err(); err != nil {
  209. log.Println("GetPlayed rows.Err", err)
  210. }
  211. return result
  212. }
  213. type MonthUser struct {
  214. Date YM
  215. Username string
  216. }
  217. type MonthStats struct {
  218. Days int
  219. Hands_Won int
  220. Run int
  221. Score int
  222. }
  223. func (db *DBData) ExpireScores(month_first YMD) {
  224. // step 1: acquire lock
  225. l := db.Lock(5)
  226. if !l {
  227. log.Println("Lock failed")
  228. return
  229. }
  230. defer db.Unlock()
  231. var Monthly map[MonthUser]MonthStats = make(map[MonthUser]MonthStats)
  232. rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won), MAX(run) "+
  233. "FROM scores WHERE date < ? "+
  234. "GROUP BY date, username ORDER BY date, SUM(score) DESC;",
  235. month_first)
  236. if err != nil {
  237. log.Println("ExpireScores", err)
  238. return
  239. }
  240. defer rows.Close()
  241. for rows.Next() {
  242. var mu MonthUser
  243. var score int
  244. var won int
  245. var run int
  246. var scoreDate YMD
  247. if err := rows.Scan(&scoreDate, &mu.Username, &score, &won, &run); err != nil {
  248. log.Println("ExpireScores Scan", err)
  249. return
  250. }
  251. mu.Date = scoreDate.YM()
  252. // FirstOfMonthDateUnix(&mu.Date)
  253. if mumap, found := Monthly[mu]; found {
  254. // entry found
  255. mumap.Days++
  256. mumap.Score += score
  257. mumap.Hands_Won += won
  258. if run > mumap.Run {
  259. mumap.Run = run
  260. }
  261. Monthly[mu] = mumap
  262. } else {
  263. // new entry
  264. var ms MonthStats = MonthStats{Days: 1,
  265. Score: score,
  266. Hands_Won: won,
  267. Run: run,
  268. }
  269. Monthly[mu] = ms
  270. }
  271. }
  272. rows.Close()
  273. if len(Monthly) == 0 {
  274. // Nothing to do
  275. return
  276. }
  277. // Begin transaction
  278. tx, err := db.DB.Begin()
  279. if err != nil {
  280. log.Println("ExpireScores Begin", err)
  281. return
  282. }
  283. tx.Exec(
  284. "DELETE FROM scores WHERE date < ?;",
  285. month_first)
  286. for mu, ms := range Monthly {
  287. _, err := tx.Exec("INSERT INTO monthly(month, username, days, hands_won, bestrun, score) "+
  288. "VALUES(?,?,?,?,?,?);",
  289. mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Run, ms.Score)
  290. if err != nil {
  291. log.Println("ExpireScores Insert", err)
  292. tx.Rollback()
  293. return
  294. }
  295. }
  296. // End transaction / Commit
  297. err = tx.Commit()
  298. if err != nil {
  299. log.Println("ExpireScores Commit", err)
  300. }
  301. }
  302. type ScoresDetails struct {
  303. User string
  304. Date int64
  305. Hand int
  306. Won int
  307. Score int
  308. }
  309. func (db *DBData) GetScoresOnDay(date int64) []ScoresDetails {
  310. var result []ScoresDetails
  311. rows, err := db.DB.Query("SELECT username, date, hand, won, score FROM SCORES "+
  312. "WHERE date=? ORDER BY username, hand;",
  313. date)
  314. if err != nil {
  315. log.Println("GetScoresOnDay", date, err)
  316. return result
  317. }
  318. defer rows.Close()
  319. for rows.Next() {
  320. var sd ScoresDetails
  321. if err := rows.Scan(&sd.User, &sd.Date, &sd.Hand, &sd.Won, &sd.Score); err != nil {
  322. log.Println("GetScoresOnDay Scan", err)
  323. return result
  324. }
  325. result = append(result, sd)
  326. }
  327. if err = rows.Err(); err != nil {
  328. log.Println("GetScoresOnDay rows.Err", err)
  329. }
  330. return result
  331. }
  332. type ScoresData struct {
  333. Date YMD
  334. User string
  335. Score int
  336. Run int
  337. Won int
  338. }
  339. func (db *DBData) GetScores(limit int) []ScoresData {
  340. var result []ScoresData
  341. rows, err := db.DB.Query("SELECT date, username, SUM(score), MAX(run), SUM(won) "+
  342. "FROM SCORES GROUP BY date, username ORDER BY SUM(score) DESC LIMIT ?;",
  343. limit)
  344. if err != nil {
  345. log.Println("GetScores", err)
  346. return result
  347. }
  348. defer rows.Close()
  349. for rows.Next() {
  350. var sd ScoresData
  351. if err := rows.Scan(&sd.Date, &sd.User, &sd.Score, &sd.Run, &sd.Won); err != nil {
  352. log.Println("GetScores Scan", err)
  353. return result
  354. }
  355. result = append(result, sd)
  356. }
  357. if err = rows.Err(); err != nil {
  358. log.Println("GetScores rows.Err", err)
  359. }
  360. return result
  361. }
  362. type MonthlyData struct {
  363. Date YMD
  364. User string
  365. Days int
  366. Hands_Won int
  367. Best_Run int
  368. Score int
  369. }
  370. func (db *DBData) GetMonthlyScores(limit int) []MonthlyData {
  371. var result []MonthlyData
  372. rows, err := db.DB.Query("SELECT month, username, days, hands_won, bestrun, score "+
  373. "FROM monthly ORDER BY score DESC LIMIT ?;",
  374. limit)
  375. if err != nil {
  376. log.Println("GetMonthlyScores", err)
  377. return result
  378. }
  379. defer rows.Close()
  380. for rows.Next() {
  381. var md MonthlyData
  382. if err := rows.Scan(&md.Date, &md.User, &md.Days, &md.Hands_Won, &md.Best_Run, &md.Score); err != nil {
  383. log.Println("GetMonthlyScores Scan", err)
  384. return result
  385. }
  386. result = append(result, md)
  387. }
  388. if err = rows.Err(); err != nil {
  389. log.Println("GetMonthlyScores rows.Err", err)
  390. }
  391. return result
  392. }