Init
This commit is contained in:
@ -0,0 +1,844 @@
|
||||
/*
|
||||
* Original work: Copyright (c) 2014, Oculus VR, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* RakNet License.txt file in the licenses directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the RakNet Patents.txt file in the same directory.
|
||||
*
|
||||
*
|
||||
* Modified work: Copyright (c) 2016-2018, SLikeSoft UG (haftungsbeschränkt)
|
||||
*
|
||||
* This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
|
||||
* license found in the license.txt file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "slikenet/string.h"
|
||||
#include "AutopatcherMySQLRepository.h"
|
||||
#include "slikenet/AutopatcherPatchContext.h"
|
||||
#include "slikenet/FileList.h"
|
||||
#include "slikenet/assert.h"
|
||||
#include "slikenet/DS_List.h"
|
||||
// ntohl
|
||||
#ifdef _WIN32
|
||||
#include <Winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
#include "mysql.h"
|
||||
#include "CreatePatch.h"
|
||||
#include "slikenet/AutopatcherPatchContext.h"
|
||||
// #include "slikenet/DR_SHA1.h"
|
||||
#include <stdlib.h>
|
||||
#include "slikenet/LinuxStrings.h"
|
||||
#include "slikenet/linux_adapter.h"
|
||||
#include "slikenet/osx_adapter.h"
|
||||
|
||||
using namespace SLNet;
|
||||
|
||||
static const unsigned HASH_LENGTH=sizeof(unsigned int);
|
||||
|
||||
struct FileInfo
|
||||
{
|
||||
SLNet::RakString filename;
|
||||
char contentHash [HASH_LENGTH];
|
||||
bool createFile;
|
||||
};
|
||||
|
||||
// alloca
|
||||
#ifdef _COMPATIBILITY_1
|
||||
#elif defined(_WIN32)
|
||||
#include <malloc.h>
|
||||
#else
|
||||
//#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define PQEXECPARAM_FORMAT_TEXT 0
|
||||
#define PQEXECPARAM_FORMAT_BINARY 1
|
||||
|
||||
AutopatcherMySQLRepository::AutopatcherMySQLRepository()
|
||||
{
|
||||
filePartConnection=0;
|
||||
}
|
||||
|
||||
AutopatcherMySQLRepository::~AutopatcherMySQLRepository()
|
||||
{
|
||||
if (filePartConnection)
|
||||
mysql_close(filePartConnection);
|
||||
}
|
||||
bool AutopatcherMySQLRepository::CreateAutopatcherTables(void)
|
||||
{
|
||||
if (!IsConnected())
|
||||
return false;
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
ExecuteBlockingCommand("BEGIN;");
|
||||
|
||||
if (!ExecuteBlockingCommand(
|
||||
"CREATE TABLE Applications ("
|
||||
"applicationID INT AUTO_INCREMENT,"
|
||||
"applicationName VARCHAR(255) NOT NULL UNIQUE,"
|
||||
"changeSetID integer NOT NULL DEFAULT 0,"
|
||||
"userName TEXT NOT NULL,"
|
||||
"PRIMARY KEY (applicationID));"
|
||||
)) {Rollback(); return false;}
|
||||
|
||||
if (!ExecuteBlockingCommand(
|
||||
"CREATE TABLE FileVersionHistory ("
|
||||
"fileID INT AUTO_INCREMENT,"
|
||||
"applicationID INT NOT NULL, "
|
||||
"filename VARCHAR(255) NOT NULL,"
|
||||
"fileLength INT,"
|
||||
"content LONGBLOB,"
|
||||
"contentHash TINYBLOB,"
|
||||
"patch LONGBLOB,"
|
||||
"createFile TINYINT NOT NULL,"
|
||||
"modificationDate double precision DEFAULT (EXTRACT(EPOCH FROM now())),"
|
||||
"lastSentDate double precision,"
|
||||
"timesSent INT NOT NULL DEFAULT 0,"
|
||||
"changeSetID INT NOT NULL,"
|
||||
"userName TEXT NOT NULL,"
|
||||
"PRIMARY KEY (fileID)); "
|
||||
)) {Rollback(); return false;}
|
||||
|
||||
if (!ExecuteBlockingCommand(
|
||||
"CREATE INDEX FV_appID on FileVersionHistory(applicationID);"
|
||||
)) {Rollback(); return false;}
|
||||
|
||||
if (!ExecuteBlockingCommand(
|
||||
"CREATE INDEX FV_fname on FileVersionHistory(filename);"
|
||||
)) {Rollback(); return false;}
|
||||
|
||||
if (!ExecuteBlockingCommand(
|
||||
"CREATE VIEW AutoPatcherView AS SELECT "
|
||||
"FileVersionHistory.applicationid,"
|
||||
"Applications.applicationName,"
|
||||
"FileVersionHistory.fileID,"
|
||||
"FileVersionHistory.fileName,"
|
||||
"FileVersionHistory.createFile,"
|
||||
"FileVersionHistory.fileLength,"
|
||||
"FileVersionHistory.changeSetID,"
|
||||
"FileVersionHistory.lastSentDate,"
|
||||
"FileVersionHistory.modificationDate,"
|
||||
"FileVersionHistory.timesSent "
|
||||
"FROM (FileVersionHistory JOIN Applications ON "
|
||||
"( FileVersionHistory.applicationID = Applications.applicationID )) "
|
||||
"ORDER BY Applications.applicationID ASC, FileVersionHistory.fileID ASC;"
|
||||
)) {Rollback(); return false;}
|
||||
|
||||
bool b = ExecuteBlockingCommand("COMMIT;");
|
||||
//sqlCommandMutex.Unlock();
|
||||
return b;
|
||||
}
|
||||
|
||||
bool AutopatcherMySQLRepository::DestroyAutopatcherTables(void)
|
||||
{
|
||||
if (!IsConnected())
|
||||
return false;
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
ExecuteBlockingCommand("DROP INDEX FV_appID;");
|
||||
ExecuteBlockingCommand("DROP INDEX FV_fname;");
|
||||
ExecuteBlockingCommand("DROP TABLE Applications CASCADE;");
|
||||
ExecuteBlockingCommand("DROP TABLE FileVersionHistory CASCADE;");
|
||||
bool b = ExecuteBlockingCommand("DROP VIEW AutoPatcherView;");
|
||||
//sqlCommandMutex.Unlock();
|
||||
return b;
|
||||
}
|
||||
|
||||
bool AutopatcherMySQLRepository::AddApplication(const char *applicationName, const char *userName)
|
||||
{
|
||||
// mysql_real_escape_string
|
||||
|
||||
char query[512];
|
||||
sprintf_s(query, "INSERT INTO Applications (applicationName, userName) VALUES ('%s', '%s');", GetEscapedString(applicationName).C_String(), GetEscapedString(userName).C_String());
|
||||
//sqlCommandMutex.Lock();
|
||||
bool b = ExecuteBlockingCommand(query);
|
||||
//sqlCommandMutex.Unlock();
|
||||
return b;
|
||||
}
|
||||
bool AutopatcherMySQLRepository::RemoveApplication(const char *applicationName)
|
||||
{
|
||||
char query[512];
|
||||
sprintf_s(query, "DELETE FROM Applications WHERE applicationName='%s';", GetEscapedString(applicationName).C_String());
|
||||
//sqlCommandMutex.Lock();
|
||||
bool b = ExecuteBlockingCommand(query);
|
||||
//sqlCommandMutex.Unlock();
|
||||
return b;
|
||||
}
|
||||
|
||||
bool AutopatcherMySQLRepository::GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate)
|
||||
{
|
||||
char query[512];
|
||||
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
||||
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
||||
|
||||
int applicationID;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteQueryReadInt(query, &applicationID))
|
||||
{
|
||||
// This message covers the lost connection to the SQL server
|
||||
//sqlCommandMutex.Unlock();
|
||||
//sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
if (sinceDate!=0)
|
||||
sprintf_s(query,
|
||||
"SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
|
||||
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND modificationDate > %f GROUP BY fileName) MaxId "
|
||||
"ON FileVersionHistory.fileId = MaxId.maxId "
|
||||
"ORDER BY filename DESC;", applicationID,sinceDate);
|
||||
else
|
||||
sprintf_s(query,
|
||||
"SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
|
||||
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
|
||||
"ON FileVersionHistory.fileId = MaxId.maxId "
|
||||
"ORDER BY filename DESC;", applicationID);
|
||||
|
||||
MYSQL_RES * result = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &result))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
MYSQL_ROW row;
|
||||
while ((row = mysql_fetch_row (result)) != 0)
|
||||
{
|
||||
const char * createFileResult = row [3];
|
||||
const char * hardDriveFilename = row [0];
|
||||
if (createFileResult[0]=='1')
|
||||
{
|
||||
const char * hardDriveHash = row [2];
|
||||
int fileLength = atoi (row [1]);
|
||||
addedOrModifiedFilesWithHashData->AddFile(hardDriveFilename, hardDriveFilename, hardDriveHash, HASH_LENGTH, fileLength, FileListNodeContext(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(), false);
|
||||
}
|
||||
}
|
||||
mysql_free_result (result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int AutopatcherMySQLRepository::GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList)
|
||||
{
|
||||
char query[512];
|
||||
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
||||
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
||||
int applicationID;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteQueryReadInt (query, &applicationID))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
sprintf_s(lastError,"ERROR: %s not found in GetPatches\n",applicationName);
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
// Go through the input list.
|
||||
for (unsigned inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++)
|
||||
{
|
||||
const char * userHash=input->fileList[inputIndex].data;
|
||||
const char * userFilename=input->fileList[inputIndex].filename;
|
||||
|
||||
char *fn = new char [(strlen(userFilename))*2+1];
|
||||
mysql_real_escape_string(mySqlConnection, fn, userFilename, (unsigned long) strlen(userFilename));
|
||||
|
||||
if (userHash==0)
|
||||
{
|
||||
// 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
|
||||
// sprintf_s(query, "SELECT content FROM FileVersionHistory "
|
||||
// "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
|
||||
// "ON FileVersionHistory.fileId = MaxId.maxId",
|
||||
// applicationID, fn);
|
||||
|
||||
sprintf_s(query, "SELECT fileId, fileLength, changeSetID FROM FileVersionHistory "
|
||||
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
|
||||
"ON FileVersionHistory.fileId = MaxId.maxId",
|
||||
applicationID, fn);
|
||||
|
||||
MYSQL_RES * result = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &result))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
delete [] fn;
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row (result);
|
||||
if (row != 0)
|
||||
{
|
||||
//const char * content = row [0];
|
||||
//unsigned long contentLength=mysql_fetch_lengths (result) [0];
|
||||
//patchList->AddFile(userFilename, content, contentLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
|
||||
const int fileId = atoi (row [0]);
|
||||
const int fileLength = atoi (row [1]);
|
||||
const int changeSetID = atoi (row [2]);
|
||||
if (allowDownloadOfOriginalUnmodifiedFiles==false && changeSetID==0)
|
||||
{
|
||||
printf("Failure: allowDownloadOfOriginalUnmodifiedFiles==false for %s length %i\n", userFilename, fileLength);
|
||||
|
||||
mysql_free_result(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0), true);
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
else // Assuming the user does have a hash.
|
||||
{
|
||||
if (input->fileList[inputIndex].dataLengthBytes!=HASH_LENGTH)
|
||||
{
|
||||
delete [] fn;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the hash and ID of the latest version of this file, by filename.
|
||||
sprintf_s(query,
|
||||
"SELECT contentHash, fileId, fileLength FROM FileVersionHistory "
|
||||
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
|
||||
"ON FileVersionHistory.fileId = MaxId.maxId",
|
||||
applicationID, fn);
|
||||
|
||||
MYSQL_RES * result = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &result))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
delete [] fn;
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row (result);
|
||||
if (row != 0)
|
||||
{
|
||||
const char * contentHash = row [0];
|
||||
const int fileId = atoi (row [1]);
|
||||
const int fileLength = atoi (row [2]); // double check if this works
|
||||
|
||||
if (memcmp(contentHash, userHash, HASH_LENGTH)!=0)
|
||||
{
|
||||
char buf [2 * HASH_LENGTH + 1];
|
||||
mysql_real_escape_string(mySqlConnection, buf, userHash, HASH_LENGTH);
|
||||
|
||||
sprintf_s(query, "SELECT patch FROM FileVersionHistory WHERE applicationId=%i AND filename='%s' AND contentHash='%s'; ", applicationID, fn, buf);
|
||||
MYSQL_RES * patchResult = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &patchResult))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
delete [] fn;
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
row = mysql_fetch_row (patchResult);
|
||||
if (row==0)
|
||||
{
|
||||
// If no patch found, then this is a non-release version, or a very old version we are no longer tracking.
|
||||
// Get the contents of latest version of this named file by fileId and return it.
|
||||
|
||||
/*
|
||||
sprintf_s(query, "SELECT content FROM FileVersionHistory WHERE fileId=%d;", fileId);
|
||||
|
||||
if (mysql_query (mySqlConnection, query) != 0)
|
||||
{
|
||||
delete [] fn;
|
||||
strcpy (lastError, mysql_error (mySqlConnection));
|
||||
return false;
|
||||
}
|
||||
|
||||
MYSQL_RES * substrresult = mysql_store_result (mySqlConnection);
|
||||
row = mysql_fetch_row (substrresult);
|
||||
char * file = row [0];
|
||||
unsigned long contentLength = mysql_fetch_lengths (substrresult) [0];
|
||||
|
||||
patchList->AddFile(userFilename, file, fileLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
|
||||
mysql_free_result(substrresult);
|
||||
*/
|
||||
patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, write the hash of the new version and then write the patch to get to that version.
|
||||
//
|
||||
char * patch = row [0];
|
||||
unsigned long patchLength = mysql_fetch_lengths (patchResult) [0];
|
||||
|
||||
char *temp = new char [patchLength + HASH_LENGTH];
|
||||
memcpy(temp, contentHash, HASH_LENGTH);
|
||||
memcpy(temp+HASH_LENGTH, patch, patchLength);
|
||||
|
||||
patchList->AddFile(userFilename,userFilename, temp, HASH_LENGTH+patchLength, fileLength, FileListNodeContext(PC_HASH_1_WITH_PATCH,0,0,0) );
|
||||
delete [] temp;
|
||||
}
|
||||
|
||||
mysql_free_result(patchResult);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// else if the hash of this file matches what the user has, the user has the latest version. Done.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// else if there is no such file, skip this file.
|
||||
}
|
||||
mysql_free_result(result);
|
||||
}
|
||||
|
||||
delete [] fn;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutopatcherMySQLRepository::GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime)
|
||||
{
|
||||
// unused parameters
|
||||
(void)applicationName;
|
||||
(void)patchedFiles;
|
||||
(void)addedFiles;
|
||||
(void)addedOrModifiedFileHashes;
|
||||
(void)deletedFiles;
|
||||
(void)priorRowPatchTime;
|
||||
(void)mostRecentRowPatchTime;
|
||||
|
||||
// Not yet implemented
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutopatcherMySQLRepository::UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb)
|
||||
{
|
||||
MYSQL_STMT *stmt;
|
||||
MYSQL_BIND bind[3];
|
||||
char query[512];
|
||||
FileList filesOnHarddrive;
|
||||
filesOnHarddrive.AddCallback(cb);
|
||||
int prepareResult;
|
||||
my_bool falseVar=false;
|
||||
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
||||
filesOnHarddrive.AddFilesFromDirectory(applicationDirectory,"", true, true, true, FileListNodeContext());
|
||||
if (filesOnHarddrive.fileList.Size()==0)
|
||||
{
|
||||
sprintf_s(lastError,"ERROR: Can't find files at %s in UpdateApplicationFiles\n",applicationDirectory);
|
||||
return false;
|
||||
}
|
||||
|
||||
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
||||
int applicationID;
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteQueryReadInt(query, &applicationID))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ExecuteBlockingCommand("BEGIN;"))
|
||||
{
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
sprintf_s(query, "UPDATE Applications SET changeSetId = changeSetId + 1 where applicationID=%i;", applicationID);
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand(query))
|
||||
{
|
||||
Rollback ();
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
int changeSetId = 0;
|
||||
sprintf_s(query, "SELECT changeSetId FROM Applications WHERE applicationID=%i;", applicationID);
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteQueryReadInt(query, &changeSetId))
|
||||
{
|
||||
Rollback ();
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
// +1 was added in the update
|
||||
changeSetId--;
|
||||
|
||||
// Gets all newest files
|
||||
sprintf_s(query, "SELECT filename, contentHash, createFile FROM FileVersionHistory "
|
||||
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
|
||||
"ON FileVersionHistory.fileId = MaxId.maxId "
|
||||
"ORDER BY filename DESC;", applicationID);
|
||||
|
||||
MYSQL_RES *result = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand(query, &result))
|
||||
{
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
DataStructures::List <FileInfo> newestFiles;
|
||||
MYSQL_ROW row;
|
||||
while ((row = mysql_fetch_row (result)) != 0)
|
||||
{
|
||||
FileInfo fi;
|
||||
fi.filename = row [0];
|
||||
fi.createFile = (atoi (row [2]) != 0);
|
||||
if (fi.createFile)
|
||||
{
|
||||
RakAssert(mysql_fetch_lengths (result) [1] == HASH_LENGTH); // check the data is sensible
|
||||
memcpy (fi.contentHash, row [1], HASH_LENGTH);
|
||||
}
|
||||
newestFiles.Insert (fi, _FILE_AND_LINE_ );
|
||||
}
|
||||
mysql_free_result(result);
|
||||
|
||||
|
||||
FileList newFiles;
|
||||
// Loop through files on filesOnHarddrive
|
||||
// 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
|
||||
for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
||||
{
|
||||
bool addFile=true;
|
||||
if (fileListIndex%10==0)
|
||||
printf("Hashing files %i/%i\n", fileListIndex+1, filesOnHarddrive.fileList.Size());
|
||||
|
||||
const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
|
||||
const char * hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
||||
|
||||
for (unsigned i = 0; i != newestFiles.Size (); ++i)
|
||||
{
|
||||
const FileInfo & fi = newestFiles [i];
|
||||
|
||||
if (_stricmp(hardDriveFilename, fi.filename)==0)
|
||||
{
|
||||
if (fi.createFile && memcmp(fi.contentHash, hardDriveHash, HASH_LENGTH)==0)
|
||||
{
|
||||
// File exists in database and is the same
|
||||
addFile=false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unless set to false, file does not exist in query result or is different.
|
||||
if (addFile)
|
||||
{
|
||||
newFiles.AddFile(hardDriveFilename,hardDriveFilename, filesOnHarddrive.fileList[fileListIndex].data, filesOnHarddrive.fileList[fileListIndex].dataLengthBytes, filesOnHarddrive.fileList[fileListIndex].fileLengthBytes, FileListNodeContext(), false, true);
|
||||
filesOnHarddrive.fileList[fileListIndex].data=0;
|
||||
}
|
||||
}
|
||||
|
||||
// Go through query results that are marked as create
|
||||
// If a file that is currently in the database is not on the harddrive, add it to the delete list
|
||||
FileList deletedFiles;
|
||||
for (unsigned i = 0; i != newestFiles.Size (); ++i)
|
||||
{
|
||||
const FileInfo & fi = newestFiles [i];
|
||||
if (!fi.createFile)
|
||||
continue; // If already false don't mark false again.
|
||||
|
||||
bool fileOnHarddrive=false;
|
||||
for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
||||
{
|
||||
const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
|
||||
//hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
||||
|
||||
if (_stricmp(hardDriveFilename, fi.filename)==0)
|
||||
{
|
||||
fileOnHarddrive=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileOnHarddrive)
|
||||
deletedFiles.AddFile(fi.filename,fi.filename,0,0,0,FileListNodeContext(), false);
|
||||
}
|
||||
|
||||
// files on harddrive no longer needed. Free this memory since generating all the patches is memory intensive.
|
||||
filesOnHarddrive.Clear();
|
||||
|
||||
// For each file in the delete list add a row indicating file deletion
|
||||
for (unsigned fileListIndex=0; fileListIndex < deletedFiles.fileList.Size(); fileListIndex++)
|
||||
{
|
||||
if (fileListIndex%10==0)
|
||||
printf("Tagging deleted files %i/%i\n", fileListIndex+1, deletedFiles.fileList.Size());
|
||||
|
||||
sprintf_s(query, "INSERT INTO FileVersionHistory(applicationID, filename, createFile, changeSetID, userName) VALUES (%i, '%s', FALSE,%i,'%s');",
|
||||
applicationID, GetEscapedString(deletedFiles.fileList[fileListIndex].filename).C_String(), changeSetId, GetEscapedString(userName).C_String());
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query))
|
||||
{
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
deletedFiles.Clear();
|
||||
newFiles.Clear();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
}
|
||||
|
||||
// Clear the delete list as it is no longer needed.
|
||||
deletedFiles.Clear();
|
||||
|
||||
// For each file in the create list
|
||||
for (unsigned fileListIndex=0; fileListIndex < newFiles.fileList.Size(); fileListIndex++)
|
||||
{
|
||||
if (fileListIndex%10==0)
|
||||
printf("Adding file %i/%i\n", fileListIndex+1, newFiles.fileList.Size());
|
||||
const char * hardDriveFilename=newFiles.fileList[fileListIndex].filename;
|
||||
const char * hardDriveData=newFiles.fileList[fileListIndex].data+HASH_LENGTH;
|
||||
const char * hardDriveHash=newFiles.fileList[fileListIndex].data;
|
||||
unsigned hardDriveDataLength=newFiles.fileList[fileListIndex].fileLengthBytes;
|
||||
|
||||
sprintf_s( query, "SELECT fileID from FileVersionHistory WHERE applicationID=%i AND filename='%s' AND createFile=TRUE;", applicationID, GetEscapedString(hardDriveFilename).C_String() );
|
||||
|
||||
MYSQL_RES * res = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &res))
|
||||
{
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
newFiles.Clear();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
// Create new patches for every create version
|
||||
while ((row = mysql_fetch_row (res)) != 0)
|
||||
{
|
||||
const char * fileID = row [0];
|
||||
|
||||
// The last query handled all the relevant comparisons
|
||||
sprintf_s(query, "SELECT content from FileVersionHistory WHERE fileID=%s", fileID );
|
||||
MYSQL_RES * queryResult = 0;
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand (query, &queryResult))
|
||||
{
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
newFiles.Clear();
|
||||
mysql_free_result(res);
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
MYSQL_ROW queryRow = mysql_fetch_row (queryResult);
|
||||
|
||||
const unsigned contentLength=mysql_fetch_lengths (queryResult) [0];
|
||||
const char * content=queryRow [0];
|
||||
|
||||
char *patch;
|
||||
unsigned patchLength;
|
||||
if (!CreatePatch(content, contentLength, (char *) hardDriveData, hardDriveDataLength, &patch, &patchLength))
|
||||
{
|
||||
strcpy_s(lastError,"CreatePatch failed.\n");
|
||||
Rollback();
|
||||
|
||||
newFiles.Clear();
|
||||
mysql_free_result(res);
|
||||
mysql_free_result(queryResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[512];
|
||||
stmt = mysql_stmt_init(mySqlConnection);
|
||||
sprintf (buf, "UPDATE FileVersionHistory SET patch=? where fileID=%s;", fileID);
|
||||
if ((prepareResult=mysql_stmt_prepare(stmt, buf, (unsigned long) strlen(buf)))!=0)
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
memset(bind, 0, sizeof(bind));
|
||||
|
||||
unsigned long l1;
|
||||
l1=patchLength;
|
||||
bind[0].buffer_type= MYSQL_TYPE_LONG_BLOB;
|
||||
bind[0].buffer= patch;
|
||||
bind[0].buffer_length= patchLength;
|
||||
bind[0].is_null= &falseVar;
|
||||
bind[0].length=&l1;
|
||||
|
||||
if (mysql_stmt_bind_param(stmt, bind))
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
if (mysql_stmt_execute(stmt))
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
newFiles.Clear();
|
||||
mysql_free_result(res);
|
||||
mysql_free_result(queryResult);
|
||||
delete [] patch;
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
delete [] patch;
|
||||
|
||||
mysql_free_result(queryResult);
|
||||
}
|
||||
mysql_free_result(res);
|
||||
|
||||
stmt = mysql_stmt_init(mySqlConnection);
|
||||
sprintf_s(query, "INSERT INTO FileVersionHistory (applicationID, filename, fileLength, content, contentHash, createFile, changeSetID, userName) "
|
||||
"VALUES (%i, ?, %i,?,?, TRUE, %i, '%s' );",
|
||||
applicationID, hardDriveDataLength, changeSetId, GetEscapedString(userName).C_String());
|
||||
|
||||
if ((prepareResult=mysql_stmt_prepare(stmt, query, (unsigned long) strlen(query)))!=0)
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
memset(bind, 0, sizeof(bind));
|
||||
|
||||
unsigned long l2,l3,l4;
|
||||
l2=(unsigned long) strlen(hardDriveFilename);
|
||||
l3=hardDriveDataLength;
|
||||
l4=HASH_LENGTH;
|
||||
bind[0].buffer_type= MYSQL_TYPE_STRING;
|
||||
bind[0].buffer= (void*) hardDriveFilename;
|
||||
bind[0].buffer_length= (unsigned long) strlen(hardDriveFilename);
|
||||
bind[0].is_null= &falseVar;
|
||||
bind[0].length=&l2;
|
||||
|
||||
bind[1].buffer_type= MYSQL_TYPE_LONG_BLOB;
|
||||
bind[1].buffer= (void*) hardDriveData;
|
||||
bind[1].buffer_length= hardDriveDataLength;
|
||||
bind[1].is_null= &falseVar;
|
||||
bind[1].length=&l3;
|
||||
|
||||
bind[2].buffer_type= MYSQL_TYPE_TINY_BLOB;
|
||||
bind[2].buffer= (void*) hardDriveHash;
|
||||
bind[2].buffer_length= HASH_LENGTH;
|
||||
bind[2].is_null= &falseVar;
|
||||
bind[2].length=&l4;
|
||||
|
||||
if (mysql_stmt_bind_param(stmt, bind))
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
if (mysql_stmt_execute(stmt))
|
||||
{
|
||||
strcpy (lastError, mysql_stmt_error (stmt));
|
||||
mysql_stmt_close(stmt);
|
||||
Rollback();
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
}
|
||||
|
||||
//sqlCommandMutex.Lock();
|
||||
if (!ExecuteBlockingCommand("COMMIT;"))
|
||||
{
|
||||
Rollback ();
|
||||
//sqlCommandMutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
//sqlCommandMutex.Unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *AutopatcherMySQLRepository::GetLastError(void) const
|
||||
{
|
||||
return MySQLInterface::GetLastError();
|
||||
}
|
||||
unsigned int AutopatcherMySQLRepository::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
|
||||
{
|
||||
// unused parameters
|
||||
(void)filename;
|
||||
|
||||
char query[512];
|
||||
sprintf_s(query, "SELECT substring(content from %i for %i) FROM FileVersionHistory WHERE fileId=%i;", startReadBytes+1,numBytesToRead,context.flnc_extraData1);
|
||||
|
||||
// CREATE NEW CONNECTION JUST FOR THIS QUERY
|
||||
// This is because the autopatcher is sharing this class, but this is called from multiple threads and mysql is not threadsafe
|
||||
|
||||
MYSQL_RES * result;
|
||||
|
||||
char error[512];
|
||||
filePartConnectionMutex.Lock();
|
||||
if (filePartConnection==0)
|
||||
{
|
||||
filePartConnection = mysql_init(0);
|
||||
mysql_real_connect (filePartConnection, _host, _user, _passwd, _db, _port, _unix_socket, _clientflag);
|
||||
}
|
||||
|
||||
if (mysql_query(filePartConnection, query)!=0)
|
||||
{
|
||||
// #med - review --- should this set the class member maybe?
|
||||
strcpy (error, mysql_error (filePartConnection));
|
||||
}
|
||||
result = mysql_store_result (filePartConnection);
|
||||
|
||||
if (result)
|
||||
{
|
||||
// This has very poor performance with any size for GetIncrementalReadChunkSize, but especially for larger sizes
|
||||
MYSQL_ROW row = mysql_fetch_row (result);
|
||||
if (row != 0)
|
||||
{
|
||||
const char * content = row [0];
|
||||
unsigned long contentLength=mysql_fetch_lengths (result) [0];
|
||||
memcpy(preallocatedDestination,content,contentLength);
|
||||
mysql_free_result (result);
|
||||
|
||||
filePartConnectionMutex.Unlock();
|
||||
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
mysql_free_result (result);
|
||||
}
|
||||
|
||||
filePartConnectionMutex.Unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
const int AutopatcherMySQLRepository::GetIncrementalReadChunkSize(void) const
|
||||
{
|
||||
// AutopatcherMySQLRepository::GetFilePart is extremely slow with larger files
|
||||
return 262144*4;
|
||||
}
|
||||
Reference in New Issue
Block a user