/xmoto-0.5.10/src/db/xmDatabase.cpp

# · C++ · 1078 lines · 852 code · 153 blank · 73 comment · 69 complexity · 1bda03a3c4daf275e69a62844738daf1 MD5 · raw file

  1. /*=============================================================================
  2. XMOTO
  3. This file is part of XMOTO.
  4. XMOTO is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. XMOTO is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with XMOTO; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  15. =============================================================================*/
  16. #include "xmDatabase.h"
  17. #include "../helpers/VExcept.h"
  18. #include "../GameText.h"
  19. #include "../WWW.h"
  20. #include "../Game.h"
  21. #include "../helpers/Log.h"
  22. #include "../XMSession.h"
  23. #include "../VFileIO.h"
  24. #include <sstream>
  25. #define XMDB_VERSION 36
  26. #define DB_MAX_SQL_RUNTIME 0.25
  27. #define DB_BUSY_TIMEOUT 60000 // 60 seconds
  28. bool xmDatabase::Trace = false;
  29. xmDatabase::xmDatabase()
  30. {
  31. m_db = NULL;
  32. m_openingVersion = -1;
  33. }
  34. void xmDatabase::setUpdateAfterInitDone() {
  35. setXmParameterKey("requireUpdateAfterInit", "0");
  36. }
  37. void xmDatabase::openIfNot(const std::string& i_dbFileUTF8) {
  38. if(m_db != NULL) return;
  39. //LogDebug("openDB(%X)", this);
  40. if(sqlite3_open(i_dbFileUTF8.c_str(), &m_db) != 0) {
  41. throw Exception("Unable to open the database (" + i_dbFileUTF8 + ") : " + sqlite3_errmsg(m_db));
  42. }
  43. sqlite3_busy_timeout(m_db, DB_BUSY_TIMEOUT);
  44. sqlite3_trace(m_db, sqlTrace, NULL);
  45. createUserFunctions();
  46. // if(sqlite3_threadsafe() == 0) {
  47. // LogWarning("Sqlite is not threadSafe !!!");
  48. // } else {
  49. // LogInfo("Sqlite is threadSafe");
  50. // }
  51. m_openingVersion = getXmDbVersion();
  52. }
  53. void xmDatabase::preInitForProfileLoading(const std::string& i_dbFileUTF8) {
  54. openIfNot(i_dbFileUTF8);
  55. // in case your db structure is not exactly the same, add some additionnal checks
  56. if(m_openingVersion == XMDB_VERSION) {
  57. return;
  58. }
  59. // be sure upgrade of xm_parameters table is done -- xm_parameters is required cause to the siteKey required for the profile
  60. if(doesTableExists("xm_parameters") == false) {
  61. simpleSql("CREATE TABLE xm_parameters(param PRIMARY KEY, value);");
  62. }
  63. // be sure upgrade of profiles_configs_configs is done -- profiles_configs is required to query values of profile config
  64. if(doesTableExists("profiles_configs") == false) {
  65. simpleSql("CREATE TABLE profiles_configs(id_profile, key, value, PRIMARY KEY(id_profile, key));");
  66. }
  67. }
  68. void xmDatabase::init(const std::string& i_dbFileUTF8,
  69. const std::string& i_profile,
  70. const std::string& i_gameDataDir,
  71. const std::string& i_userDataDir,
  72. const std::string& i_binPackCheckSum,
  73. bool i_dbDirsCheck,
  74. XmDatabaseUpdateInterface *i_interface) {
  75. m_requiredLevelsUpdateAfterInit = false;
  76. m_requiredReplaysUpdateAfterInit = false;
  77. m_requiredThemesUpdateAfterInit = false;
  78. openIfNot(i_dbFileUTF8);
  79. LogInfo("XmDb version is %i", m_openingVersion);
  80. // mark all objects as requiring an update
  81. // will be set off - aims to allow people killing xmoto while updating
  82. // if the previous value was not 0, force update
  83. std::string v_previousRequireUpdateAfterInit;
  84. if(m_openingVersion > 0) { // the version 0 is special => xm_parameters still doesn't exist
  85. if(getXmParameterKey("requireUpdateAfterInit", v_previousRequireUpdateAfterInit) == false) {
  86. m_requiredLevelsUpdateAfterInit = true;
  87. m_requiredReplaysUpdateAfterInit = true;
  88. m_requiredThemesUpdateAfterInit = true;
  89. } else {
  90. if(v_previousRequireUpdateAfterInit == "1") {
  91. m_requiredLevelsUpdateAfterInit = true;
  92. m_requiredReplaysUpdateAfterInit = true;
  93. m_requiredThemesUpdateAfterInit = true;
  94. }
  95. }
  96. }
  97. if(m_openingVersion > XMDB_VERSION) {
  98. throw Exception("Your XM database required a newer version of xmoto");
  99. }
  100. if(m_openingVersion < XMDB_VERSION) {
  101. LogInfo("Update XmDb version from %i to %i", m_openingVersion, XMDB_VERSION);
  102. if(i_interface != NULL) {
  103. i_interface->updatingDatabase(GAMETEXT_DB_UPGRADING, 0);
  104. }
  105. // now, mark it as required in any case if case the player kills xmoto
  106. if(m_openingVersion > 0) { // the version 0 is special => xm_parameters still doesn't exist
  107. setXmParameterKey("requireUpdateAfterInit", "1");
  108. }
  109. upgradeXmDbToVersion(m_openingVersion, i_profile, i_interface);
  110. }
  111. /* check if gameDir and userDataDir are the same - otherwise, the computer probably changed */
  112. std::string v_oldGameDataDir = getXmDbGameDataDir();
  113. std::string v_oldUserDataDir = getXmDbUserDataDir();
  114. bool v_areDirectoryOK = XMFS::areSamePath(i_gameDataDir, v_oldGameDataDir) && XMFS::areSamePath(i_userDataDir, v_oldUserDataDir);
  115. if((v_areDirectoryOK == false && i_dbDirsCheck) || i_binPackCheckSum != getXmDbBinPackCheckSum()) {
  116. m_requiredLevelsUpdateAfterInit = true;
  117. m_requiredReplaysUpdateAfterInit = true;
  118. m_requiredThemesUpdateAfterInit = true;
  119. // now, mark it as required in any case if case the player kills xmoto
  120. setXmParameterKey("requireUpdateAfterInit", "1");
  121. setXmDbGameDataDir(i_gameDataDir);
  122. setXmDbUserDataDir(i_userDataDir);
  123. setXmDbBinPackCheckSum(i_binPackCheckSum);
  124. /* -- first initialisation or xmoto.bin/userdatadir update -- */
  125. webLoadDataFirstTime();
  126. } else {
  127. // directory are not ok, but check directory is disabled => update the directories anyway
  128. if(v_areDirectoryOK == false) {
  129. // now, mark it as required in any case if case the player kills xmoto
  130. setXmParameterKey("requireUpdateAfterInit", "1");
  131. setXmDbGameDataDir(i_gameDataDir);
  132. setXmDbUserDataDir(i_userDataDir);
  133. try {
  134. updateXMDirectories(v_oldGameDataDir, i_gameDataDir, v_oldUserDataDir, i_userDataDir);
  135. } catch(Exception &e) {
  136. /* forcing update */
  137. m_requiredLevelsUpdateAfterInit = true;
  138. m_requiredReplaysUpdateAfterInit = true;
  139. m_requiredThemesUpdateAfterInit = true;
  140. /* -- first initialisation or xmoto.bin/userdatadir update -- */
  141. webLoadDataFirstTime();
  142. }
  143. }
  144. }
  145. }
  146. void xmDatabase::updateXMDirectories(const std::string& i_oldGameDataDir, const std::string& i_newGameDataDir,
  147. const std::string& i_oldUserDataDir, const std::string& i_newUserDataDir) {
  148. LogInfo("Updating XM directories from %s to %s", i_oldGameDataDir.c_str(), i_newGameDataDir.c_str());
  149. LogInfo("Updating XM directories from %s to %s", i_oldUserDataDir.c_str(), i_newUserDataDir.c_str());
  150. try {
  151. simpleSql("BEGIN TRANSACTION;");
  152. simpleSql("UPDATE levels SET filepath ="
  153. " xm_replaceStart(filepath, \"" + protectString(i_oldGameDataDir) + "\", \"" + protectString(i_newGameDataDir) + "\")"
  154. " WHERE filepath LIKE \"" + protectString(i_oldGameDataDir) + "%\";");
  155. simpleSql("UPDATE themes SET filepath ="
  156. " xm_replaceStart(filepath, \"" + protectString(i_oldGameDataDir) + "\", \"" + protectString(i_newGameDataDir) + "\")"
  157. " WHERE filepath LIKE \"" + protectString(i_oldGameDataDir) + "%\";");
  158. simpleSql("UPDATE levels SET filepath ="
  159. " xm_replaceStart(filepath, \"" + protectString(i_oldUserDataDir) + "\", \"" + protectString(i_newUserDataDir) + "\")"
  160. " WHERE filepath LIKE \"" + protectString(i_oldUserDataDir) + "%\";");
  161. simpleSql("UPDATE themes SET filepath ="
  162. " xm_replaceStart(filepath, \"" + protectString(i_oldUserDataDir) + "\", \"" + protectString(i_newUserDataDir) + "\")"
  163. " WHERE filepath LIKE \"" + protectString(i_oldUserDataDir) + "%\";");
  164. simpleSql("COMMIT;");
  165. } catch(Exception &e) {
  166. simpleSql("ROLLBACK;");
  167. throw e;
  168. }
  169. }
  170. void xmDatabase::init(const std::string& i_dbFile, bool i_readOnly)
  171. {
  172. /* my sqlite version doesn't allow readonly ; wait to allow this */
  173. // if(i_readOnly) {
  174. // LogDebug("Database opened in read-only mode");
  175. // if(sqlite3_open_v2(i_dbFile.c_str(), &m_db, SQLITE_OPEN_READONLY, NULL) != 0){
  176. // if(m_db != NULL) {
  177. // sqlite3_close(m_db); // close even if it fails as requested in the documentation
  178. // m_db = NULL;
  179. // }
  180. // throw Exception("Unable to open the database (" + i_dbFile + ") : " + sqlite3_errmsg(m_db));
  181. // }
  182. // } else {
  183. //
  184. openIfNot(i_dbFile);
  185. }
  186. xmDatabase::~xmDatabase() {
  187. if(m_db != NULL) {
  188. sqlite3_close(m_db);
  189. }
  190. }
  191. void xmDatabase::setTrace(bool i_value) {
  192. xmDatabase::Trace = i_value;
  193. }
  194. void xmDatabase::sqlTrace(void* arg1, const char* sql) {
  195. if(Trace) {
  196. printf("%s\n", sql);
  197. }
  198. }
  199. /* return false if the parameter doesn't exist */
  200. bool xmDatabase::getXmParameterKey(const std::string& i_key, std::string& o_value) {
  201. char **v_result;
  202. unsigned int nrow;
  203. v_result = readDB("SELECT value FROM xm_parameters WHERE param=\"" + protectString(i_key) + "\";", nrow);
  204. if(nrow != 1) {
  205. read_DB_free(v_result);
  206. return false;
  207. }
  208. o_value = getResult(v_result, 1, 0, 0);
  209. read_DB_free(v_result);
  210. return true;
  211. }
  212. std::string xmDatabase::getXmDbGameDataDir() {
  213. std::string v_dir;
  214. if(getXmParameterKey("gameDir", v_dir)) {
  215. return v_dir;
  216. }
  217. return "";
  218. }
  219. std::string xmDatabase::getXmDbUserDataDir() {
  220. std::string v_dir;
  221. if(getXmParameterKey("userDir", v_dir)) {
  222. return v_dir;
  223. }
  224. return "";
  225. }
  226. void xmDatabase::setXmParameterKey(const std::string& i_key, const std::string& i_value) {
  227. char **v_result;
  228. unsigned int nrow;
  229. v_result = readDB("SELECT value FROM xm_parameters WHERE param=\"" + protectString(i_key) + "\";", nrow);
  230. read_DB_free(v_result);
  231. if(nrow == 0) {
  232. simpleSql("INSERT INTO xm_parameters(param, value) VALUES(\"" + protectString(i_key) + "\", \""
  233. + protectString(i_value) + "\");");
  234. } else {
  235. simpleSql("UPDATE xm_parameters SET value=\""
  236. + protectString(i_value) + "\" WHERE param=\"" + protectString(i_key) + "\"");
  237. }
  238. }
  239. void xmDatabase::setXmDbGameDataDir(const std::string& i_gameDataDir) {
  240. setXmParameterKey("gameDir", i_gameDataDir);
  241. }
  242. void xmDatabase::setXmDbUserDataDir(const std::string& i_userDataDir) {
  243. setXmParameterKey("userDir", i_userDataDir);
  244. }
  245. std::string xmDatabase::getXmDbBinPackCheckSum() {
  246. std::string v_sum;
  247. if(getXmParameterKey("binPackCkSum", v_sum)) {
  248. return v_sum;
  249. }
  250. return "";
  251. }
  252. void xmDatabase::setXmDbBinPackCheckSum(const std::string& i_binPackChecksum) {
  253. setXmParameterKey("binPackCkSum", i_binPackChecksum);
  254. }
  255. int xmDatabase::getMemoryUsed() {
  256. return sqlite3_memory_used();
  257. }
  258. bool xmDatabase::doesTableExists(const std::string& i_table) {
  259. return checkKey("SELECT count(1) FROM sqlite_master WHERE type='table' AND name='" + i_table + "';");
  260. }
  261. int xmDatabase::getXmDbVersion() {
  262. char **v_result;
  263. int nrow;
  264. int ncolumn;
  265. char *errMsg;
  266. std::string v_errMsg;
  267. int v_version;
  268. /* check if table xm_parameters exists */
  269. if(doesTableExists("xm_parameters") == false) {
  270. return 0;
  271. }
  272. /* dabatase created, get the version number */
  273. if(sqlite3_get_table(m_db,
  274. "SELECT value FROM xm_parameters WHERE param=\"xmdb_version\";",
  275. &v_result, &nrow, &ncolumn, &errMsg)
  276. != SQLITE_OK) {
  277. v_errMsg = errMsg;
  278. sqlite3_free(errMsg);
  279. throw Exception("Unable to get xmDb version: " + v_errMsg);
  280. }
  281. if(nrow != 1) {
  282. sqlite3_free_table(v_result);
  283. throw Exception("Unable to get xmDb version: " + v_errMsg);
  284. }
  285. v_version = atoi(v_result[1]);
  286. sqlite3_free_table(v_result);
  287. return v_version;
  288. }
  289. void xmDatabase::updateXmDbVersion(int i_newVersion, XmDatabaseUpdateInterface *i_interface) {
  290. std::ostringstream v_newVersion;
  291. std::string v_errMsg;
  292. v_newVersion << i_newVersion;
  293. simpleSql("UPDATE xm_parameters SET value="+ v_newVersion.str() +
  294. " WHERE param=\"xmdb_version\";");
  295. i_interface->updatingDatabase(GAMETEXT_DB_UPGRADING, (int)((i_newVersion*100) / XMDB_VERSION));
  296. }
  297. std::string xmDatabase::getXmDbSiteKey() {
  298. std::string v_sitekey;
  299. if(getXmParameterKey("siteKey", v_sitekey)) {
  300. return v_sitekey;
  301. }
  302. // site key is empty, generate it
  303. return setXmDbSiteKey();
  304. }
  305. std::string xmDatabase::setXmDbSiteKey() {
  306. std::string v_sitekey = generateSiteKey();
  307. setXmParameterKey("siteKey", v_sitekey);
  308. return v_sitekey;
  309. }
  310. std::string xmDatabase::generateSiteKey() {
  311. std::string v_sitekey = "";
  312. struct tm *pTime;
  313. time_t T;
  314. char cBuf[256] = "";
  315. int n;
  316. std::ostringstream v_rd;
  317. time(&T);
  318. pTime = localtime(&T);
  319. if(pTime != NULL) {
  320. snprintf(cBuf, 256, "%d%02d%02d%02d%02d%02d",
  321. pTime->tm_year+1900, pTime->tm_mon+1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min, pTime->tm_sec);
  322. v_sitekey += cBuf;
  323. }
  324. // add random key
  325. n = (int) (10000000.0 * (rand() / (RAND_MAX + 1.0)));
  326. v_rd << n;
  327. v_sitekey += v_rd.str();
  328. //printf("siteKey: %s\n", v_sitekey.c_str());
  329. return v_sitekey;
  330. }
  331. void xmDatabase::upgradeXmDbToVersion(int i_fromVersion,
  332. const std::string& i_profile,
  333. XmDatabaseUpdateInterface *i_interface) {
  334. std::string v_errMsg;
  335. std::string v_sitekey;
  336. char **v_result;
  337. unsigned int nrow;
  338. if(i_fromVersion != 0) { /* cannot create site key if xm_parameters doesn't exist */
  339. v_sitekey = getXmDbSiteKey();
  340. }
  341. /* no break in this swicth ! */
  342. switch(i_fromVersion) {
  343. case 0:
  344. try {
  345. if(doesTableExists("xm_parameters") == false) { // be carefull of preinit
  346. simpleSql("CREATE TABLE xm_parameters(param PRIMARY KEY, value);");
  347. }
  348. simpleSql("INSERT INTO xm_parameters(param, value) VALUES(\"xmdb_version\", 1);");
  349. setXmParameterKey("requireUpdateAfterInit", "1");
  350. v_sitekey = getXmDbSiteKey();
  351. } catch(Exception &e) {
  352. throw Exception("Unable to update xmDb from 0: " + e.getMsg());
  353. }
  354. case 1:
  355. try {
  356. simpleSql("CREATE TABLE stats_profiles(id_profile PRIMARY KEY, nbStarts, since);"
  357. "CREATE TABLE stats_profiles_levels("
  358. "id_profile, id_level, nbPlayed, nbDied, nbCompleted, nbRestarted, playedTime,"
  359. "PRIMARY KEY(id_profile, id_level));");
  360. updateXmDbVersion(2, i_interface);
  361. } catch(Exception &e) {
  362. throw Exception("Unable to update xmDb from 1: " + e.getMsg());
  363. }
  364. try {
  365. updateDB_stats(i_interface);
  366. } catch(Exception &e) {
  367. LogError(std::string("Oups, updateDB_stats() failed: " + e.getMsg()).c_str());
  368. }
  369. case 2:
  370. try {
  371. simpleSql("CREATE TABLE levels(id_level PRIMARY KEY,"
  372. "filepath, name, checkSum, author, description, "
  373. "date_str, packName, packNum, music, isScripted, isToReload);");
  374. updateXmDbVersion(3, i_interface);
  375. m_requiredLevelsUpdateAfterInit = true;
  376. } catch(Exception &e) {
  377. throw Exception("Unable to update xmDb from 2: " + e.getMsg());
  378. }
  379. case 3:
  380. try {
  381. simpleSql("CREATE TABLE levels_new(id_level PRIMARY KEY, isAnUpdate);");
  382. simpleSql("CREATE TABLE levels_favorite(id_profile, id_level, PRIMARY KEY(id_profile, id_level));");
  383. try {
  384. updateDB_favorite(i_profile, i_interface);
  385. } catch(Exception &e) {
  386. LogError(std::string("Oups, updateDB_favorite() failed: " + e.getMsg()).c_str());
  387. }
  388. updateXmDbVersion(4, i_interface);
  389. } catch(Exception &e) {
  390. throw Exception("Unable to update xmDb from 3: " + e.getMsg());
  391. }
  392. case 4:
  393. try {
  394. simpleSql("CREATE TABLE profile_completedLevels("
  395. "id_profile, id_level, timeStamp, finishTime);");
  396. simpleSql("CREATE INDEX profile_completedLevels_idx1 "
  397. "ON profile_completedLevels(id_profile, id_level);");
  398. try {
  399. updateDB_profiles(i_interface);
  400. } catch(Exception &e) {
  401. LogError(std::string("Oups, updateDB_profiles() failed: " + e.getMsg()).c_str());
  402. }
  403. updateXmDbVersion(5, i_interface);
  404. } catch(Exception &e) {
  405. throw Exception("Unable to update xmDb from 4: " + e.getMsg());
  406. }
  407. case 5:
  408. try {
  409. simpleSql("CREATE INDEX levels_packName_idx1 ON levels(packName);");
  410. updateXmDbVersion(6, i_interface);
  411. } catch(Exception &e) {
  412. throw Exception("Unable to update xmDb from 5: " + e.getMsg());
  413. }
  414. case 6:
  415. try {
  416. simpleSql("CREATE table replays(id_level, name PRIMARY KEY, id_profile, isFinished, finishTime);");
  417. simpleSql("CREATE INDEX replays_id_level_idx1 ON replays(id_level);");
  418. simpleSql("CREATE INDEX replays_id_profile_idx1 ON replays(id_profile);");
  419. m_requiredReplaysUpdateAfterInit = true;
  420. updateXmDbVersion(7, i_interface);
  421. } catch(Exception &e) {
  422. throw Exception("Unable to update xmDb from 6: " + e.getMsg());
  423. }
  424. case 7:
  425. try {
  426. simpleSql("CREATE table webhighscores(id_room, id_level, id_profile, finishTime, date, fileUrl, PRIMARY KEY(id_room, id_level));");
  427. simpleSql("CREATE INDEX webhighscores_id_profile_idx1 ON webhighscores(id_profile);");
  428. simpleSql("CREATE INDEX webhighscores_date_idx1 ON webhighscores(date);");
  429. updateXmDbVersion(8, i_interface);
  430. } catch(Exception &e) {
  431. throw Exception("Unable to update xmDb from 7: " + e.getMsg());
  432. }
  433. case 8:
  434. try {
  435. simpleSql("CREATE table weblevels(id_level, name, fileUrl, checkSum, difficulty, quality, creationDate, PRIMARY KEY(id_level));");
  436. simpleSql("CREATE INDEX weblevels_difficulty_idx1 ON weblevels(difficulty);");
  437. simpleSql("CREATE INDEX weblevels_quality_idx1 ON weblevels(quality);");
  438. simpleSql("CREATE INDEX weblevels_creationDate_idx1 ON weblevels(creationDate);");
  439. updateXmDbVersion(9, i_interface);
  440. } catch(Exception &e) {
  441. throw Exception("Unable to update xmDb from 8: " + e.getMsg());
  442. }
  443. case 9:
  444. try {
  445. simpleSql("CREATE table webrooms(id_room PRIMARY KEY, name, highscoresUrl);");
  446. simpleSql("CREATE INDEX weblevels_name_idx1 ON webrooms(name);");
  447. simpleSql("INSERT INTO webrooms(id_room, name, highscoresUrl) VALUES ("
  448. DEFAULT_WEBROOM_ID ", \"" +
  449. protectString(DEFAULT_WEBROOM_NAME) + "\", \"" +
  450. protectString(DEFAULT_WEBHIGHSCORES_URL) + "\");");
  451. updateXmDbVersion(10, i_interface);
  452. } catch(Exception &e) {
  453. throw Exception("Unable to update xmDb from 9: " + e.getMsg());
  454. }
  455. case 10:
  456. try {
  457. simpleSql("CREATE table themes(id_theme PRIMARY KEY, filepath, checkSum);");
  458. m_requiredThemesUpdateAfterInit = true;
  459. updateXmDbVersion(11, i_interface);
  460. } catch(Exception &e) {
  461. throw Exception("Unable to update xmDb from 10: " + e.getMsg());
  462. }
  463. case 11:
  464. try {
  465. simpleSql("CREATE table webthemes(id_theme PRIMARY KEY, fileUrl, checkSum);");
  466. updateXmDbVersion(12, i_interface);
  467. } catch(Exception &e) {
  468. throw Exception("Unable to update xmDb from 11: " + e.getMsg());
  469. }
  470. case 12:
  471. try {
  472. simpleSql("ALTER TABLE stats_profiles_levels ADD COLUMN last_play_date DEFAULT NULL;");
  473. simpleSql("CREATE INDEX stats_profiles_levels_last_play_date_idx1 ON stats_profiles_levels(last_play_date);");
  474. updateXmDbVersion(13, i_interface);
  475. } catch(Exception &e) {
  476. throw Exception("Unable to update xmDb from 12: " + e.getMsg());
  477. }
  478. case 13:
  479. try {
  480. simpleSql("ALTER TABLE weblevels ADD COLUMN crappy DEFAULT 0;");
  481. simpleSql("CREATE INDEX weblevels_crappy_idx1 ON weblevels(crappy);");
  482. updateXmDbVersion(14, i_interface);
  483. } catch(Exception &e) {
  484. throw Exception("Unable to update xmDb from 13: " + e.getMsg());
  485. }
  486. case 14:
  487. try {
  488. simpleSql("CREATE INDEX webhighscores_finishTime_idx1 ON webhighscores(finishTime);");
  489. updateXmDbVersion(15, i_interface);
  490. } catch(Exception &e) {
  491. throw Exception("Unable to update xmDb from 14: " + e.getMsg());
  492. }
  493. case 15:
  494. try {
  495. simpleSql("CREATE TABLE levels_blacklist(id_profile, id_level, PRIMARY KEY(id_profile, id_level));");
  496. updateXmDbVersion(16, i_interface);
  497. } catch(Exception &e) {
  498. throw Exception("Unable to update xmDb from 15: " + e.getMsg());
  499. }
  500. case 16:
  501. try {
  502. simpleSql("CREATE INDEX webhighscores_id_level_idx1 ON webhighscores(id_level);");
  503. updateXmDbVersion(17, i_interface);
  504. } catch(Exception &e) {
  505. throw Exception("Unable to update xmDb from 16: " + e.getMsg());
  506. }
  507. case 17:
  508. try {
  509. // fix stats_profiles_levels due to old old old xmoto versions
  510. fixStatsProfilesLevelsNbCompleted();
  511. updateXmDbVersion(18, i_interface);
  512. } catch(Exception &e) {
  513. throw Exception("Unable to update xmDb from 17: " + e.getMsg());
  514. }
  515. case 18: // from float to int storage for time
  516. try {
  517. m_requiredReplaysUpdateAfterInit = true;
  518. simpleSql("DELETE from webhighscores;");
  519. simpleSql("UPDATE profile_completedLevels SET finishTime=finishTime*100;");
  520. simpleSql("UPDATE stats_profiles_levels SET playedTime=playedTime*100;");
  521. updateXmDbVersion(19, i_interface);
  522. } catch(Exception &e) {
  523. throw Exception("Unable to update xmDb from 18: " + e.getMsg());
  524. }
  525. case 19:
  526. try {
  527. simpleSql("ALTER TABLE weblevels ADD COLUMN children_compliant DEFAULT 1;");
  528. simpleSql("CREATE INDEX weblevels_children_compliant_idx1 ON weblevels(children_compliant);");
  529. updateXmDbVersion(20, i_interface);
  530. } catch(Exception &e) {
  531. throw Exception("Unable to update xmDb from 19: " + e.getMsg());
  532. }
  533. case 20:
  534. try {
  535. if(doesTableExists("profiles_configs") == false) { // be carefull of preinit
  536. simpleSql("CREATE TABLE profiles_configs(id_profile, key, value, PRIMARY KEY(id_profile, key));");
  537. }
  538. updateXmDbVersion(21, i_interface);
  539. } catch(Exception &e) {
  540. throw Exception("Unable to update xmDb from 20: " + e.getMsg());
  541. }
  542. case 21:
  543. try {
  544. try {
  545. updateDB_config("" /* site key still does exist */);
  546. } catch(Exception &e) {
  547. // ok, no upgrade
  548. }
  549. updateXmDbVersion(22, i_interface);
  550. } catch(Exception &e) {
  551. throw Exception("Unable to update xmDb from 21: " + e.getMsg());
  552. }
  553. case 22:
  554. try {
  555. // add column sitekey as primary key - sqlite doesn't support that without recreating the table
  556. simpleSql("ALTER TABLE stats_profiles RENAME TO stats_profiles_old;");
  557. simpleSql("CREATE TABLE stats_profiles(sitekey, id_profile, nbStarts, since, PRIMARY KEY(sitekey, id_profile));");
  558. simpleSql("INSERT INTO stats_profiles SELECT \"" + protectString(v_sitekey) + "\", id_profile, nbStarts, since FROM stats_profiles_old;");
  559. simpleSql("DROP TABLE stats_profiles_old;");
  560. updateXmDbVersion(23, i_interface);
  561. } catch(Exception &e) {
  562. throw Exception("Unable to update xmDb from 22: " + e.getMsg());
  563. }
  564. case 23:
  565. try {
  566. // add column sitekey as primary key - sqlite doesn't support that without recreating the table
  567. simpleSql("ALTER TABLE stats_profiles_levels RENAME TO stats_profiles_levels_old;");
  568. simpleSql("CREATE TABLE stats_profiles_levels(sitekey, id_profile, id_level, nbPlayed, nbDied, nbCompleted, nbRestarted, playedTime, last_play_date DEFAULT NULL, synchronized, PRIMARY KEY(sitekey, id_profile, id_level));");
  569. simpleSql("INSERT INTO stats_profiles_levels SELECT \"" + protectString(v_sitekey) + "\", id_profile, id_level, nbPlayed, nbDied, nbCompleted, nbRestarted, playedTime, last_play_date, 0 FROM stats_profiles_levels_old;");
  570. simpleSql("DROP TABLE stats_profiles_levels_old;");
  571. /* recreate the index */
  572. simpleSql("CREATE INDEX stats_profiles_levels_last_play_date_idx1 ON stats_profiles_levels(last_play_date);");
  573. // index not redondante with the key while it is choiced when sitekey is not set ; make xmoto really faster
  574. simpleSql("CREATE INDEX stats_profiles_levels_id_profile_id_level_idx1 ON stats_profiles_levels(id_profile, id_level);");
  575. updateXmDbVersion(24, i_interface);
  576. } catch(Exception &e) {
  577. throw Exception("Unable to update xmDb from 23: " + e.getMsg());
  578. }
  579. case 24:
  580. try {
  581. simpleSql("ALTER TABLE profile_completedLevels ADD COLUMN sitekey;");
  582. simpleSql("ALTER TABLE profile_completedLevels ADD COLUMN synchronized;");
  583. simpleSql("UPDATE profile_completedLevels SET sitekey = \"" + protectString(v_sitekey) + "\";");
  584. simpleSql("UPDATE profile_completedLevels SET synchronized = 0;");
  585. simpleSql("DROP INDEX profile_completedLevels_idx1;");
  586. /* recreate the index */
  587. simpleSql("CREATE INDEX profile_completedLevels_idx1 "
  588. "ON profile_completedLevels(sitekey, id_profile, id_level);");
  589. // index not redondante with the idx1 while it is choiced when sitekey is not set ; make xmoto really faster
  590. simpleSql("CREATE INDEX profile_completedLevels_idx2 "
  591. "ON profile_completedLevels(id_profile, id_level);");
  592. updateXmDbVersion(25, i_interface);
  593. } catch(Exception &e) {
  594. throw Exception("Unable to update xmDb from 24: " + e.getMsg());
  595. }
  596. case 25:
  597. try {
  598. simpleSql("ALTER TABLE levels ADD COLUMN isPhysics DEFAULT 0;");
  599. simpleSql("CREATE INDEX levels_isPhysics_idx1 ON levels(isPhysics);");
  600. updateXmDbVersion(26, i_interface);
  601. m_requiredLevelsUpdateAfterInit = true;
  602. } catch(Exception &e) {
  603. throw Exception("Unable to update xmDb from 25: " + e.getMsg());
  604. }
  605. case 26:
  606. try {
  607. simpleSql("ALTER TABLE profile_completedLevels ADD COLUMN dbSync DEFAULT NULL;");
  608. simpleSql("CREATE INDEX profile_completedLevels_dbSync_idx1 ON profile_completedLevels(dbSync);");
  609. simpleSql("ALTER TABLE stats_profiles_levels ADD COLUMN dbSync DEFAULT NULL;");
  610. simpleSql("CREATE INDEX stats_profiles_levels_dbSync_idx1 ON stats_profiles_levels(dbSync);");
  611. updateXmDbVersion(27, i_interface);
  612. } catch(Exception &e) {
  613. throw Exception("Unable to update xmDb from 26: " + e.getMsg());
  614. }
  615. case 27:
  616. try {
  617. simpleSql("CREATE TABLE profiles_votes(id_profile, id_level, PRIMARY KEY(id_profile, id_level));");
  618. updateXmDbVersion(28, i_interface);
  619. } catch(Exception &e) {
  620. throw Exception("Unable to update xmDb from 27: " + e.getMsg());
  621. }
  622. case 28:
  623. try {
  624. simpleSql("ALTER TABLE levels ADD COLUMN loadingCacheFormatVersion DEFAULT NULL;");
  625. simpleSql("ALTER TABLE levels ADD COLUMN loaded DEFAULT NULL;");
  626. simpleSql("CREATE INDEX levels_checkSum_idx1 ON levels(checkSum);");
  627. updateXmDbVersion(29, i_interface);
  628. } catch(Exception &e) {
  629. throw Exception("Unable to update xmDb from 28: " + e.getMsg());
  630. }
  631. case 29:
  632. try {
  633. simpleSql("CREATE TABLE levels_mywebhighscores(id_profile, id_room, id_level, PRIMARY KEY(id_profile, id_room, id_level));");
  634. updateXmDbVersion(30, i_interface);
  635. } catch(Exception &e) {
  636. throw Exception("Unable to update xmDb from 29: " + e.getMsg());
  637. }
  638. case 30:
  639. try {
  640. simpleSql("ALTER TABLE levels_mywebhighscores ADD COLUMN known_stolen DEFAULT 0;");
  641. simpleSql("ALTER TABLE levels_mywebhighscores ADD COLUMN known_stolen_date;");
  642. simpleSql("CREATE INDEX levels_mywebhighscores_idx1 ON levels_mywebhighscores(known_stolen);");
  643. simpleSql("CREATE INDEX levels_mywebhighscores_idx2 ON levels_mywebhighscores(known_stolen_date);");
  644. updateXmDbVersion(31, i_interface);
  645. } catch(Exception &e) {
  646. throw Exception("Unable to update xmDb from 30: " + e.getMsg());
  647. }
  648. case 31:
  649. try {
  650. simpleSql("CREATE TABLE srv_admins(id INTEGER PRIMARY KEY AUTOINCREMENT, id_profile UNIQUE, password);");
  651. updateXmDbVersion(32, i_interface);
  652. } catch(Exception &e) {
  653. throw Exception("Unable to update xmDb from 31: " + e.getMsg());
  654. }
  655. case 32:
  656. try {
  657. simpleSql("CREATE TABLE srv_bans(id INTEGER PRIMARY KEY AUTOINCREMENT, id_profile, ip, from_date, nb_days);");
  658. updateXmDbVersion(33, i_interface);
  659. } catch(Exception &e) {
  660. throw Exception("Unable to update xmDb from 32: " + e.getMsg());
  661. }
  662. case 33:
  663. try {
  664. simpleSql("ALTER TABLE weblevels ADD COLUMN vote_locked DEFAULT 0;");
  665. simpleSql("CREATE INDEX weblevels_vote_locked_idx1 ON weblevels(vote_locked);");
  666. updateXmDbVersion(34, i_interface);
  667. } catch(Exception &e) {
  668. throw Exception("Unable to update xmDb from 33: " + e.getMsg());
  669. }
  670. case 34:
  671. try {
  672. simpleSql("ALTER TABLE weblevels ADD COLUMN packname;");
  673. simpleSql("ALTER TABLE weblevels ADD COLUMN packnum;");
  674. simpleSql("CREATE INDEX weblevels_packname_idx1 ON weblevels(packname);");
  675. simpleSql("DROP INDEX levels_packName_idx1;");
  676. // do not use level.packname anymore
  677. // update weblevels table from levels for the first time
  678. try {
  679. std::string v_id_level, v_packname, v_packnum;
  680. simpleSql("BEGIN TRANSACTION;");
  681. v_result = readDB(std::string("SELECT id_level, packname, packnum FROM levels WHERE packname <> '';"), nrow);
  682. for(unsigned int i=0; i<nrow; i++) {
  683. v_id_level = getResult(v_result, 3, i, 0);
  684. v_packname = getResult(v_result, 3, i, 1);
  685. v_packnum = getResult(v_result, 3, i, 2);
  686. simpleSql("UPDATE weblevels SET packname=\"" + xmDatabase::protectString(v_packname) +
  687. "\", packnum=\"" + xmDatabase::protectString(v_packnum) +
  688. "\" WHERE id_level=\"" + xmDatabase::protectString(v_id_level) + "\";");
  689. }
  690. read_DB_free(v_result);
  691. // remove information for the table levels
  692. simpleSql("UPDATE levels SET packname='', packnum='';");
  693. simpleSql("COMMIT;");
  694. } catch(Exception &e) {
  695. /* ok, the player will have to update weblevels via internet */
  696. simpleSql("COMMIT;"); // anyway, commit what can be commited
  697. }
  698. updateXmDbVersion(35, i_interface);
  699. } catch(Exception &e) {
  700. throw Exception("Unable to update xmDb from 34: " + e.getMsg());
  701. }
  702. case 35:
  703. try {
  704. simpleSql("ALTER TABLE srv_bans ADD COLUMN id_admin_banner;");
  705. updateXmDbVersion(36, i_interface);
  706. } catch(Exception &e) {
  707. throw Exception("Unable to update xmDb from 35: " + e.getMsg());
  708. }
  709. // next
  710. }
  711. }
  712. bool xmDatabase::checkKey(const std::string& i_sql) {
  713. char **v_result;
  714. int nrow, ncolumn;
  715. char *errMsg;
  716. std::string v_errMsg;
  717. int v_nb;
  718. if(sqlite3_get_table(m_db,
  719. i_sql.c_str(),
  720. &v_result, &nrow, &ncolumn, &errMsg)
  721. != SQLITE_OK) {
  722. v_errMsg = errMsg;
  723. sqlite3_free(errMsg);
  724. throw Exception("xmDb: " + v_errMsg);
  725. }
  726. v_nb = atoi(v_result[1]);
  727. sqlite3_free_table(v_result);
  728. return v_nb != 0;
  729. }
  730. void xmDatabase::simpleSql(const std::string& i_sql) {
  731. char *errMsg;
  732. std::string v_errMsg;
  733. //LogDebug("simpleSql(%X): %s", this, i_sql.c_str());
  734. if(sqlite3_exec(m_db,
  735. i_sql.c_str(),
  736. NULL,
  737. NULL, &errMsg) != SQLITE_OK) {
  738. v_errMsg = errMsg;
  739. sqlite3_free(errMsg);
  740. LogInfo(std::string("simpleSql failed on running : " + i_sql).c_str());
  741. throw Exception(v_errMsg);
  742. }
  743. }
  744. void xmDatabase::debugResult(char **i_result, int ncolumn, unsigned int nrow) {
  745. for(unsigned int i=0; i<ncolumn*(nrow+1); i++) {
  746. printf("result[%i] = %s\n", i, i_result[i]);
  747. }
  748. }
  749. char* xmDatabase::getResult(char **i_result, int ncolumn, unsigned int i_row, int i_column) {
  750. return i_result[ncolumn*(i_row+1) + i_column];
  751. }
  752. char** xmDatabase::readDB(const std::string& i_sql, unsigned int &i_nrow) {
  753. char **v_result;
  754. int ncolumn;
  755. char *errMsg;
  756. std::string v_errMsg;
  757. int v_nrow;
  758. double v_startTime, v_endTime;
  759. //LogDebug("readDB(%X): %s", this, i_sql.c_str());
  760. v_startTime = GameApp::getXMTime();
  761. if(sqlite3_get_table(m_db,
  762. i_sql.c_str(),
  763. &v_result, &v_nrow, &ncolumn, &errMsg)
  764. != SQLITE_OK) {
  765. v_errMsg = errMsg;
  766. sqlite3_free(errMsg);
  767. LogError("xmDb failed while running :");
  768. LogInfo("%s", i_sql.c_str());
  769. LogError("%s", v_errMsg.c_str())
  770. throw Exception("xmDb: " + v_errMsg);
  771. }
  772. v_endTime = GameApp::getXMTime();
  773. if(v_endTime - v_startTime > DB_MAX_SQL_RUNTIME) {
  774. LogWarning("long query time detected (%.3f'') for query '%s'", v_endTime - v_startTime, i_sql.c_str());
  775. }
  776. i_nrow = (unsigned int) v_nrow;
  777. return v_result;
  778. }
  779. void xmDatabase::read_DB_free(char **i_result) {
  780. sqlite3_free_table(i_result);
  781. }
  782. std::string xmDatabase::protectString(const std::string& i_str) {
  783. std::string v_res;
  784. for(unsigned int i=0; i<i_str.length(); i++) {
  785. switch(i_str[i]) {
  786. case '"':
  787. v_res.append("\"\"");
  788. break;
  789. default:
  790. char c[2] = {i_str[i], '\0'};
  791. v_res.append(c);
  792. }
  793. }
  794. return v_res;
  795. }
  796. void xmDatabase::createUserFunctions() {
  797. if(sqlite3_create_function(m_db, "xm_floord", 1, SQLITE_ANY, NULL,
  798. user_xm_floord, NULL, NULL) != SQLITE_OK) {
  799. throw Exception("xmDatabase::createUserFunctions() failed !");
  800. }
  801. if(sqlite3_create_function(m_db, "xm_lvlUpdatedToTxt", 1, SQLITE_ANY, NULL,
  802. user_xm_lvlUpdatedToTxt, NULL, NULL) != SQLITE_OK) {
  803. throw Exception("xmDatabase::createUserFunctions() failed !");
  804. }
  805. if(sqlite3_create_function(m_db, "xm_userCrappy", 1, SQLITE_ANY, NULL,
  806. user_xm_userCrappy, NULL, NULL) != SQLITE_OK) {
  807. throw Exception("xmDatabase::createUserFunctions() failed !");
  808. }
  809. if(sqlite3_create_function(m_db, "xm_userChildrenCompliant", 1, SQLITE_ANY, NULL,
  810. user_xm_userChildrenCompliant, NULL, NULL) != SQLITE_OK) {
  811. throw Exception("xmDatabase::createUserFunctions() failed !");
  812. }
  813. if(sqlite3_create_function(m_db, "xm_replaceStart", 3, SQLITE_ANY, NULL,
  814. user_xm_replaceStart, NULL, NULL) != SQLITE_OK) {
  815. throw Exception("xmDatabase::createUserFunctions() failed !");
  816. }
  817. if(sqlite3_create_function(m_db, "xm_profile", 0, SQLITE_ANY, NULL,
  818. user_xm_profile, NULL, NULL) != SQLITE_OK) {
  819. throw Exception("xmDatabase::createUserFunctions() failed !");
  820. }
  821. if(sqlite3_create_function(m_db, "xm_idRoom", 1, SQLITE_ANY, NULL,
  822. user_xm_idRoom, NULL, NULL) != SQLITE_OK) {
  823. throw Exception("xmDatabase::createUserFunctions() failed !");
  824. }
  825. }
  826. void xmDatabase::user_xm_floord(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  827. double v_value;
  828. if(i_nArgs != 1) {
  829. throw Exception("user_xm_floord failed !");
  830. }
  831. v_value = sqlite3_value_double(i_values[0]);
  832. sqlite3_result_double(i_context, (double)((int)(v_value)));
  833. }
  834. void xmDatabase::user_xm_lvlUpdatedToTxt(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  835. int v_value;
  836. if(i_nArgs != 1) {
  837. throw Exception("user_xm_lvlUpdatedToTxt failed !");
  838. }
  839. v_value = sqlite3_value_int(i_values[0]);
  840. sqlite3_result_text(i_context, v_value == 0 ? GAMETEXT_NEW : GAMETEXT_UPDATED, -1, SQLITE_TRANSIENT);
  841. }
  842. void xmDatabase::user_xm_userCrappy(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  843. int v_value;
  844. if(i_nArgs != 1) {
  845. throw Exception("user_xm_userCrappy failed !");
  846. }
  847. v_value = sqlite3_value_int(i_values[0]);
  848. sqlite3_result_int(i_context, XMSession::instance()->useCrappyPack() ? v_value : 0);
  849. }
  850. void xmDatabase::user_xm_userChildrenCompliant(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  851. int v_value;
  852. if(i_nArgs != 1) {
  853. throw Exception("user_xm_userChildrenCompliant failed !");
  854. }
  855. v_value = sqlite3_value_int(i_values[0]);
  856. sqlite3_result_int(i_context, XMSession::instance()->useChildrenCompliant() ? v_value : 1);
  857. }
  858. void xmDatabase::user_xm_replaceStart(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  859. std::string v_value, v_old, v_new, v_result, v_begin, v_end;
  860. if(i_nArgs != 3) {
  861. throw Exception("user_xm_replaceStart failed !");
  862. }
  863. v_value = (char*) sqlite3_value_text(i_values[0]);
  864. v_old = (char*) sqlite3_value_text(i_values[1]);
  865. v_new = (char*) sqlite3_value_text(i_values[2]);
  866. v_begin = v_value.substr(0, v_old.length());
  867. if(v_begin != v_old) {
  868. /* change nothing */
  869. sqlite3_result_text(i_context, v_value.c_str(), -1, SQLITE_TRANSIENT);
  870. return;
  871. }
  872. v_end = v_value.substr(v_old.length(), v_value.length() - v_old.length());
  873. v_result = v_new + v_end;
  874. sqlite3_result_text(i_context, v_result.c_str(), -1, SQLITE_TRANSIENT);
  875. }
  876. void xmDatabase::user_xm_profile(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  877. if(i_nArgs != 0) {
  878. throw Exception("user_xm_profile failed !");
  879. }
  880. sqlite3_result_text(i_context, XMSession::instance()->profile().c_str(), XMSession::instance()->profile().size(), NULL);
  881. }
  882. void xmDatabase::user_xm_idRoom(sqlite3_context* i_context, int i_nArgs, sqlite3_value** i_values) {
  883. int v_value;
  884. if(i_nArgs != 1) {
  885. throw Exception("user_xm_idRoom failed !");
  886. }
  887. v_value = sqlite3_value_int(i_values[0]);
  888. sqlite3_result_int(i_context, atoi(XMSession::instance()->idRoom(v_value).c_str()));
  889. }