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 DBDate, date DBDate, 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 DBDate) 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[DBDate]int {
  190. var result map[DBDate]int = make(map[DBDate]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 DBDate
  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 DBDate
  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 DBDate) {
  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. if err := rows.Scan(&mu.Date, &mu.Username, &score, &won, &run); err != nil {
  246. log.Println("ExpireScores Scan", err)
  247. return
  248. }
  249. mu.Date.First()
  250. // FirstOfMonthDateUnix(&mu.Date)
  251. if mumap, found := Monthly[mu]; found {
  252. // entry found
  253. mumap.Days++
  254. mumap.Score += score
  255. mumap.Hands_Won += won
  256. if run > mumap.Run {
  257. mumap.Run = run
  258. }
  259. Monthly[mu] = mumap
  260. } else {
  261. // new entry
  262. var ms MonthStats = MonthStats{Days: 1,
  263. Score: score,
  264. Hands_Won: won,
  265. Run: run,
  266. }
  267. Monthly[mu] = ms
  268. }
  269. }
  270. rows.Close()
  271. if len(Monthly) == 0 {
  272. // Nothing to do
  273. return
  274. }
  275. // Begin transaction
  276. tx, err := db.DB.Begin()
  277. if err != nil {
  278. log.Println("ExpireScores Begin", err)
  279. return
  280. }
  281. tx.Exec(
  282. "DELETE FROM scores WHERE date < ?;",
  283. month_first)
  284. for mu, ms := range Monthly {
  285. _, err := tx.Exec("INSERT INTO monthly(month, username, days, hands_won, bestrun, score) "+
  286. "VALUES(?,?,?,?,?,?);",
  287. mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Run, ms.Score)
  288. if err != nil {
  289. log.Println("ExpireScores Insert", err)
  290. tx.Rollback()
  291. return
  292. }
  293. }
  294. // End transaction / Commit
  295. err = tx.Commit()
  296. if err != nil {
  297. log.Println("ExpireScores Commit", err)
  298. }
  299. }
  300. type ScoresDetails struct {
  301. User string
  302. Date int64
  303. Hand int
  304. Won int
  305. Score int
  306. }
  307. func (db *DBData) GetScoresOnDay(date int64) []ScoresDetails {
  308. var result []ScoresDetails
  309. rows, err := db.DB.Query("SELECT username, date, hand, won, score FROM SCORES "+
  310. "WHERE date=? ORDER BY username, hand;",
  311. date)
  312. if err != nil {
  313. log.Println("GetScoresOnDay", date, err)
  314. return result
  315. }
  316. defer rows.Close()
  317. for rows.Next() {
  318. var sd ScoresDetails
  319. if err := rows.Scan(&sd.User, &sd.Date, &sd.Hand, &sd.Won, &sd.Score); err != nil {
  320. log.Println("GetScoresOnDay Scan", err)
  321. return result
  322. }
  323. result = append(result, sd)
  324. }
  325. if err = rows.Err(); err != nil {
  326. log.Println("GetScoresOnDay rows.Err", err)
  327. }
  328. return result
  329. }
  330. type ScoresData struct {
  331. Date int64
  332. User string
  333. Score int
  334. Won int
  335. }
  336. func (db *DBData) GetScores(limit int) []ScoresData {
  337. var result []ScoresData
  338. rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won) "+
  339. "FROM SCORES GROUP BY date, username ORDER BY SUM(score) DESC LIMIT ?;",
  340. limit)
  341. if err != nil {
  342. log.Println("GetScores", err)
  343. return result
  344. }
  345. defer rows.Close()
  346. for rows.Next() {
  347. var sd ScoresData
  348. if err := rows.Scan(&sd.Date, &sd.User, &sd.Score, &sd.Won); err != nil {
  349. log.Println("GetScores Scan", err)
  350. return result
  351. }
  352. result = append(result, sd)
  353. }
  354. if err = rows.Err(); err != nil {
  355. log.Println("GetScores rows.Err", err)
  356. }
  357. return result
  358. }
  359. type MonthlyData struct {
  360. Date int64
  361. User string
  362. Days int
  363. Hands_Won int
  364. Best_Run int
  365. Score int
  366. }
  367. func (db *DBData) GetMonthlyScores(limit int) []MonthlyData {
  368. var result []MonthlyData
  369. rows, err := db.DB.Query("SELECT month, username, days, hands_won, bestrun, score "+
  370. "FROM monthly ORDER BY score DESC LIMIT ?;",
  371. limit)
  372. if err != nil {
  373. log.Println("GetMonthlyScores", err)
  374. return result
  375. }
  376. defer rows.Close()
  377. for rows.Next() {
  378. var md MonthlyData
  379. if err := rows.Scan(&md.Date, &md.User, &md.Days, &md.Hands_Won, &md.Best_Run, &md.Score); err != nil {
  380. log.Println("GetMonthlyScores Scan", err)
  381. return result
  382. }
  383. result = append(result, md)
  384. }
  385. if err = rows.Err(); err != nil {
  386. log.Println("GetMonthlyScores rows.Err", err)
  387. }
  388. return result
  389. }