AutopatcherMySQLRepository.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. /*
  2. * Copyright (c) 2014, Oculus VR, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. */
  10. #include "RakString.h"
  11. #include "AutopatcherMySQLRepository.h"
  12. #include "AutopatcherPatchContext.h"
  13. #include "FileList.h"
  14. #include "RakAssert.h"
  15. #include "DS_List.h"
  16. // ntohl
  17. #ifdef _WIN32
  18. #include <Winsock2.h>
  19. #else
  20. #include <netinet/in.h>
  21. #endif
  22. // If you get fatal error C1083: Cannot open include file: 'mysql.h' then you need to install MySQL. See readme.txt in this sample directory.
  23. #include "mysql.h"
  24. #include "CreatePatch.h"
  25. #include "AutopatcherPatchContext.h"
  26. // #include "DR_SHA1.h"
  27. #include <stdlib.h>
  28. #include "LinuxStrings.h"
  29. using namespace RakNet;
  30. static const unsigned HASH_LENGTH=sizeof(unsigned int);
  31. struct FileInfo
  32. {
  33. RakNet::RakString filename;
  34. char contentHash [HASH_LENGTH];
  35. bool createFile;
  36. };
  37. // alloca
  38. #ifdef _COMPATIBILITY_1
  39. #elif defined(_WIN32)
  40. #include <malloc.h>
  41. #else
  42. //#include <stdlib.h>
  43. #endif
  44. #define PQEXECPARAM_FORMAT_TEXT 0
  45. #define PQEXECPARAM_FORMAT_BINARY 1
  46. AutopatcherMySQLRepository::AutopatcherMySQLRepository()
  47. {
  48. filePartConnection=0;
  49. }
  50. AutopatcherMySQLRepository::~AutopatcherMySQLRepository()
  51. {
  52. if (filePartConnection)
  53. mysql_close(filePartConnection);
  54. }
  55. bool AutopatcherMySQLRepository::CreateAutopatcherTables(void)
  56. {
  57. if (!IsConnected())
  58. return false;
  59. //sqlCommandMutex.Lock();
  60. ExecuteBlockingCommand("BEGIN;");
  61. if (!ExecuteBlockingCommand(
  62. "CREATE TABLE Applications ("
  63. "applicationID INT AUTO_INCREMENT,"
  64. "applicationName VARCHAR(255) NOT NULL UNIQUE,"
  65. "changeSetID integer NOT NULL DEFAULT 0,"
  66. "userName TEXT NOT NULL,"
  67. "PRIMARY KEY (applicationID));"
  68. )) {Rollback(); return false;}
  69. if (!ExecuteBlockingCommand(
  70. "CREATE TABLE FileVersionHistory ("
  71. "fileID INT AUTO_INCREMENT,"
  72. "applicationID INT NOT NULL, "
  73. "filename VARCHAR(255) NOT NULL,"
  74. "fileLength INT,"
  75. "content LONGBLOB,"
  76. "contentHash TINYBLOB,"
  77. "patch LONGBLOB,"
  78. "createFile TINYINT NOT NULL,"
  79. "modificationDate double precision DEFAULT (EXTRACT(EPOCH FROM now())),"
  80. "lastSentDate double precision,"
  81. "timesSent INT NOT NULL DEFAULT 0,"
  82. "changeSetID INT NOT NULL,"
  83. "userName TEXT NOT NULL,"
  84. "PRIMARY KEY (fileID)); "
  85. )) {Rollback(); return false;}
  86. if (!ExecuteBlockingCommand(
  87. "CREATE INDEX FV_appID on FileVersionHistory(applicationID);"
  88. )) {Rollback(); return false;}
  89. if (!ExecuteBlockingCommand(
  90. "CREATE INDEX FV_fname on FileVersionHistory(filename);"
  91. )) {Rollback(); return false;}
  92. if (!ExecuteBlockingCommand(
  93. "CREATE VIEW AutoPatcherView AS SELECT "
  94. "FileVersionHistory.applicationid,"
  95. "Applications.applicationName,"
  96. "FileVersionHistory.fileID,"
  97. "FileVersionHistory.fileName,"
  98. "FileVersionHistory.createFile,"
  99. "FileVersionHistory.fileLength,"
  100. "FileVersionHistory.changeSetID,"
  101. "FileVersionHistory.lastSentDate,"
  102. "FileVersionHistory.modificationDate,"
  103. "FileVersionHistory.timesSent "
  104. "FROM (FileVersionHistory JOIN Applications ON "
  105. "( FileVersionHistory.applicationID = Applications.applicationID )) "
  106. "ORDER BY Applications.applicationID ASC, FileVersionHistory.fileID ASC;"
  107. )) {Rollback(); return false;}
  108. bool b = ExecuteBlockingCommand("COMMIT;");
  109. //sqlCommandMutex.Unlock();
  110. return b;
  111. }
  112. bool AutopatcherMySQLRepository::DestroyAutopatcherTables(void)
  113. {
  114. if (!IsConnected())
  115. return false;
  116. //sqlCommandMutex.Lock();
  117. ExecuteBlockingCommand("DROP INDEX FV_appID;");
  118. ExecuteBlockingCommand("DROP INDEX FV_fname;");
  119. ExecuteBlockingCommand("DROP TABLE Applications CASCADE;");
  120. ExecuteBlockingCommand("DROP TABLE FileVersionHistory CASCADE;");
  121. bool b = ExecuteBlockingCommand("DROP VIEW AutoPatcherView;");
  122. //sqlCommandMutex.Unlock();
  123. return b;
  124. }
  125. bool AutopatcherMySQLRepository::AddApplication(const char *applicationName, const char *userName)
  126. {
  127. // mysql_real_escape_string
  128. char query[512];
  129. sprintf(query, "INSERT INTO Applications (applicationName, userName) VALUES ('%s', '%s');", GetEscapedString(applicationName).C_String(), GetEscapedString(userName).C_String());
  130. //sqlCommandMutex.Lock();
  131. bool b = ExecuteBlockingCommand(query);
  132. //sqlCommandMutex.Unlock();
  133. return b;
  134. }
  135. bool AutopatcherMySQLRepository::RemoveApplication(const char *applicationName)
  136. {
  137. char query[512];
  138. sprintf(query, "DELETE FROM Applications WHERE applicationName='%s';", GetEscapedString(applicationName).C_String());
  139. //sqlCommandMutex.Lock();
  140. bool b = ExecuteBlockingCommand(query);
  141. //sqlCommandMutex.Unlock();
  142. return b;
  143. }
  144. bool AutopatcherMySQLRepository::GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate)
  145. {
  146. char query[512];
  147. RakNet::RakString escapedApplicationName = GetEscapedString(applicationName);
  148. sprintf(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
  149. int applicationID;
  150. //sqlCommandMutex.Lock();
  151. if (!ExecuteQueryReadInt(query, &applicationID))
  152. {
  153. // This message covers the lost connection to the SQL server
  154. //sqlCommandMutex.Unlock();
  155. //sprintf(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
  156. return false;
  157. }
  158. //sqlCommandMutex.Unlock();
  159. if (sinceDate!=0)
  160. sprintf(query,
  161. "SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
  162. "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND modificationDate > %f GROUP BY fileName) MaxId "
  163. "ON FileVersionHistory.fileId = MaxId.maxId "
  164. "ORDER BY filename DESC;", applicationID,sinceDate);
  165. else
  166. sprintf(query,
  167. "SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
  168. "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
  169. "ON FileVersionHistory.fileId = MaxId.maxId "
  170. "ORDER BY filename DESC;", applicationID);
  171. MYSQL_RES * result = 0;
  172. //sqlCommandMutex.Lock();
  173. if (!ExecuteBlockingCommand (query, &result))
  174. {
  175. //sqlCommandMutex.Unlock();
  176. return false;
  177. }
  178. //sqlCommandMutex.Unlock();
  179. MYSQL_ROW row;
  180. while ((row = mysql_fetch_row (result)) != 0)
  181. {
  182. const char * createFileResult = row [3];
  183. const char * hardDriveFilename = row [0];
  184. if (createFileResult[0]=='1')
  185. {
  186. const char * hardDriveHash = row [2];
  187. int fileLength = atoi (row [1]);
  188. addedFiles->AddFile(hardDriveFilename, hardDriveFilename, hardDriveHash, HASH_LENGTH, fileLength, FileListNodeContext(0,0), false);
  189. }
  190. else
  191. {
  192. deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(0,0), false);
  193. }
  194. }
  195. mysql_free_result (result);
  196. return true;
  197. }
  198. int AutopatcherMySQLRepository::GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList)
  199. {
  200. char query[512];
  201. RakNet::RakString escapedApplicationName = GetEscapedString(applicationName);
  202. sprintf(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
  203. int applicationID;
  204. //sqlCommandMutex.Lock();
  205. if (!ExecuteQueryReadInt (query, &applicationID))
  206. {
  207. //sqlCommandMutex.Unlock();
  208. sprintf(lastError,"ERROR: %s not found in GetPatches\n",applicationName);
  209. return false;
  210. }
  211. //sqlCommandMutex.Unlock();
  212. // Go through the input list.
  213. for (unsigned inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++)
  214. {
  215. const char * userHash=input->fileList[inputIndex].data;
  216. const char * userFilename=input->fileList[inputIndex].filename;
  217. char *fn = new char [(strlen(userFilename))*2+1];
  218. mysql_real_escape_string(mySqlConnection, fn, userFilename, (unsigned long) strlen(userFilename));
  219. if (userHash==0)
  220. {
  221. // If the user does not have a hash in the input list, get the contents of latest version of this named file and write it to the patch list
  222. // sprintf(query, "SELECT content FROM FileVersionHistory "
  223. // "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
  224. // "ON FileVersionHistory.fileId = MaxId.maxId",
  225. // applicationID, fn);
  226. sprintf(query, "SELECT fileId, fileLength, changeSetID FROM FileVersionHistory "
  227. "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
  228. "ON FileVersionHistory.fileId = MaxId.maxId",
  229. applicationID, fn);
  230. MYSQL_RES * result = 0;
  231. //sqlCommandMutex.Lock();
  232. if (!ExecuteBlockingCommand (query, &result))
  233. {
  234. //sqlCommandMutex.Unlock();
  235. delete [] fn;
  236. return false;
  237. }
  238. //sqlCommandMutex.Unlock();
  239. MYSQL_ROW row = mysql_fetch_row (result);
  240. if (row != 0)
  241. {
  242. //const char * content = row [0];
  243. //unsigned long contentLength=mysql_fetch_lengths (result) [0];
  244. //patchList->AddFile(userFilename, content, contentLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
  245. const int fileId = atoi (row [0]);
  246. const int fileLength = atoi (row [1]);
  247. const int changeSetID = atoi (row [2]);
  248. if (allowDownloadOfOriginalUnmodifiedFiles==false && changeSetID==0)
  249. {
  250. printf("Failure: allowDownloadOfOriginalUnmodifiedFiles==false for %s length %i\n", userFilename.C_String(), fileLength);
  251. mysql_free_result(result);
  252. return false;
  253. }
  254. patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId), true);
  255. }
  256. mysql_free_result(result);
  257. }
  258. else // Assuming the user does have a hash.
  259. {
  260. if (input->fileList[inputIndex].dataLengthBytes!=HASH_LENGTH)
  261. {
  262. delete [] fn;
  263. return false;
  264. }
  265. // Get the hash and ID of the latest version of this file, by filename.
  266. sprintf(query,
  267. "SELECT contentHash, fileId, fileLength FROM FileVersionHistory "
  268. "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
  269. "ON FileVersionHistory.fileId = MaxId.maxId",
  270. applicationID, fn);
  271. MYSQL_RES * result = 0;
  272. //sqlCommandMutex.Lock();
  273. if (!ExecuteBlockingCommand (query, &result))
  274. {
  275. //sqlCommandMutex.Unlock();
  276. delete [] fn;
  277. return false;
  278. }
  279. //sqlCommandMutex.Unlock();
  280. MYSQL_ROW row = mysql_fetch_row (result);
  281. if (row != 0)
  282. {
  283. const char * contentHash = row [0];
  284. const int fileId = atoi (row [1]);
  285. const int fileLength = atoi (row [2]); // double check if this works
  286. if (memcmp(contentHash, userHash, HASH_LENGTH)!=0)
  287. {
  288. char buf [2 * HASH_LENGTH + 1];
  289. mysql_real_escape_string(mySqlConnection, buf, userHash, HASH_LENGTH);
  290. sprintf(query, "SELECT patch FROM FileVersionHistory WHERE applicationId=%i AND filename='%s' AND contentHash='%s'; ", applicationID, fn, buf);
  291. MYSQL_RES * patchResult = 0;
  292. //sqlCommandMutex.Lock();
  293. if (!ExecuteBlockingCommand (query, &patchResult))
  294. {
  295. //sqlCommandMutex.Unlock();
  296. delete [] fn;
  297. return false;
  298. }
  299. //sqlCommandMutex.Unlock();
  300. MYSQL_ROW row = mysql_fetch_row (patchResult);
  301. if (row==0)
  302. {
  303. // If no patch found, then this is a non-release version, or a very old version we are no longer tracking.
  304. // Get the contents of latest version of this named file by fileId and return it.
  305. /*
  306. sprintf(query, "SELECT content FROM FileVersionHistory WHERE fileId=%d;", fileId);
  307. if (mysql_query (mySqlConnection, query) != 0)
  308. {
  309. delete [] fn;
  310. strcpy (lastError, mysql_error (mySqlConnection));
  311. return false;
  312. }
  313. MYSQL_RES * substrresult = mysql_store_result (mySqlConnection);
  314. MYSQL_ROW row = mysql_fetch_row (substrresult);
  315. char * file = row [0];
  316. unsigned long contentLength = mysql_fetch_lengths (substrresult) [0];
  317. patchList->AddFile(userFilename, file, fileLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
  318. mysql_free_result(substrresult);
  319. */
  320. patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId), true);
  321. }
  322. else
  323. {
  324. // Otherwise, write the hash of the new version and then write the patch to get to that version.
  325. //
  326. char * patch = row [0];
  327. unsigned long patchLength = mysql_fetch_lengths (patchResult) [0];
  328. char *temp = new char [patchLength + HASH_LENGTH];
  329. memcpy(temp, contentHash, HASH_LENGTH);
  330. memcpy(temp+HASH_LENGTH, patch, patchLength);
  331. patchList->AddFile(userFilename,userFilename, temp, HASH_LENGTH+patchLength, fileLength, FileListNodeContext(PC_HASH_1_WITH_PATCH,0) );
  332. delete [] temp;
  333. }
  334. mysql_free_result(patchResult);
  335. }
  336. else
  337. {
  338. // else if the hash of this file matches what the user has, the user has the latest version. Done.
  339. }
  340. }
  341. else
  342. {
  343. // else if there is no such file, skip this file.
  344. }
  345. mysql_free_result(result);
  346. }
  347. delete [] fn;
  348. }
  349. return true;
  350. }
  351. bool AutopatcherMySQLRepository::GetMostRecentChangelistWithPatches(RakNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime)
  352. {
  353. // Not yet implemented
  354. return false;
  355. }
  356. bool AutopatcherMySQLRepository::UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb)
  357. {
  358. MYSQL_STMT *stmt;
  359. MYSQL_BIND bind[3];
  360. char query[512];
  361. FileList filesOnHarddrive;
  362. filesOnHarddrive.AddCallback(cb);
  363. int prepareResult;
  364. my_bool falseVar=false;
  365. RakNet::RakString escapedApplicationName = GetEscapedString(applicationName);
  366. filesOnHarddrive.AddFilesFromDirectory(applicationDirectory,"", true, true, true, FileListNodeContext(0,0));
  367. if (filesOnHarddrive.fileList.Size()==0)
  368. {
  369. sprintf(lastError,"ERROR: Can't find files at %s in UpdateApplicationFiles\n",applicationDirectory);
  370. return false;
  371. }
  372. sprintf(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
  373. int applicationID;
  374. //sqlCommandMutex.Lock();
  375. if (!ExecuteQueryReadInt(query, &applicationID))
  376. {
  377. //sqlCommandMutex.Unlock();
  378. sprintf(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
  379. return false;
  380. }
  381. if (!ExecuteBlockingCommand("BEGIN;"))
  382. {
  383. //sqlCommandMutex.Unlock();
  384. return false;
  385. }
  386. //sqlCommandMutex.Unlock();
  387. sprintf(query, "UPDATE Applications SET changeSetId = changeSetId + 1 where applicationID=%i;", applicationID);
  388. //sqlCommandMutex.Lock();
  389. if (!ExecuteBlockingCommand(query))
  390. {
  391. Rollback ();
  392. //sqlCommandMutex.Unlock();
  393. return false;
  394. }
  395. //sqlCommandMutex.Unlock();
  396. int changeSetId = 0;
  397. sprintf(query, "SELECT changeSetId FROM Applications WHERE applicationID=%i;", applicationID);
  398. //sqlCommandMutex.Lock();
  399. if (!ExecuteQueryReadInt(query, &changeSetId))
  400. {
  401. Rollback ();
  402. //sqlCommandMutex.Unlock();
  403. return false;
  404. }
  405. //sqlCommandMutex.Unlock();
  406. // +1 was added in the update
  407. changeSetId--;
  408. // Gets all newest files
  409. sprintf(query, "SELECT filename, contentHash, createFile FROM FileVersionHistory "
  410. "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
  411. "ON FileVersionHistory.fileId = MaxId.maxId "
  412. "ORDER BY filename DESC;", applicationID);
  413. MYSQL_RES *result = 0;
  414. //sqlCommandMutex.Lock();
  415. if (!ExecuteBlockingCommand(query, &result))
  416. {
  417. Rollback();
  418. //sqlCommandMutex.Unlock();
  419. return false;
  420. }
  421. //sqlCommandMutex.Unlock();
  422. DataStructures::List <FileInfo> newestFiles;
  423. MYSQL_ROW row;
  424. while ((row = mysql_fetch_row (result)) != 0)
  425. {
  426. FileInfo fi;
  427. fi.filename = row [0];
  428. fi.createFile = (atoi (row [2]) != 0);
  429. if (fi.createFile)
  430. {
  431. RakAssert(mysql_fetch_lengths (result) [1] == HASH_LENGTH); // check the data is sensible
  432. memcpy (fi.contentHash, row [1], HASH_LENGTH);
  433. }
  434. newestFiles.Insert (fi, _FILE_AND_LINE_ );
  435. }
  436. mysql_free_result(result);
  437. FileList newFiles;
  438. // Loop through files on filesOnHarddrive
  439. // If the file in filesOnHarddrive does not exist in the query result, or if it does but the hash is different or non-existent, add this file to the create list
  440. for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
  441. {
  442. bool addFile=true;
  443. if (fileListIndex%10==0)
  444. printf("Hashing files %i/%i\n", fileListIndex+1, filesOnHarddrive.fileList.Size());
  445. const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
  446. const char * hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
  447. for (unsigned i = 0; i != newestFiles.Size (); ++i)
  448. {
  449. const FileInfo & fi = newestFiles [i];
  450. if (_stricmp(hardDriveFilename, fi.filename)==0)
  451. {
  452. if (fi.createFile && memcmp(fi.contentHash, hardDriveHash, HASH_LENGTH)==0)
  453. {
  454. // File exists in database and is the same
  455. addFile=false;
  456. }
  457. break;
  458. }
  459. }
  460. // Unless set to false, file does not exist in query result or is different.
  461. if (addFile)
  462. {
  463. newFiles.AddFile(hardDriveFilename,hardDriveFilename, filesOnHarddrive.fileList[fileListIndex].data, filesOnHarddrive.fileList[fileListIndex].dataLengthBytes, filesOnHarddrive.fileList[fileListIndex].fileLengthBytes, FileListNodeContext(0,0), false, true);
  464. filesOnHarddrive.fileList[fileListIndex].data=0;
  465. }
  466. }
  467. // Go through query results that are marked as create
  468. // If a file that is currently in the database is not on the harddrive, add it to the delete list
  469. FileList deletedFiles;
  470. for (unsigned i = 0; i != newestFiles.Size (); ++i)
  471. {
  472. const FileInfo & fi = newestFiles [i];
  473. if (!fi.createFile)
  474. continue; // If already false don't mark false again.
  475. bool fileOnHarddrive=false;
  476. for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
  477. {
  478. const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
  479. //hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
  480. if (_stricmp(hardDriveFilename, fi.filename)==0)
  481. {
  482. fileOnHarddrive=true;
  483. break;
  484. }
  485. }
  486. if (!fileOnHarddrive)
  487. deletedFiles.AddFile(fi.filename,fi.filename,0,0,0,FileListNodeContext(0,0), false);
  488. }
  489. // files on harddrive no longer needed. Free this memory since generating all the patches is memory intensive.
  490. filesOnHarddrive.Clear();
  491. // For each file in the delete list add a row indicating file deletion
  492. for (unsigned fileListIndex=0; fileListIndex < deletedFiles.fileList.Size(); fileListIndex++)
  493. {
  494. if (fileListIndex%10==0)
  495. printf("Tagging deleted files %i/%i\n", fileListIndex+1, deletedFiles.fileList.Size());
  496. sprintf(query, "INSERT INTO FileVersionHistory(applicationID, filename, createFile, changeSetID, userName) VALUES (%i, '%s', FALSE,%i,'%s');",
  497. applicationID, GetEscapedString(deletedFiles.fileList[fileListIndex].filename).C_String(), changeSetId, GetEscapedString(userName).C_String());
  498. //sqlCommandMutex.Lock();
  499. if (!ExecuteBlockingCommand (query))
  500. {
  501. Rollback();
  502. //sqlCommandMutex.Unlock();
  503. deletedFiles.Clear();
  504. newFiles.Clear();
  505. return false;
  506. }
  507. //sqlCommandMutex.Unlock();
  508. }
  509. // Clear the delete list as it is no longer needed.
  510. deletedFiles.Clear();
  511. // For each file in the create list
  512. for (unsigned fileListIndex=0; fileListIndex < newFiles.fileList.Size(); fileListIndex++)
  513. {
  514. if (fileListIndex%10==0)
  515. printf("Adding file %i/%i\n", fileListIndex+1, newFiles.fileList.Size());
  516. const char * hardDriveFilename=newFiles.fileList[fileListIndex].filename;
  517. const char * hardDriveData=newFiles.fileList[fileListIndex].data+HASH_LENGTH;
  518. const char * hardDriveHash=newFiles.fileList[fileListIndex].data;
  519. unsigned hardDriveDataLength=newFiles.fileList[fileListIndex].fileLengthBytes;
  520. sprintf( query, "SELECT fileID from FileVersionHistory WHERE applicationID=%i AND filename='%s' AND createFile=TRUE;", applicationID, GetEscapedString(hardDriveFilename).C_String() );
  521. MYSQL_RES * res = 0;
  522. //sqlCommandMutex.Lock();
  523. if (!ExecuteBlockingCommand (query, &res))
  524. {
  525. Rollback();
  526. //sqlCommandMutex.Unlock();
  527. newFiles.Clear();
  528. return false;
  529. }
  530. //sqlCommandMutex.Unlock();
  531. // Create new patches for every create version
  532. MYSQL_ROW row;
  533. while ((row = mysql_fetch_row (res)) != 0)
  534. {
  535. const char * fileID = row [0];
  536. // The last query handled all the relevant comparisons
  537. sprintf(query, "SELECT content from FileVersionHistory WHERE fileID=%s", fileID );
  538. MYSQL_RES * queryResult = 0;
  539. //sqlCommandMutex.Lock();
  540. if (!ExecuteBlockingCommand (query, &queryResult))
  541. {
  542. Rollback();
  543. //sqlCommandMutex.Unlock();
  544. newFiles.Clear();
  545. mysql_free_result(res);
  546. return false;
  547. }
  548. //sqlCommandMutex.Unlock();
  549. MYSQL_ROW queryRow = mysql_fetch_row (queryResult);
  550. const unsigned contentLength=mysql_fetch_lengths (queryResult) [0];
  551. const char * content=queryRow [0];
  552. char *patch;
  553. unsigned patchLength;
  554. if (!CreatePatch(content, contentLength, (char *) hardDriveData, hardDriveDataLength, &patch, &patchLength))
  555. {
  556. strcpy(lastError,"CreatePatch failed.\n");
  557. Rollback();
  558. newFiles.Clear();
  559. mysql_free_result(res);
  560. mysql_free_result(queryResult);
  561. return false;
  562. }
  563. char buf[512];
  564. stmt = mysql_stmt_init(mySqlConnection);
  565. sprintf (buf, "UPDATE FileVersionHistory SET patch=? where fileID=%s;", fileID);
  566. if ((prepareResult=mysql_stmt_prepare(stmt, buf, (unsigned long) strlen(buf)))!=0)
  567. {
  568. strcpy (lastError, mysql_stmt_error (stmt));
  569. mysql_stmt_close(stmt);
  570. Rollback();
  571. return false;
  572. }
  573. memset(bind, 0, sizeof(bind));
  574. unsigned long l1;
  575. l1=patchLength;
  576. bind[0].buffer_type= MYSQL_TYPE_LONG_BLOB;
  577. bind[0].buffer= patch;
  578. bind[0].buffer_length= patchLength;
  579. bind[0].is_null= &falseVar;
  580. bind[0].length=&l1;
  581. if (mysql_stmt_bind_param(stmt, bind))
  582. {
  583. strcpy (lastError, mysql_stmt_error (stmt));
  584. mysql_stmt_close(stmt);
  585. Rollback();
  586. return false;
  587. }
  588. //sqlCommandMutex.Lock();
  589. if (mysql_stmt_execute(stmt))
  590. {
  591. strcpy (lastError, mysql_stmt_error (stmt));
  592. mysql_stmt_close(stmt);
  593. Rollback();
  594. //sqlCommandMutex.Unlock();
  595. newFiles.Clear();
  596. mysql_free_result(res);
  597. mysql_free_result(queryResult);
  598. delete [] patch;
  599. return false;
  600. }
  601. //sqlCommandMutex.Unlock();
  602. mysql_stmt_close(stmt);
  603. delete [] patch;
  604. mysql_free_result(queryResult);
  605. }
  606. mysql_free_result(res);
  607. stmt = mysql_stmt_init(mySqlConnection);
  608. sprintf(query, "INSERT INTO FileVersionHistory (applicationID, filename, fileLength, content, contentHash, createFile, changeSetID, userName) "
  609. "VALUES (%i, ?, %i,?,?, TRUE, %i, '%s' );",
  610. applicationID, hardDriveDataLength, changeSetId, GetEscapedString(userName).C_String());
  611. if ((prepareResult=mysql_stmt_prepare(stmt, query, (unsigned long) strlen(query)))!=0)
  612. {
  613. strcpy (lastError, mysql_stmt_error (stmt));
  614. mysql_stmt_close(stmt);
  615. Rollback();
  616. return false;
  617. }
  618. memset(bind, 0, sizeof(bind));
  619. unsigned long l2,l3,l4;
  620. l2=(unsigned long) strlen(hardDriveFilename);
  621. l3=hardDriveDataLength;
  622. l4=HASH_LENGTH;
  623. bind[0].buffer_type= MYSQL_TYPE_STRING;
  624. bind[0].buffer= (void*) hardDriveFilename;
  625. bind[0].buffer_length= (unsigned long) strlen(hardDriveFilename);
  626. bind[0].is_null= &falseVar;
  627. bind[0].length=&l2;
  628. bind[1].buffer_type= MYSQL_TYPE_LONG_BLOB;
  629. bind[1].buffer= (void*) hardDriveData;
  630. bind[1].buffer_length= hardDriveDataLength;
  631. bind[1].is_null= &falseVar;
  632. bind[1].length=&l3;
  633. bind[2].buffer_type= MYSQL_TYPE_TINY_BLOB;
  634. bind[2].buffer= (void*) hardDriveHash;
  635. bind[2].buffer_length= HASH_LENGTH;
  636. bind[2].is_null= &falseVar;
  637. bind[2].length=&l4;
  638. if (mysql_stmt_bind_param(stmt, bind))
  639. {
  640. strcpy (lastError, mysql_stmt_error (stmt));
  641. mysql_stmt_close(stmt);
  642. Rollback();
  643. return false;
  644. }
  645. //sqlCommandMutex.Lock();
  646. if (mysql_stmt_execute(stmt))
  647. {
  648. strcpy (lastError, mysql_stmt_error (stmt));
  649. mysql_stmt_close(stmt);
  650. Rollback();
  651. //sqlCommandMutex.Unlock();
  652. return false;
  653. }
  654. //sqlCommandMutex.Unlock();
  655. mysql_stmt_close(stmt);
  656. }
  657. //sqlCommandMutex.Lock();
  658. if (!ExecuteBlockingCommand("COMMIT;"))
  659. {
  660. Rollback ();
  661. //sqlCommandMutex.Unlock();
  662. return false;
  663. }
  664. //sqlCommandMutex.Unlock();
  665. return true;
  666. }
  667. const char *AutopatcherMySQLRepository::GetLastError(void) const
  668. {
  669. return MySQLInterface::GetLastError();
  670. }
  671. unsigned int AutopatcherMySQLRepository::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
  672. {
  673. char query[512];
  674. sprintf(query, "SELECT substring(content from %i for %i) FROM FileVersionHistory WHERE fileId=%i;", startReadBytes+1,numBytesToRead,context.flnc_extraData);
  675. // CREATE NEW CONNECTION JUST FOR THIS QUERY
  676. // This is because the autopatcher is sharing this class, but this is called from multiple threads and mysql is not threadsafe
  677. MYSQL_RES * result;
  678. char lastError[512];
  679. filePartConnectionMutex.Lock();
  680. if (filePartConnection==0)
  681. {
  682. filePartConnection = mysql_init(0);
  683. mysql_real_connect (filePartConnection, _host, _user, _passwd, _db, _port, _unix_socket, _clientflag);
  684. }
  685. if (mysql_query(filePartConnection, query)!=0)
  686. {
  687. strcpy (lastError, mysql_error (filePartConnection));
  688. }
  689. result = mysql_store_result (filePartConnection);
  690. if (result)
  691. {
  692. // This has very poor performance with any size for GetIncrementalReadChunkSize, but especially for larger sizes
  693. MYSQL_ROW row = mysql_fetch_row (result);
  694. if (row != 0)
  695. {
  696. const char * content = row [0];
  697. unsigned long contentLength=mysql_fetch_lengths (result) [0];
  698. memcpy(preallocatedDestination,content,contentLength);
  699. mysql_free_result (result);
  700. filePartConnectionMutex.Unlock();
  701. return contentLength;
  702. }
  703. mysql_free_result (result);
  704. }
  705. filePartConnectionMutex.Unlock();
  706. return 0;
  707. }
  708. const int AutopatcherMySQLRepository::GetIncrementalReadChunkSize(void) const
  709. {
  710. // AutopatcherMySQLRepository::GetFilePart is extremely slow with larger files
  711. return 262144*4;
  712. }
粤ICP备19079148号