db.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #include "db.h"
  2. #include <SQLiteCpp/VariadicBind.h>
  3. #include <iomanip>
  4. #include <iostream>
  5. #include <sstream>
  6. // configuration settings access
  7. #include "yaml-cpp/yaml.h"
  8. extern YAML::Node config;
  9. #include <fstream>
  10. #include <functional>
  11. extern std::function<std::ofstream &(void)> get_logger;
  12. /*
  13. The database access is slow.
  14. So, make sure you set it up so that you do your writes right
  15. before you collect user input. That way, the user won't see
  16. the lags.
  17. This might be an issue on rPI systems!
  18. Change the strategy so we only update when the game ends.
  19. */
  20. DBData::DBData(void)
  21. : db("space-data.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {
  22. create_tables();
  23. stmt_getSet = std::make_unique<SQLite::Statement>(
  24. db, "SELECT value FROM settings WHERE username=? AND setting=?");
  25. stmt_setSet = std::make_unique<SQLite::Statement>(
  26. db, "REPLACE INTO settings(username, setting, value) VALUES(?,?,?);");
  27. }
  28. DBData::~DBData() {}
  29. /**
  30. * @brief create tables if they don't exist.
  31. */
  32. void DBData::create_tables(void) {
  33. try {
  34. db.exec("CREATE TABLE IF NOT EXISTS \
  35. settings(username TEXT, setting TEXT, value TEXT, \
  36. PRIMARY KEY(username, setting));");
  37. db.exec("CREATE TABLE IF NOT EXISTS \
  38. scores ( \"username\" TEXT, \"when\" INTEGER, \
  39. \"date\" INTEGER, \"hand\" INTEGER, \"score\" INTEGER, \
  40. PRIMARY KEY(\"username\", \"date\", \"hand\"));");
  41. } catch (std::exception &e) {
  42. if (get_logger) {
  43. get_logger() << "create_tables():" << std::endl;
  44. get_logger() << "SQLite exception: " << e.what() << std::endl;
  45. }
  46. }
  47. }
  48. /**
  49. * @brief get setting from the settings table
  50. *
  51. * We use user and setting.
  52. * Return isMissing if not found.
  53. *
  54. * @param setting
  55. * @param ifMissing
  56. * @return std::string
  57. */
  58. std::string DBData::getSetting(const std::string &setting,
  59. std::string ifMissing) {
  60. try {
  61. stmt_getSet->reset();
  62. stmt_getSet->bind(1, user);
  63. stmt_getSet->bind(2, setting);
  64. if (stmt_getSet->executeStep()) {
  65. std::string value = stmt_getSet->getColumn(0);
  66. return value;
  67. };
  68. return ifMissing;
  69. } catch (std::exception &e) {
  70. if (get_logger) {
  71. get_logger() << "getSettings( " << setting << "," << ifMissing
  72. << " ): " << user << std::endl;
  73. get_logger() << "SQLite exception: " << e.what() << std::endl;
  74. }
  75. }
  76. return ifMissing;
  77. }
  78. /**
  79. * @brief save setting in the settings table
  80. *
  81. * We save user setting in the settings table.
  82. * We use SQLite's REPLACE INTO so it does an update if it exists, or an insert
  83. * if it is missing.
  84. * @param setting
  85. * @param value
  86. */
  87. void DBData::setSetting(const std::string &setting, const std::string &value) {
  88. try {
  89. stmt_setSet->reset();
  90. stmt_setSet->bind(1, user);
  91. stmt_setSet->bind(2, setting);
  92. stmt_setSet->bind(3, value);
  93. stmt_setSet->exec();
  94. } catch (std::exception &e) {
  95. if (get_logger) {
  96. get_logger() << "setSettings( " << setting << "," << value
  97. << " ): " << user << std::endl;
  98. get_logger() << "SQLite exception: " << e.what() << std::endl;
  99. }
  100. }
  101. }
  102. /**
  103. * @brief save the user's score
  104. *
  105. * @param when now()
  106. * @param date what day they played
  107. * @param hand which hand they played
  108. * @param score
  109. */
  110. void DBData::saveScore(time_t when, time_t date, int hand, int score) {
  111. try {
  112. SQLite::Statement stmt(db,
  113. "INSERT INTO scores( \"username\", \"when\", "
  114. "\"date\", \"hand\", \"score\") VALUES(?,?,?,?,?);");
  115. stmt.bind(1, user);
  116. stmt.bind(2, when);
  117. stmt.bind(3, date);
  118. stmt.bind(4, hand);
  119. stmt.bind(5, score);
  120. stmt.exec();
  121. } catch (std::exception &e) {
  122. if (get_logger) {
  123. get_logger() << "saveScore( " << when << "," << date << "," << hand << ","
  124. << score << " ): " << user << std::endl;
  125. get_logger() << "SQLite exception: " << e.what() << std::endl;
  126. }
  127. }
  128. }
  129. /**
  130. * @brief Returns number of hands played on given day
  131. *
  132. * returns number of hands they played, or 0
  133. * @param day
  134. * @return int
  135. */
  136. int DBData::handsPlayedOnDay(time_t day) {
  137. try {
  138. SQLite::Statement stmt(
  139. db, "SELECT COUNT(*) FROM scores WHERE \"username\"=? AND \"DATE\"=?;");
  140. stmt.bind(1, user);
  141. stmt.bind(2, day);
  142. int count = 0;
  143. if (stmt.executeStep()) {
  144. count = stmt.getColumn(0);
  145. };
  146. return count;
  147. } catch (std::exception &e) {
  148. if (get_logger) {
  149. get_logger() << "handsPlayedOnDay( " << day << " ): " << user
  150. << std::endl;
  151. get_logger() << "SQLite exception: " << e.what() << std::endl;
  152. }
  153. }
  154. return 0;
  155. }
  156. /**
  157. * @brief Format date to string.
  158. *
  159. * We use default "%0m/%0d/%Y", but can be configured by SysOp via
  160. * config["date_score"] setting. "%Y/%0m/%0d" for non-US?
  161. *
  162. * @param tt
  163. * @return std::string
  164. */
  165. std::string convertDateToDateScoreFormat(time_t tt) {
  166. std::stringstream ss;
  167. if (config["date_score"]) {
  168. std::string custom_format = config["date_score"].as<std::string>();
  169. ss << std::put_time(std::localtime(&tt), custom_format.c_str());
  170. } else {
  171. ss << std::put_time(std::localtime(&tt), "%0m/%0d/%Y");
  172. }
  173. std::string date = ss.str();
  174. return date;
  175. }
  176. /**
  177. * @brief change datetime to have consistent time
  178. *
  179. * This converts the time part to hour:00
  180. *
  181. * @param tt
  182. * @param hour
  183. */
  184. void normalizeDate(time_t &tt, int hour) {
  185. std::tm *local_tm = localtime(&tt);
  186. // adjust date to 2:00:00 AM
  187. tt -= (local_tm->tm_min * 60) + local_tm->tm_sec;
  188. while (local_tm->tm_hour < hour) {
  189. ++local_tm->tm_hour;
  190. tt += 60 * 60;
  191. }
  192. if (local_tm->tm_hour > hour) {
  193. tt -= (60 * 60) * (local_tm->tm_hour - hour);
  194. }
  195. }