db.go 10 KB

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