2178 lines
74 KiB
C++
2178 lines
74 KiB
C++
/*
|
|
* 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-2017, 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.
|
|
*/
|
|
|
|
/// \file
|
|
/// \brief An implementation of the AutopatcherRepositoryInterface to use PostgreSQL to store the relevant data
|
|
|
|
|
|
#include "AutopatcherPostgreRepository.h"
|
|
#include "slikenet/AutopatcherPatchContext.h"
|
|
#include "slikenet/FileList.h"
|
|
// libpq-fe.h is part of PostgreSQL which must be installed on this computer to use the PostgreRepository
|
|
#include "libpq-fe.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"
|
|
// localtime
|
|
#include <time.h>
|
|
|
|
static const unsigned HASH_LENGTH=sizeof(unsigned int);
|
|
|
|
// ntohl
|
|
#ifdef _WIN32
|
|
#include <Winsock2.h>
|
|
#else
|
|
#include <netinet/in.h>
|
|
#endif
|
|
|
|
// 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
|
|
|
|
using namespace SLNet;
|
|
|
|
AutopatcherPostgreRepository::AutopatcherPostgreRepository()
|
|
{
|
|
filePartConnection=0;
|
|
}
|
|
AutopatcherPostgreRepository::~AutopatcherPostgreRepository()
|
|
{
|
|
if (filePartConnection)
|
|
PQfinish(filePartConnection);
|
|
}
|
|
bool AutopatcherPostgreRepository::CreateAutopatcherTables(void)
|
|
{
|
|
if (isConnected==false)
|
|
return false;
|
|
|
|
const char *command =
|
|
"BEGIN;"
|
|
"CREATE TABLE Applications ("
|
|
"applicationID serial PRIMARY KEY UNIQUE,"
|
|
"applicationName text NOT NULL UNIQUE,"
|
|
"changeSetID integer NOT NULL DEFAULT 0,"
|
|
"userName text NOT NULL"
|
|
");"
|
|
"CREATE TABLE FileVersionHistory ("
|
|
"fileID serial PRIMARY KEY UNIQUE,"
|
|
"applicationID integer REFERENCES Applications ON DELETE CASCADE,"
|
|
"filename text NOT NULL,"
|
|
"fileLength integer,"
|
|
"content bytea,"
|
|
"contentHash bytea,"
|
|
"patch bytea,"
|
|
"patchAlgorithm integer,"
|
|
"createFile boolean NOT NULL,"
|
|
"modificationDate double precision DEFAULT (EXTRACT(EPOCH FROM now())),"
|
|
"lastSentDate double precision,"
|
|
"timesSent integer NOT NULL DEFAULT 0,"
|
|
"changeSetID integer NOT NULL,"
|
|
"userName text NOT NULL,"
|
|
"CONSTRAINT file_has_data CHECK ( createFile=FALSE OR ((content IS NOT NULL) AND (contentHash IS NOT NULL) AND (fileLength IS NOT NULL) ) )"
|
|
");"
|
|
"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;"
|
|
"COMMIT;";
|
|
|
|
PGresult *result;
|
|
//sqlCommandMutex.Lock();
|
|
bool res = ExecuteBlockingCommand(command, &result, true);
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return res;
|
|
}
|
|
bool AutopatcherPostgreRepository2::CreateAutopatcherTables(void)
|
|
{
|
|
if (isConnected==false)
|
|
return false;
|
|
|
|
const char *command =
|
|
"BEGIN;"
|
|
"CREATE TABLE Applications ("
|
|
"applicationID serial PRIMARY KEY UNIQUE,"
|
|
"applicationName text NOT NULL UNIQUE,"
|
|
"changeSetID integer NOT NULL DEFAULT 0,"
|
|
"userName text NOT NULL"
|
|
");"
|
|
"CREATE TABLE FileVersionHistory ("
|
|
"fileID serial PRIMARY KEY UNIQUE,"
|
|
"applicationID integer REFERENCES Applications ON DELETE CASCADE,"
|
|
"filename text NOT NULL,"
|
|
"fileLength integer,"
|
|
"pathToContent text,"
|
|
"contentHash bytea,"
|
|
"patch bytea,"
|
|
"patchAlgorithm integer,"
|
|
"createFile boolean NOT NULL,"
|
|
"modificationDate double precision DEFAULT (EXTRACT(EPOCH FROM now())),"
|
|
"lastSentDate double precision,"
|
|
"timesSent integer NOT NULL DEFAULT 0,"
|
|
"changeSetID integer NOT NULL,"
|
|
"userName text NOT NULL,"
|
|
"CONSTRAINT file_has_data CHECK ( createFile=FALSE OR ((contentHash IS NOT NULL) AND (fileLength IS NOT NULL) ) )"
|
|
");"
|
|
"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;"
|
|
"COMMIT;";
|
|
|
|
PGresult *result;
|
|
//sqlCommandMutex.Lock();
|
|
bool res = ExecuteBlockingCommand(command, &result, true);
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return res;
|
|
}
|
|
bool AutopatcherPostgreRepository::DestroyAutopatcherTables(void)
|
|
{
|
|
if (isConnected==false)
|
|
return false;
|
|
|
|
const char *command =
|
|
"BEGIN;"
|
|
"DROP TABLE Applications CASCADE;"
|
|
"DROP TABLE FileVersionHistory CASCADE;"
|
|
"COMMIT;";
|
|
|
|
PGresult *result;
|
|
//sqlCommandMutex.Lock();
|
|
bool b = ExecuteBlockingCommand(command, &result, true);
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return b;
|
|
}
|
|
bool AutopatcherPostgreRepository::AddApplication(const char *applicationName, const char *userName)
|
|
{
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return false;
|
|
if (strlen(userName)>100)
|
|
return false;
|
|
|
|
sprintf_s(query, "INSERT INTO Applications (applicationName, userName) VALUES ('%s', '%s');", GetEscapedString(applicationName).C_String(), GetEscapedString(userName).C_String());
|
|
PGresult *result;
|
|
//sqlCommandMutex.Lock();
|
|
bool b = ExecuteBlockingCommand(query, &result, false);
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return b;
|
|
}
|
|
bool AutopatcherPostgreRepository::RemoveApplication(const char *applicationName)
|
|
{
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return false;
|
|
|
|
sprintf_s(query, "DELETE FROM Applications WHERE applicationName='%s';", GetEscapedString(applicationName).C_String());
|
|
PGresult *result;
|
|
//sqlCommandMutex.Lock();
|
|
bool b = ExecuteBlockingCommand(query, &result, false);
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return b;
|
|
}
|
|
|
|
bool AutopatcherPostgreRepository::GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate)
|
|
{
|
|
PGresult *result;
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return false;
|
|
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
|
sprintf_s(query, "SELECT applicationID FROM applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
int numRows;
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",applicationName);
|
|
return false;
|
|
}
|
|
char *res;
|
|
res = PQgetvalue(result,0,0);
|
|
int applicationID;
|
|
applicationID=atoi(res);
|
|
PQclear(result);
|
|
if (sinceDate!=0.0)
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) filename, fileLength, contentHash, createFile FROM FileVersionHistory WHERE applicationId=%i AND modificationDate > %f ORDER BY filename, fileId DESC;", applicationID, sinceDate);
|
|
else
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) filename, fileLength, contentHash, createFile FROM FileVersionHistory WHERE applicationId=%i ORDER BY filename, fileId DESC;", applicationID);
|
|
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, false)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
int filenameColumnIndex = PQfnumber(result, "filename");
|
|
int contentHashColumnIndex = PQfnumber(result, "contentHash");
|
|
int createFileColumnIndex = PQfnumber(result, "createFile");
|
|
int fileLengthColumnIndex = PQfnumber(result, "fileLength");
|
|
char *hardDriveFilename;
|
|
char *hardDriveHash;
|
|
char *createFileResult;
|
|
char *fileLengthPtr;
|
|
int rowIndex;
|
|
int fileLength;
|
|
RakAssert(PQfsize(result, fileLengthColumnIndex)==sizeof(fileLength));
|
|
|
|
numRows = PQntuples(result);
|
|
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++)
|
|
{
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
hardDriveFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
if (createFileResult[0]==1)
|
|
{
|
|
hardDriveHash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
fileLengthPtr = PQgetvalue(result, rowIndex, fileLengthColumnIndex);
|
|
memcpy(&fileLength, fileLengthPtr, sizeof(fileLength));
|
|
fileLength=ntohl(fileLength); // This is asinine...
|
|
addedOrModifiedFilesWithHashData->AddFile(hardDriveFilename, hardDriveFilename, hardDriveHash, HASH_LENGTH, fileLength, FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
else
|
|
{
|
|
deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
int AutopatcherPostgreRepository::GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList)
|
|
{
|
|
PGresult *result;
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return 0;
|
|
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
|
sprintf_s(query, "SELECT applicationID FROM applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return 0;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
int numRows;
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",applicationName);
|
|
return 0;
|
|
}
|
|
char *res;
|
|
res = PQgetvalue(result,0,0);
|
|
int applicationID;
|
|
applicationID=atoi(res);
|
|
PQclear(result);
|
|
|
|
// Go through the input list.
|
|
unsigned inputIndex;
|
|
char *userHash, *contentHash;
|
|
SLNet::RakString userFilename;
|
|
// char *content;
|
|
// char *fileId, *fileLength;
|
|
char *patch;
|
|
// int contentLength;
|
|
int patchLength;
|
|
// int contentColumnIndex;
|
|
int contentHashIndex, fileIdIndex, fileLengthIndex;
|
|
const char *outTemp[2];
|
|
int outLengths[2];
|
|
int formats[2];
|
|
PGresult *patchResult;
|
|
for (inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++)
|
|
{
|
|
userHash=input->fileList[inputIndex].data;
|
|
userFilename=input->fileList[inputIndex].filename;
|
|
|
|
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 DISTINCT ON (filename) content FROM FileVersionHistory WHERE applicationId=%i AND filename=$1::text ORDER BY filename, fileId DESC;", applicationID);
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) fileId, fileLength, changeSetID FROM FileVersionHistory WHERE applicationId=%i AND filename=$1::text ORDER BY filename, fileId DESC;", applicationID);
|
|
outTemp[0]=userFilename.C_String();
|
|
outLengths[0]=(int) userFilename.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, false)==false)
|
|
{
|
|
PQclear(result);
|
|
return 0;
|
|
}
|
|
numRows = PQntuples(result);
|
|
if (numRows>0)
|
|
{
|
|
// content = PQgetvalue(result, 0, 0);
|
|
// contentLength=PQgetlength(result, 0, 0);
|
|
// patchList->AddFile(userFilename, content, contentLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0),false);
|
|
|
|
fileIdIndex = PQfnumber(result, "fileId");
|
|
fileLengthIndex = PQfnumber(result, "fileLength");
|
|
int changeSetIdIndex = PQfnumber(result, "changeSetID");
|
|
int fileId = ntohl(*((int*)PQgetvalue(result, 0, fileIdIndex)));
|
|
int fileLength = ntohl(*((int*)PQgetvalue(result, 0, fileLengthIndex)));
|
|
int changeSetId = ntohl(*((int*)PQgetvalue(result, 0, changeSetIdIndex)));
|
|
|
|
if (allowDownloadOfOriginalUnmodifiedFiles==false && changeSetId==0)
|
|
{
|
|
printf("Failure: allowDownloadOfOriginalUnmodifiedFiles==false for %s length %i\n", userFilename.C_String(), fileLength);
|
|
PQclear(result);
|
|
return -1;
|
|
}
|
|
|
|
patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),true);
|
|
}
|
|
PQclear(result);
|
|
}
|
|
else // Assuming the user does have a hash.
|
|
{
|
|
if (input->fileList[inputIndex].dataLengthBytes!=HASH_LENGTH)
|
|
return 0;
|
|
|
|
// Get the hash and ID of the latest version of this file, by filename.
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) contentHash, fileId, fileLength FROM FileVersionHistory WHERE applicationId=%i AND filename=$1::text ORDER BY filename, fileId DESC;", applicationID);
|
|
outTemp[0]=userFilename.C_String();
|
|
outLengths[0]=(int)userFilename.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, false)==false)
|
|
{
|
|
PQclear(result);
|
|
return 0;
|
|
}
|
|
numRows = PQntuples(result);
|
|
if (numRows>0)
|
|
{
|
|
contentHashIndex = PQfnumber(result, "contentHash");
|
|
fileIdIndex = PQfnumber(result, "fileId");
|
|
fileLengthIndex = PQfnumber(result, "fileLength");
|
|
contentHash = PQgetvalue(result, 0, contentHashIndex);
|
|
int fileIdInt = ntohl(*((int*)PQgetvalue(result, 0, fileIdIndex)));
|
|
int fileLengthInt = ntohl(*((int*)PQgetvalue(result, 0, fileLengthIndex)));
|
|
// fileId = PQgetvalue(result, 0, fileIdIndex);
|
|
// fileLength = PQgetvalue(result, 0, fileLengthIndex);
|
|
// int fileLengthInt = ntohl(*((int*)fileLength));
|
|
|
|
// unsigned int hash1 = SuperFastHashFile("C:/temp/RakNet_Icon_Final.psd");
|
|
// unsigned int hash2 = SuperFastHashFile("C:/temp/AutopatcherClient/RakNet_Icon_Final.psd");
|
|
|
|
if (memcmp(contentHash, userHash, HASH_LENGTH)!=0)
|
|
{
|
|
// Look up by user hash/filename/applicationID, returning the patch
|
|
sprintf_s(query, "SELECT patch, patchAlgorithm, fileId FROM FileVersionHistory WHERE applicationId=%i AND filename=$1::text AND contentHash=$2::bytea;", applicationID);
|
|
outTemp[0]=userFilename.C_String();
|
|
outLengths[0]=(int)userFilename.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
outTemp[1]=userHash;
|
|
outLengths[1]=HASH_LENGTH;
|
|
formats[1]=PQEXECPARAM_FORMAT_BINARY;
|
|
//sqlCommandMutex.Lock();
|
|
patchResult = PQexecParams(pgConn, query,2,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
|
|
if (IsResultSuccessful(patchResult, false)==false)
|
|
{
|
|
PQclear(patchResult);
|
|
return 0;
|
|
}
|
|
numRows = PQntuples(patchResult);
|
|
if (numRows==0)
|
|
{
|
|
PQclear(patchResult);
|
|
|
|
// outTemp[0]=fileId;
|
|
// outLengths[0]=PQfsize(result, fileIdIndex);
|
|
// formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
|
|
/*
|
|
// Get 32000000 bytes at a time to workaround http://support.microsoft.com/kb/q201213
|
|
int fileIndex=0;
|
|
char *file = SLNet::OP_NEW char[fileLengthInt];
|
|
while (fileIndex < fileLengthInt)
|
|
{
|
|
sprintf_s(query, "SELECT substring(content from %i for 32000000) FROM FileVersionHistory WHERE fileId=$1::integer;", fileIndex, fileId);
|
|
patchResult = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
if (IsResultSuccessful(patchResult, false)==false)
|
|
{
|
|
PQclear(patchResult);
|
|
return false;
|
|
}
|
|
content = PQgetvalue(patchResult, 0, 0);
|
|
contentLength=PQgetlength(patchResult, 0, 0);
|
|
assert(contentLength==32000000 || contentLength==fileLengthInt-fileIndex);
|
|
memcpy(file+fileIndex,content,contentLength);
|
|
PQclear(patchResult);
|
|
fileIndex+=32000000;
|
|
}
|
|
*/
|
|
|
|
/*
|
|
sprintf_s(query, "SELECT content FROM FileVersionHistory WHERE fileId=%i", fileIdInt);
|
|
patchResult = PQexecParams(pgConn, query,0,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
if (IsResultSuccessful(patchResult, false)==false)
|
|
{
|
|
PQclear(patchResult);
|
|
return false;
|
|
}
|
|
char *content = PQgetvalue(patchResult, 0, 0);
|
|
int contentLength=PQgetlength(patchResult, 0, 0);
|
|
unsigned int hash1 = SuperFastHashFile("C:/temp/RakNet_Icon_Final.psd");
|
|
unsigned int hash2 = SuperFastHash(content, contentLength);
|
|
*/
|
|
|
|
// No patch, add the file
|
|
patchList->AddFile(userFilename,userFilename, 0, fileLengthInt, fileLengthInt, FileListNodeContext(PC_WRITE_FILE,fileIdInt,0,0), true);
|
|
// patchList->AddFile(userFilename, file, fileLengthInt, contentLength, FileListNodeContext(PC_WRITE_FILE,0), true);
|
|
// SLNet::OP_DELETE_ARRAY file;
|
|
}
|
|
else
|
|
{
|
|
int patchColumnIndex = PQfnumber(patchResult, "patch");
|
|
int patchAlgorithmColumnIndex = PQfnumber(patchResult, "patchAlgorithm");
|
|
|
|
|
|
// Otherwise, write the hash of the new version and then write the patch to get to that version.
|
|
//
|
|
// int patchAlgorithm = ntohl(*((int*)PQgetvalue(result, 0, patchAlgorithmColumnIndex)));
|
|
|
|
const char *tv = PQgetvalue(patchResult, 0, patchAlgorithmColumnIndex);
|
|
int patchAlgorithm;
|
|
if (tv)
|
|
patchAlgorithm = ntohl(*((int*)tv));
|
|
else
|
|
patchAlgorithm = 0;
|
|
|
|
int patchFileIdColumnIndex = PQfnumber(patchResult, "fileId");
|
|
const char *tv2 = PQgetvalue(patchResult, 0, patchFileIdColumnIndex);
|
|
int patchIdInt = ntohl(*((int*)tv2));
|
|
|
|
patchLength=PQgetlength(patchResult, 0, patchColumnIndex);
|
|
|
|
bool useReference = patchLength > 1048576;
|
|
char *temp;
|
|
|
|
if (useReference==false)
|
|
temp = SLNet::OP_NEW_ARRAY<char>(patchLength + HASH_LENGTH, _FILE_AND_LINE_ );
|
|
else
|
|
temp = 0;
|
|
|
|
if (temp!=0)
|
|
{
|
|
patch = PQgetvalue(patchResult, 0, patchColumnIndex);
|
|
memcpy(temp, contentHash, HASH_LENGTH);
|
|
memcpy(temp+HASH_LENGTH, patch, patchLength);
|
|
// int len;
|
|
// assert(PQfsize(result, fileLengthIndex)==sizeof(fileLength));
|
|
// memcpy(&len, fileLength, sizeof(len));
|
|
// len=ntohl(len);
|
|
// printf("send patch %i bytes\n", patchLength);
|
|
// for (int i=0; i < patchLength; i++)
|
|
// printf("%i ", patch[i]);
|
|
// printf("\n");
|
|
|
|
patchList->AddFile(userFilename,userFilename, temp, HASH_LENGTH+patchLength, fileLengthInt, FileListNodeContext(PC_HASH_1_WITH_PATCH,0,patchAlgorithm,0),false );
|
|
}
|
|
else
|
|
{
|
|
patchList->AddFile(userFilename,userFilename, temp, HASH_LENGTH+patchLength, fileLengthInt, FileListNodeContext(PC_HASH_1_WITH_PATCH,fileIdInt,patchAlgorithm,patchIdInt),true );
|
|
}
|
|
PQclear(patchResult);
|
|
SLNet::OP_DELETE_ARRAY(temp, _FILE_AND_LINE_);
|
|
}
|
|
}
|
|
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.
|
|
}
|
|
|
|
PQclear(result);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
bool AutopatcherPostgreRepository::GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime)
|
|
{
|
|
PGresult *result;
|
|
char query[1024];
|
|
if (applicationName.GetLength()>100)
|
|
return false;
|
|
|
|
(*priorRowPatchTime)=0;
|
|
(*mostRecentRowPatchTime)=0;
|
|
|
|
const char *outTemp[2];
|
|
int outLengths[2];
|
|
int formats[2];
|
|
|
|
if (applicationName.GetLength()==0)
|
|
{
|
|
strcpy_s(query,
|
|
"SELECT tbl1.applicationName, tbl4.* FROM "
|
|
"(SELECT applicationID, applicationName FROM Applications) as tbl1, "
|
|
"(SELECT tbl2.applicationId, tbl2.fileId, tbl2.fileName, tbl2.fileLength, tbl2.contentHash, tbl2.createFile, tbl2.changeSetId, tbl2.content, tbl3.patch, tbl3.patchAlgorithm, tbl3.contentHash as priorHash FROM "
|
|
"(SELECT * From FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory) AND changeSetId!=0 ) as tbl2 "
|
|
"LEFT OUTER JOIN "
|
|
"(SELECT patch, patchAlgorithm, fileName, contentHash FROM FileVersionHistory WHERE "
|
|
"(changeSetId = (SELECT changeSetId FROM FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory) LIMIT 1) - 1 )) as tbl3 "
|
|
"ON tbl2.filename=tbl3.filename) as tbl4; "
|
|
);
|
|
|
|
result = PQexecParams(pgConn, query,0,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strcpy_s(query,
|
|
"SELECT tbl2.fileId, tbl2.fileName, tbl2.fileLength, tbl2.contentHash, tbl2.createFile, tbl2.changeSetId, tbl2.content, tbl3.patch, tbl3.patchAlgorithm, tbl3.contentHash as priorHash FROM "
|
|
"(SELECT * From FileVersionHistory WHERE changeSetId=(SELECT MAX(changeSetId) FROM Applications WHERE applicationName=$1::text)-1 AND changeSetId!=0 "
|
|
") as tbl2 "
|
|
"LEFT OUTER JOIN "
|
|
"(SELECT fileName, patch, patchAlgorithm, contentHash From FileVersionHistory WHERE changeSetId=(SELECT MAX(changeSetId) FROM Applications WHERE applicationName=$2::text)-2 "
|
|
") as tbl3 "
|
|
"ON tbl2.filename=tbl3.filename;"
|
|
);
|
|
|
|
outTemp[0]=applicationName.C_String();
|
|
outLengths[0]=(int) applicationName.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
outTemp[1]=applicationName.C_String();
|
|
outLengths[1]=(int) applicationName.GetLength();
|
|
formats[1]=PQEXECPARAM_FORMAT_BINARY;
|
|
result = PQexecParams(pgConn, query,2,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int numRows;
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
// Nothing was ever patched. However, read mostRecentRowPatchTime if possible
|
|
if (applicationName.GetLength()==0)
|
|
{
|
|
// Lookup application if unspecified
|
|
PGresult *result3;
|
|
|
|
strcpy_s(query,
|
|
"SELECT applicationName FROM Applications as tbl1, "
|
|
"(SELECT applicationId From FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory LIMIT 1) LIMIT 1) as tbl2 "
|
|
"WHERE tbl1.applicationID=tbl2.applicationID;"
|
|
);
|
|
|
|
result3 = PQexecParams(pgConn, query,0,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result3, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
if (PQntuples(result3)==0)
|
|
{
|
|
// No applications at all
|
|
PQclear(result);
|
|
PQclear(result3);
|
|
return false;
|
|
}
|
|
|
|
int applicationNameColumnIndex = PQfnumber(result, "applicationName");
|
|
applicationName = PQgetvalue(result3, 0, applicationNameColumnIndex);
|
|
PQclear(result3);
|
|
}
|
|
}
|
|
else if (applicationName.GetLength()==0)
|
|
{
|
|
int applicationNameColumnIndex = PQfnumber(result, "applicationName");
|
|
applicationName = PQgetvalue(result, 0, applicationNameColumnIndex);
|
|
}
|
|
|
|
SLNet::RakString escapedApplicationName2 = GetEscapedString(applicationName);
|
|
PGresult *result2;
|
|
char *ts;
|
|
int numRows2;
|
|
|
|
// For the given application, get the highest file date
|
|
sprintf_s(query,
|
|
"SELECT modificationDate from FileVersionHistory WHERE changeSetId=(SELECT changeSetId FROM Applications WHERE applicationName='%s' AND changeSetId!=0 LIMIT 1)-1 LIMIT 1;"
|
|
, escapedApplicationName2.C_String());
|
|
if (ExecuteBlockingCommand(query, &result2, false)==false)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
numRows2 = PQntuples(result2);
|
|
if (numRows2==0)
|
|
{
|
|
// No application
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
ts=PQgetvalue(result2, 0, 0);
|
|
*mostRecentRowPatchTime=atof(ts);
|
|
PQclear(result2);
|
|
|
|
if (numRows==0)
|
|
{
|
|
// No patches to serve
|
|
*priorRowPatchTime=0;
|
|
|
|
PQclear(result);
|
|
return true;
|
|
}
|
|
|
|
|
|
// In SQL, SELECT (EXTRACT(EPOCH FROM now())) is equivalent to time() function
|
|
sprintf_s(query,
|
|
"SELECT modificationDate from FileVersionHistory WHERE changeSetId=(SELECT changeSetId FROM Applications WHERE applicationName='%s' AND changeSetId!=0 LIMIT 1)-2 LIMIT 1;"
|
|
, escapedApplicationName2.C_String());
|
|
if (ExecuteBlockingCommand(query, &result2, false)==false)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
numRows2 = PQntuples(result2);
|
|
if (numRows2==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
ts=PQgetvalue(result2, 0, 0);
|
|
*priorRowPatchTime=atof(ts);
|
|
PQclear(result2);
|
|
|
|
|
|
int fileIdColumnIndex = PQfnumber(result, "fileId");
|
|
int filenameColumnIndex = PQfnumber(result, "filename");
|
|
int fileLengthColumnIndex = PQfnumber(result, "fileLength");
|
|
int contentColumnIndex = PQfnumber(result, "content");
|
|
int contentHashColumnIndex = PQfnumber(result, "contentHash");
|
|
int patchColumnIndex = PQfnumber(result, "patch");
|
|
int patchAlgorithmColumnIndex = PQfnumber(result, "patchAlgorithm");
|
|
int priorHashColumnIndex = PQfnumber(result, "priorHash");
|
|
int createFileColumnIndex = PQfnumber(result, "createFile");
|
|
|
|
char *createFileResult;
|
|
char *hardDriveFilename;
|
|
char *hardDriveHash;
|
|
char *fileData;
|
|
char *patch;
|
|
int rowIndex;
|
|
int patchLength;
|
|
char *contentHash;
|
|
|
|
// AutopatcherPostgreRepository::GetPatches fills patchList with
|
|
// either PC_WRITE_FILE and filename (but no data) when the user has no hash
|
|
// PC_WRITE_FILE with filename (but no data) when the user has a hash that does not match any patch
|
|
// PC_HASH_1_WITH_PATCH with the filename, (HASH,patch) if the patch was found
|
|
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++)
|
|
{
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
hardDriveFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
if (createFileResult[0]==1)
|
|
{
|
|
hardDriveHash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
|
|
int fileId = ntohl(*((int*)PQgetvalue(result, rowIndex, fileIdColumnIndex)));
|
|
int fileLength = ntohl(*((int*)PQgetvalue(result, rowIndex, fileLengthColumnIndex)));
|
|
contentHash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
|
|
patchLength=PQgetlength(result, rowIndex, patchColumnIndex);
|
|
if (patchLength==0)
|
|
{
|
|
// New file that never before existed
|
|
|
|
// OK
|
|
addedOrModifiedFileHashes->AddFile(hardDriveFilename,hardDriveFilename, contentHash, HASH_LENGTH, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,false);
|
|
|
|
fileData = PQgetvalue(result, rowIndex, contentColumnIndex);
|
|
addedFiles->AddFile(hardDriveFilename,hardDriveFilename, fileData, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,false);
|
|
}
|
|
else
|
|
{
|
|
// Patch to next version
|
|
patch = PQgetvalue(result, rowIndex, patchColumnIndex);
|
|
// int patchAlgorithm = ntohl(*((int*)PQgetvalue(result, rowIndex, patchAlgorithmColumnIndex)));
|
|
|
|
const char *tv = PQgetvalue(result, rowIndex, patchAlgorithmColumnIndex);
|
|
int patchAlgorithm;
|
|
if (tv)
|
|
patchAlgorithm = ntohl(*((int*)tv));
|
|
else
|
|
patchAlgorithm = 0;
|
|
|
|
char *temp = (char *) rakMalloc_Ex(patchLength + HASH_LENGTH*2, _FILE_AND_LINE_ );
|
|
RakAssert(temp);
|
|
char *priorHash = PQgetvalue(result, rowIndex, priorHashColumnIndex);
|
|
memcpy(temp, priorHash, HASH_LENGTH);
|
|
memcpy(temp+HASH_LENGTH, contentHash, HASH_LENGTH);
|
|
memcpy(temp+HASH_LENGTH*2, patch, patchLength);
|
|
|
|
// OK
|
|
addedOrModifiedFileHashes->AddFile(hardDriveFilename,hardDriveFilename, contentHash, HASH_LENGTH, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,false);
|
|
patchedFiles->AddFile(hardDriveFilename,hardDriveFilename, temp, HASH_LENGTH*2+patchLength, patchLength, FileListNodeContext(PC_HASH_2_WITH_PATCH,fileId,patchAlgorithm,0), false, true );
|
|
|
|
// fileData = PQgetvalue(result, rowIndex, contentColumnIndex);
|
|
// updatedFiles->AddFile(hardDriveFilename,hardDriveFilename, fileData, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId),false,false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deleted file
|
|
deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
}
|
|
|
|
PQclear(result);
|
|
return true;
|
|
}
|
|
bool AutopatcherPostgreRepository2::GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime)
|
|
{
|
|
PGresult *result;
|
|
char query[1024];
|
|
if (applicationName.GetLength()>100)
|
|
return false;
|
|
|
|
(*priorRowPatchTime)=0;
|
|
(*mostRecentRowPatchTime)=0;
|
|
|
|
const char *outTemp[2];
|
|
int outLengths[2];
|
|
int formats[2];
|
|
|
|
if (applicationName.GetLength()==0)
|
|
{
|
|
strcpy_s(query,
|
|
"SELECT tbl1.applicationName, tbl4.* FROM "
|
|
"(SELECT applicationID, applicationName FROM Applications) as tbl1, "
|
|
"(SELECT tbl2.applicationId, tbl2.fileId, tbl2.fileName, tbl2.fileLength, tbl2.contentHash, tbl2.createFile, tbl2.changeSetId, tbl2.pathToContent, tbl3.patch, tbl3.patchAlgorithm, tbl3.contentHash as priorHash FROM "
|
|
"(SELECT * From FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory) AND changeSetId!=0 ) as tbl2 "
|
|
"LEFT OUTER JOIN "
|
|
"(SELECT patch, patchAlgorithm, fileName, contentHash FROM FileVersionHistory WHERE "
|
|
"(changeSetId = (SELECT changeSetId FROM FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory) LIMIT 1) - 1 )) as tbl3 "
|
|
"ON tbl2.filename=tbl3.filename) as tbl4; "
|
|
);
|
|
|
|
result = PQexecParams(pgConn, query,0,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strcpy_s(query,
|
|
"SELECT tbl2.fileId, tbl2.fileName, tbl2.fileLength, tbl2.contentHash, tbl2.createFile, tbl2.changeSetId, tbl2.pathToContent, tbl3.patch, tbl3.patchAlgorithm, tbl3.contentHash as priorHash FROM "
|
|
"(SELECT * From FileVersionHistory WHERE changeSetId=(SELECT MAX(changeSetId) FROM Applications WHERE applicationName=$1::text)-1 AND changeSetId!=0 "
|
|
") as tbl2 "
|
|
"LEFT OUTER JOIN "
|
|
"(SELECT fileName, patch, patchAlgorithm, contentHash From FileVersionHistory WHERE changeSetId=(SELECT MAX(changeSetId) FROM Applications WHERE applicationName=$2::text)-2 "
|
|
") as tbl3 "
|
|
"ON tbl2.filename=tbl3.filename;"
|
|
);
|
|
|
|
outTemp[0]=applicationName.C_String();
|
|
outLengths[0]=(int) applicationName.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
outTemp[1]=applicationName.C_String();
|
|
outLengths[1]=(int) applicationName.GetLength();
|
|
formats[1]=PQEXECPARAM_FORMAT_BINARY;
|
|
result = PQexecParams(pgConn, query,2,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int numRows;
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
// Nothing was ever patched. However, read mostRecentRowPatchTime if possible
|
|
if (applicationName.GetLength()==0)
|
|
{
|
|
// Lookup application if unspecified
|
|
PGresult *result3;
|
|
|
|
strcpy_s(query,
|
|
"SELECT applicationName FROM Applications as tbl1, "
|
|
"(SELECT applicationId From FileVersionHistory WHERE modificationDate=(select MAX(modificationDate) from FileVersionHistory LIMIT 1) LIMIT 1) as tbl2 "
|
|
"WHERE tbl1.applicationID=tbl2.applicationID;"
|
|
);
|
|
|
|
result3 = PQexecParams(pgConn, query,0,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result3, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
if (PQntuples(result3)==0)
|
|
{
|
|
// No applications at all
|
|
PQclear(result);
|
|
PQclear(result3);
|
|
return false;
|
|
}
|
|
|
|
int applicationNameColumnIndex = PQfnumber(result, "applicationName");
|
|
applicationName = PQgetvalue(result3, 0, applicationNameColumnIndex);
|
|
PQclear(result3);
|
|
}
|
|
}
|
|
else if (applicationName.GetLength()==0)
|
|
{
|
|
int applicationNameColumnIndex = PQfnumber(result, "applicationName");
|
|
applicationName = PQgetvalue(result, 0, applicationNameColumnIndex);
|
|
}
|
|
|
|
SLNet::RakString escapedApplicationName2 = GetEscapedString(applicationName);
|
|
PGresult *result2;
|
|
char *ts;
|
|
int numRows2;
|
|
|
|
// For the given application, get the highest file date
|
|
sprintf_s(query,
|
|
"SELECT modificationDate from FileVersionHistory WHERE changeSetId=(SELECT changeSetId FROM Applications WHERE applicationName='%s' AND changeSetId!=0 LIMIT 1)-1 LIMIT 1;"
|
|
, escapedApplicationName2.C_String());
|
|
if (ExecuteBlockingCommand(query, &result2, false)==false)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
numRows2 = PQntuples(result2);
|
|
if (numRows2==0)
|
|
{
|
|
// No application
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
ts=PQgetvalue(result2, 0, 0);
|
|
*mostRecentRowPatchTime=atof(ts);
|
|
PQclear(result2);
|
|
|
|
if (numRows==0)
|
|
{
|
|
// No patches to serve
|
|
*priorRowPatchTime=0;
|
|
|
|
PQclear(result);
|
|
return true;
|
|
}
|
|
|
|
|
|
// In SQL, SELECT (EXTRACT(EPOCH FROM now())) is equivalent to time() function
|
|
sprintf_s(query,
|
|
"SELECT modificationDate from FileVersionHistory WHERE changeSetId=(SELECT changeSetId FROM Applications WHERE applicationName='%s' AND changeSetId!=0 LIMIT 1)-2 LIMIT 1;"
|
|
, escapedApplicationName2.C_String());
|
|
if (ExecuteBlockingCommand(query, &result2, false)==false)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
numRows2 = PQntuples(result2);
|
|
if (numRows2==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Query is bad in file %s at line %i in function GetMostRecentChangelistWithPatches\n",_FILE_AND_LINE_);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
return false;
|
|
}
|
|
ts=PQgetvalue(result2, 0, 0);
|
|
*priorRowPatchTime=atof(ts);
|
|
PQclear(result2);
|
|
|
|
|
|
int fileIdColumnIndex = PQfnumber(result, "fileId");
|
|
int filenameColumnIndex = PQfnumber(result, "filename");
|
|
int fileLengthColumnIndex = PQfnumber(result, "fileLength");
|
|
int pathToContentColumnIndex = PQfnumber(result, "pathToContent");
|
|
int contentHashColumnIndex = PQfnumber(result, "contentHash");
|
|
int patchColumnIndex = PQfnumber(result, "patch");
|
|
int patchAlgorithmColumnIndex = PQfnumber(result, "patchAlgorithm");
|
|
int priorHashColumnIndex = PQfnumber(result, "priorHash");
|
|
int createFileColumnIndex = PQfnumber(result, "createFile");
|
|
|
|
char *createFileResult;
|
|
char *hardDriveFilename;
|
|
char *hardDriveHash;
|
|
//char *fileData;
|
|
char *patch;
|
|
int rowIndex;
|
|
int patchLength;
|
|
char *contentHash;
|
|
|
|
// AutopatcherPostgreRepository::GetPatches fills patchList with
|
|
// either PC_WRITE_FILE and filename (but no data) when the user has no hash
|
|
// PC_WRITE_FILE with filename (but no data) when the user has a hash that does not match any patch
|
|
// PC_HASH_1_WITH_PATCH with the filename, (HASH,patch) if the patch was found
|
|
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++)
|
|
{
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
hardDriveFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
if (createFileResult[0]==1)
|
|
{
|
|
hardDriveHash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
|
|
int fileId = ntohl(*((int*)PQgetvalue(result, rowIndex, fileIdColumnIndex)));
|
|
int fileLength = ntohl(*((int*)PQgetvalue(result, rowIndex, fileLengthColumnIndex)));
|
|
contentHash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
|
|
patchLength=PQgetlength(result, rowIndex, patchColumnIndex);
|
|
if (patchLength==0)
|
|
{
|
|
// New file that never before existed
|
|
|
|
// fileData is given to AddFile
|
|
char *pathToContent = PQgetvalue(result, rowIndex, pathToContentColumnIndex);
|
|
FILE *fp;
|
|
if (fopen_s(&fp, pathToContent, "rb") != 0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Cannot open file %s in file %s at line %i in function GetMostRecentChangelistWithPatches\n",pathToContent, _FILE_AND_LINE_);
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
|
|
char *fileData = (char*) rakMalloc_Ex(fileLength, _FILE_AND_LINE_);
|
|
RakAssert(fileData);
|
|
fread(fileData, fileLength, 1, fp);
|
|
fclose(fp);
|
|
|
|
// OK
|
|
addedOrModifiedFileHashes->AddFile(hardDriveFilename,hardDriveFilename, contentHash, HASH_LENGTH, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,false);
|
|
|
|
// fileData = PQgetvalue(result, rowIndex, contentColumnIndex);
|
|
// const char *pathToContent = PQgetvalue(result, rowIndex, pathToContentColumnIndex);
|
|
|
|
// Last parameter is take the fileData pointer
|
|
addedFiles->AddFile(hardDriveFilename,hardDriveFilename, fileData, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,true);
|
|
}
|
|
else
|
|
{
|
|
// Patch to next version
|
|
patch = PQgetvalue(result, rowIndex, patchColumnIndex);
|
|
const char *tv = PQgetvalue(result, rowIndex, patchAlgorithmColumnIndex);
|
|
int patchAlgorithm;
|
|
if (tv)
|
|
patchAlgorithm = ntohl(*((int*)tv));
|
|
else
|
|
patchAlgorithm = 0;
|
|
|
|
char *temp = (char *) rakMalloc_Ex(patchLength + HASH_LENGTH*2, _FILE_AND_LINE_ );
|
|
RakAssert(temp);
|
|
char *priorHash = PQgetvalue(result, rowIndex, priorHashColumnIndex);
|
|
memcpy(temp, priorHash, HASH_LENGTH);
|
|
memcpy(temp+HASH_LENGTH, contentHash, HASH_LENGTH);
|
|
memcpy(temp+HASH_LENGTH*2, patch, patchLength);
|
|
|
|
// OK
|
|
addedOrModifiedFileHashes->AddFile(hardDriveFilename,hardDriveFilename, contentHash, HASH_LENGTH, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0),false,false);
|
|
patchedFiles->AddFile(hardDriveFilename,hardDriveFilename, temp, HASH_LENGTH*2+patchLength, patchLength, FileListNodeContext(PC_HASH_2_WITH_PATCH,fileId,patchAlgorithm,0), false, true );
|
|
|
|
// fileData = PQgetvalue(result, rowIndex, contentColumnIndex);
|
|
// updatedFiles->AddFile(hardDriveFilename,hardDriveFilename, fileData, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId),false,false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Deleted file
|
|
deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
}
|
|
|
|
PQclear(result);
|
|
return true;
|
|
}
|
|
bool AutopatcherPostgreRepository::UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb)
|
|
{
|
|
FileList filesOnHarddrive;
|
|
filesOnHarddrive.AddCallback(cb);
|
|
filesOnHarddrive.AddFilesFromDirectory(applicationDirectory,"", true, false, true, FileListNodeContext(0,0,0,0));
|
|
if (filesOnHarddrive.fileList.Size()==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Can't find files at %s in UpdateApplicationFiles\n",applicationDirectory);
|
|
return false;
|
|
}
|
|
|
|
int numRows;
|
|
PGresult *result;
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return false;
|
|
if (strlen(userName)>100)
|
|
return false;
|
|
|
|
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
|
sprintf_s(query, "SELECT applicationID FROM applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",applicationName);
|
|
return false;
|
|
}
|
|
char *res;
|
|
res = PQgetvalue(result,0,0);
|
|
int applicationID;
|
|
applicationID=atoi(res);
|
|
PQclear(result);
|
|
|
|
// If ExecuteBlockingCommand fails then it does a rollback
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand("BEGIN;", &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
|
|
sprintf_s(query, "UPDATE applications SET changeSetId = changeSetId + 1 where applicationID=%i; SELECT changeSetId FROM applications WHERE applicationID=%i;", applicationID, applicationID);
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, true)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: applicationID %i not found in UpdateApplicationFiles\n", applicationID);
|
|
Rollback();
|
|
return false;
|
|
}
|
|
res = PQgetvalue(result,0,0);
|
|
int changeSetId;
|
|
changeSetId=atoi(res);
|
|
PQclear(result);
|
|
|
|
// +1 was added in the update
|
|
changeSetId--;
|
|
|
|
// Gets all newest files
|
|
// TODO - This can be non-blocking
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) filename, contentHash, createFile FROM FileVersionHistory WHERE applicationID=%i ORDER BY filename, fileId DESC;", applicationID);
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
int filenameColumnIndex = PQfnumber(result, "filename");
|
|
int contentHashColumnIndex = PQfnumber(result, "contentHash");
|
|
int createFileColumnIndex = PQfnumber(result, "createFile");
|
|
|
|
//char *PQgetvalue(result,int row_number,int column_number);
|
|
//int PQgetlength(result, int row_number, int column_number);
|
|
|
|
unsigned fileListIndex;
|
|
int rowIndex;
|
|
SLNet::RakString hardDriveFilename;
|
|
char *hardDriveHash;
|
|
SLNet::RakString queryFilename;
|
|
char *createFileResult;
|
|
char *hash;
|
|
bool addFile;
|
|
FileList newFiles;
|
|
numRows = PQntuples(result);
|
|
|
|
// Loop through files om 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 (fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Hashing files %i/%i\n", fileListIndex+1, filesOnHarddrive.fileList.Size());
|
|
addFile=true;
|
|
hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
|
|
hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
queryFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
|
|
if (_stricmp(hardDriveFilename, queryFilename)==0)
|
|
{
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
hash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
if (createFileResult[0]==1 && memcmp(hash, 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==true)
|
|
{
|
|
newFiles.AddFile(hardDriveFilename,hardDriveFilename, filesOnHarddrive.fileList[fileListIndex].data, filesOnHarddrive.fileList[fileListIndex].dataLengthBytes, filesOnHarddrive.fileList[fileListIndex].fileLengthBytes, FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
}
|
|
|
|
// Go through query results that are marked as create
|
|
// If a file that is currently on the database is not on the harddrive, add it to the delete list
|
|
FileList deletedFiles;
|
|
bool fileOnHarddrive;
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
queryFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
if (createFileResult[0]!=1)
|
|
continue; // If already false don't mark false again.
|
|
|
|
fileOnHarddrive=false;
|
|
for (fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
|
{
|
|
hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename.C_String();
|
|
hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
|
|
|
if (_stricmp(hardDriveFilename, queryFilename)==0)
|
|
{
|
|
fileOnHarddrive=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fileOnHarddrive==false)
|
|
deletedFiles.AddFile(queryFilename,queryFilename,0,0,0,FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
|
|
// Query memory and files on harddrive no longer needed. Free this memory since generating all the patches is memory intensive.
|
|
PQclear(result);
|
|
filesOnHarddrive.Clear();
|
|
|
|
const char *outTemp[3];
|
|
int outLengths[3];
|
|
int formats[3];
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
formats[1]=PQEXECPARAM_FORMAT_BINARY; // Always happens to be binary
|
|
formats[2]=PQEXECPARAM_FORMAT_BINARY; // Always happens to be binary
|
|
|
|
// For each file in the delete list add a row indicating file deletion
|
|
for (fileListIndex=0; fileListIndex < deletedFiles.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Tagging deleted files %i/%i\n", fileListIndex+1, deletedFiles.fileList.Size());
|
|
|
|
// BUGGED
|
|
sprintf_s(query, "INSERT INTO FileVersionHistory(applicationID, filename, createFile, changeSetID, userName) VALUES (%i, $1::text,FALSE,%i,'%s');", applicationID, changeSetId, GetEscapedString(userName).C_String());
|
|
outTemp[0]=deletedFiles.fileList[fileListIndex].filename.C_String();
|
|
outLengths[0]=(int)deletedFiles.fileList[fileListIndex].filename.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
deletedFiles.Clear();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
PQclear(result);
|
|
}
|
|
|
|
// Clear the delete list as it is no longer needed.
|
|
deletedFiles.Clear();
|
|
|
|
int contentColumnIndex;
|
|
|
|
PGresult *uploadResult;
|
|
PGresult *fileRows;
|
|
char *content;
|
|
char *fileID;
|
|
int contentLength;
|
|
int fileIDLength;
|
|
int fileIDColumnIndex;
|
|
char *hardDriveData;
|
|
unsigned hardDriveDataLength;
|
|
char *patch;
|
|
unsigned patchLength;
|
|
|
|
// For each file in the create list
|
|
for (fileListIndex=0; fileListIndex < newFiles.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Adding file %i/%i\n", fileListIndex+1, newFiles.fileList.Size());
|
|
hardDriveFilename=newFiles.fileList[fileListIndex].filename;
|
|
// hardDriveData=newFiles.fileList[fileListIndex].data+HASH_LENGTH;
|
|
hardDriveHash=newFiles.fileList[fileListIndex].data;
|
|
hardDriveDataLength=newFiles.fileList[fileListIndex].fileLengthBytes;
|
|
|
|
char path[MAX_PATH];
|
|
strcpy_s(path, applicationDirectory);
|
|
strcat_s(path, "/");
|
|
strcat_s(path, newFiles.fileList[fileListIndex].fullPathToFile);
|
|
|
|
FILE *fp;
|
|
if (fopen_s(&fp, path, "rb") != 0)
|
|
{
|
|
newFiles.Clear();
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
hardDriveData =(char*) rakMalloc_Ex( hardDriveDataLength, _FILE_AND_LINE_ );
|
|
RakAssert(hardDriveData);
|
|
fread(hardDriveData,1,hardDriveDataLength,fp);
|
|
fclose(fp);
|
|
|
|
sprintf_s( query, "SELECT fileID from FileVersionHistory WHERE applicationID=%i AND filename=$1::text AND createFile=TRUE;", applicationID );
|
|
outTemp[0]=hardDriveFilename;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
//sqlCommandMutex.Lock();
|
|
fileRows = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_TEXT);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(fileRows, true)==false)
|
|
{
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
newFiles.Clear();
|
|
PQclear(fileRows);
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
|
|
fileIDColumnIndex = PQfnumber( fileRows, "fileID" );
|
|
numRows = PQntuples(fileRows);
|
|
outTemp[0]=hardDriveFilename;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
|
|
// Create new patches for every create version
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
fileIDLength=PQgetlength(fileRows, rowIndex, fileIDColumnIndex);
|
|
fileID=PQgetvalue(fileRows, rowIndex, fileIDColumnIndex);
|
|
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
outTemp[0]=fileID;
|
|
outLengths[0]=fileIDLength;
|
|
|
|
// The last query handled all the relevant comparisons
|
|
sprintf_s(query, "SELECT content from FileVersionHistory WHERE fileID=$1::int;" );
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
int numContent = PQntuples(result);
|
|
if( numContent > 1 || numContent == 0 )
|
|
{
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
formats[0] = PQEXECPARAM_FORMAT_TEXT;
|
|
|
|
contentColumnIndex = PQfnumber(result, "content");
|
|
contentLength=PQgetlength(result, 0, contentColumnIndex);
|
|
content=PQgetvalue(result, 0, contentColumnIndex);
|
|
|
|
|
|
if (CreatePatch(content, contentLength, hardDriveData, hardDriveDataLength, &patch, &patchLength)==false)
|
|
{
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
|
|
strcpy_s(lastError,"CreatePatch failed.\n");
|
|
Rollback();
|
|
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
return false;
|
|
}
|
|
|
|
// outTemp[0]=fileID;
|
|
// outLengths[0]=fileIDLength;
|
|
// outTemp[1]=patch;
|
|
// outLengths[1]=patchLength;
|
|
|
|
outTemp[0]=patch;
|
|
outLengths[0]=patchLength;
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
|
|
//sqlCommandMutex.Lock();
|
|
char buff[256];
|
|
sprintf_s(buff, "UPDATE FileVersionHistory SET patch=$1::bytea, patchAlgorithm=0 where fileID=%s;", fileID);
|
|
uploadResult = PQexecParams(pgConn, buff, 1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
// Done with this patch data
|
|
delete [] patch;
|
|
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(uploadResult, true)==false)
|
|
{
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
PQclear(result);
|
|
PQclear(uploadResult);
|
|
}
|
|
PQclear(fileRows);
|
|
|
|
// Add totally new files
|
|
sprintf_s(query, "INSERT INTO FileVersionHistory (applicationID, filename, fileLength, content, contentHash, createFile, changeSetID, userName) "
|
|
"VALUES ("
|
|
"%i,"
|
|
"$1::text,"
|
|
"%i,"
|
|
"$2::bytea,"
|
|
"$3::bytea,"
|
|
"TRUE,"
|
|
"%i,"
|
|
"'%s'"
|
|
");", applicationID, hardDriveDataLength, changeSetId, GetEscapedString(userName).C_String());
|
|
|
|
outTemp[0]=hardDriveFilename;
|
|
outTemp[1]=hardDriveData;
|
|
outTemp[2]=hardDriveHash;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
outLengths[1]=hardDriveDataLength;
|
|
outLengths[2]=HASH_LENGTH;
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
RakAssert(formats[1]==PQEXECPARAM_FORMAT_BINARY);
|
|
RakAssert(formats[2]==PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
// Upload the new file
|
|
//sqlCommandMutex.Lock();
|
|
uploadResult = PQexecParams(pgConn, query,3,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
rakFree_Ex(hardDriveData, _FILE_AND_LINE_);
|
|
|
|
//sqlCommandMutex.Unlock();
|
|
if( !uploadResult )
|
|
{
|
|
// Libpq had a problem inserting the file to the table. Most likely due to it running out of
|
|
// memory or a buffer being too small.
|
|
RakAssert( 0 );
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
if (IsResultSuccessful(uploadResult, true)==false)
|
|
{
|
|
newFiles.Clear();
|
|
PQclear(uploadResult);
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
// Clear the upload result
|
|
PQclear(uploadResult);
|
|
}
|
|
|
|
printf("DB COMMIT\n");
|
|
|
|
// If ExecuteBlockingCommand fails then it does a rollback
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand("COMMIT;", &result, true)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
printf("COMMIT Failed!\n");
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
|
|
printf("COMMIT Success\n");
|
|
|
|
return true;
|
|
}
|
|
bool AutopatcherPostgreRepository2::UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb)
|
|
{
|
|
FileList filesOnHarddrive;
|
|
filesOnHarddrive.AddCallback(cb);
|
|
filesOnHarddrive.AddFilesFromDirectory(applicationDirectory,"", true, false, true, FileListNodeContext(0,0,0,0));
|
|
if (filesOnHarddrive.fileList.Size()==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: Can't find files at %s in UpdateApplicationFiles\n",applicationDirectory);
|
|
return false;
|
|
}
|
|
|
|
int numRows;
|
|
PGresult *result;
|
|
char query[512];
|
|
if (strlen(applicationName)>100)
|
|
return false;
|
|
if (strlen(userName)>100)
|
|
return false;
|
|
|
|
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
|
|
sprintf_s(query, "SELECT applicationID FROM applications WHERE applicationName='%s';", escapedApplicationName.C_String());
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",applicationName);
|
|
return false;
|
|
}
|
|
char *res;
|
|
res = PQgetvalue(result,0,0);
|
|
int applicationID;
|
|
applicationID=atoi(res);
|
|
PQclear(result);
|
|
|
|
// If ExecuteBlockingCommand fails then it does a rollback
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand("BEGIN;", &result, false)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
|
|
sprintf_s(query, "UPDATE applications SET changeSetId = changeSetId + 1 where applicationID=%i; SELECT changeSetId FROM applications WHERE applicationID=%i;", applicationID, applicationID);
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand(query, &result, true)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
numRows = PQntuples(result);
|
|
if (numRows==0)
|
|
{
|
|
sprintf_s(lastError,"ERROR: applicationID %i not found in UpdateApplicationFiles\n", applicationID);
|
|
Rollback();
|
|
return false;
|
|
}
|
|
res = PQgetvalue(result,0,0);
|
|
int changeSetId;
|
|
changeSetId=atoi(res);
|
|
PQclear(result);
|
|
|
|
// +1 was added in the update
|
|
changeSetId--;
|
|
|
|
// Gets all newest files
|
|
// TODO - This can be non-blocking
|
|
sprintf_s(query, "SELECT DISTINCT ON (filename) filename, contentHash, createFile FROM FileVersionHistory WHERE applicationID=%i ORDER BY filename, fileId DESC;", applicationID);
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
int filenameColumnIndex = PQfnumber(result, "filename");
|
|
int contentHashColumnIndex = PQfnumber(result, "contentHash");
|
|
int createFileColumnIndex = PQfnumber(result, "createFile");
|
|
|
|
//char *PQgetvalue(result,int row_number,int column_number);
|
|
//int PQgetlength(result, int row_number, int column_number);
|
|
|
|
unsigned fileListIndex;
|
|
int rowIndex;
|
|
SLNet::RakString hardDriveFilename;
|
|
char *hardDriveHash;
|
|
SLNet::RakString queryFilename;
|
|
char *createFileResult;
|
|
char *hash;
|
|
bool addFile;
|
|
FileList newFiles;
|
|
numRows = PQntuples(result);
|
|
|
|
// Loop through files om 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 (fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Hashing files %i/%i\n", fileListIndex+1, filesOnHarddrive.fileList.Size());
|
|
addFile=true;
|
|
hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
|
|
hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
queryFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
|
|
if (_stricmp(hardDriveFilename, queryFilename)==0)
|
|
{
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
hash = PQgetvalue(result, rowIndex, contentHashColumnIndex);
|
|
if (createFileResult[0]==1 && memcmp(hash, 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==true)
|
|
{
|
|
newFiles.AddFile(hardDriveFilename,hardDriveFilename, filesOnHarddrive.fileList[fileListIndex].data, filesOnHarddrive.fileList[fileListIndex].dataLengthBytes, filesOnHarddrive.fileList[fileListIndex].fileLengthBytes, FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
}
|
|
|
|
// Go through query results that are marked as create
|
|
// If a file that is currently on the database is not on the harddrive, add it to the delete list
|
|
FileList deletedFiles;
|
|
bool fileOnHarddrive;
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
queryFilename = PQgetvalue(result, rowIndex, filenameColumnIndex);
|
|
createFileResult = PQgetvalue(result, rowIndex, createFileColumnIndex);
|
|
if (createFileResult[0]!=1)
|
|
continue; // If already false don't mark false again.
|
|
|
|
fileOnHarddrive=false;
|
|
for (fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
|
|
{
|
|
hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename.C_String();
|
|
hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
|
|
|
|
if (_stricmp(hardDriveFilename, queryFilename)==0)
|
|
{
|
|
fileOnHarddrive=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fileOnHarddrive==false)
|
|
deletedFiles.AddFile(queryFilename,queryFilename,0,0,0,FileListNodeContext(0,0,0,0), false);
|
|
}
|
|
|
|
// Query memory and files on harddrive no longer needed. Free this memory since generating all the patches is memory intensive.
|
|
PQclear(result);
|
|
filesOnHarddrive.Clear();
|
|
|
|
const char *outTemp[3];
|
|
int outLengths[3];
|
|
int formats[3];
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
formats[1]=PQEXECPARAM_FORMAT_BINARY; // Always happens to be binary
|
|
formats[2]=PQEXECPARAM_FORMAT_BINARY; // Always happens to be binary
|
|
|
|
// For each file in the delete list add a row indicating file deletion
|
|
for (fileListIndex=0; fileListIndex < deletedFiles.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Tagging deleted files %i/%i\n", fileListIndex+1, deletedFiles.fileList.Size());
|
|
|
|
// BUGGED
|
|
sprintf_s(query, "INSERT INTO FileVersionHistory(applicationID, filename, createFile, changeSetID, userName) VALUES (%i, $1::text,FALSE,%i,'%s');", applicationID, changeSetId, GetEscapedString(userName).C_String());
|
|
outTemp[0]=deletedFiles.fileList[fileListIndex].filename.C_String();
|
|
outLengths[0]=(int)deletedFiles.fileList[fileListIndex].filename.GetLength();
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
deletedFiles.Clear();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
PQclear(result);
|
|
}
|
|
|
|
// Clear the delete list as it is no longer needed.
|
|
deletedFiles.Clear();
|
|
|
|
int contentColumnIndex;
|
|
|
|
PGresult *uploadResult;
|
|
PGresult *fileRows;
|
|
char *fileID;
|
|
//int contentLength;
|
|
int fileIDLength;
|
|
int fileIDColumnIndex;
|
|
// char *newContent;
|
|
unsigned hardDriveDataLength;
|
|
char *patch;
|
|
unsigned patchLength;
|
|
|
|
// For each file in the create list
|
|
for (fileListIndex=0; fileListIndex < newFiles.fileList.Size(); fileListIndex++)
|
|
{
|
|
if (fileListIndex%10==0)
|
|
printf("Adding file %i/%i\n", fileListIndex+1, newFiles.fileList.Size());
|
|
hardDriveFilename=newFiles.fileList[fileListIndex].filename;
|
|
// hardDriveData=newFiles.fileList[fileListIndex].data+HASH_LENGTH;
|
|
hardDriveHash=newFiles.fileList[fileListIndex].data;
|
|
hardDriveDataLength=newFiles.fileList[fileListIndex].fileLengthBytes;
|
|
|
|
char pathToNewContent[MAX_PATH];
|
|
strcpy_s(pathToNewContent, applicationDirectory);
|
|
strcat_s(pathToNewContent, "/");
|
|
strcat_s(pathToNewContent, newFiles.fileList[fileListIndex].fullPathToFile);
|
|
|
|
|
|
sprintf_s( query, "SELECT fileID from FileVersionHistory WHERE applicationID=%i AND filename=$1::text AND createFile=TRUE;", applicationID );
|
|
outTemp[0]=hardDriveFilename;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
//sqlCommandMutex.Lock();
|
|
fileRows = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_TEXT);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(fileRows, true)==false)
|
|
{
|
|
newFiles.Clear();
|
|
PQclear(fileRows);
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
|
|
fileIDColumnIndex = PQfnumber( fileRows, "fileID" );
|
|
numRows = PQntuples(fileRows);
|
|
outTemp[0]=hardDriveFilename;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
|
|
/*
|
|
FILE *fp;
|
|
if (fopen_s(&fp, pathToNewContent, "rb") != 0)
|
|
{
|
|
newFiles.Clear();
|
|
PQclear(fileRows);
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
newContent =(char*) rakMalloc_Ex( hardDriveDataLength, _FILE_AND_LINE_ );
|
|
fread(newContent,1,hardDriveDataLength,fp);
|
|
fclose(fp);
|
|
*/
|
|
|
|
// Create new patches for every create version
|
|
for (rowIndex=0; rowIndex < numRows; rowIndex++ )
|
|
{
|
|
fileIDLength=PQgetlength(fileRows, rowIndex, fileIDColumnIndex);
|
|
fileID=PQgetvalue(fileRows, rowIndex, fileIDColumnIndex);
|
|
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
outTemp[0]=fileID;
|
|
outLengths[0]=fileIDLength;
|
|
|
|
// The last query handled all the relevant comparisons
|
|
sprintf_s(query, "SELECT pathToContent from FileVersionHistory WHERE fileID=$1::int;" );
|
|
//sqlCommandMutex.Lock();
|
|
result = PQexecParams(pgConn, query,1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
// rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
int numContent = PQntuples(result);
|
|
if( numContent > 1 || numContent == 0 )
|
|
{
|
|
// rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
RakAssert(0);
|
|
return false;
|
|
}
|
|
formats[0] = PQEXECPARAM_FORMAT_TEXT;
|
|
|
|
contentColumnIndex = PQfnumber(result, "pathToContent");
|
|
|
|
// contentLength=PQgetlength(result, 0, contentColumnIndex);
|
|
char *pathToOldContent;
|
|
pathToOldContent=PQgetvalue(result, 0, contentColumnIndex);
|
|
|
|
if (strcmp(pathToNewContent, pathToOldContent)==0)
|
|
{
|
|
// rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
|
|
sprintf_s(lastError,"New file version cannot have the same path as the old file version. Path=%s.\n", pathToNewContent);
|
|
Rollback();
|
|
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
return false;
|
|
}
|
|
|
|
printf("%i/%i.%i/%i DIFF from %s to %s ...", fileListIndex+1, newFiles.fileList.Size(), rowIndex+1, numRows, pathToOldContent, pathToNewContent);
|
|
int patchAlgorithm;
|
|
int makePatchResult=MakePatch(pathToOldContent, pathToNewContent, &patch, &patchLength, &patchAlgorithm);
|
|
if (makePatchResult < 0 || makePatchResult == 2)
|
|
{
|
|
strcpy_s(lastError,"MakePatch failed.\n");
|
|
Rollback();
|
|
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
return false;
|
|
}
|
|
if (makePatchResult ==1)
|
|
{
|
|
PQclear(result);
|
|
strcpy_s(lastError,"MakePatch skipped - No old version on disk.\n");
|
|
}
|
|
else
|
|
{
|
|
|
|
/*
|
|
if (CreatePatch(oldContent, contentLength, newContent, hardDriveDataLength, &patch, &patchLength)==false)
|
|
{
|
|
rakFree_Ex(oldContent, _FILE_AND_LINE_);
|
|
rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
|
|
strcpy_s(lastError,"CreatePatch failed.\n");
|
|
Rollback();
|
|
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
PQclear(fileRows);
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
// rakFree_Ex(oldContent, _FILE_AND_LINE_);
|
|
// oldContent=0;
|
|
|
|
// outTemp[0]=fileID;
|
|
// outLengths[0]=fileIDLength;
|
|
// outTemp[1]=patch;
|
|
// outLengths[1]=patchLength;
|
|
|
|
outTemp[0]=patch;
|
|
outLengths[0]=patchLength;
|
|
|
|
//sqlCommandMutex.Lock();
|
|
formats[0]=PQEXECPARAM_FORMAT_BINARY;
|
|
char buff[256];
|
|
sprintf_s(buff, "UPDATE FileVersionHistory SET patch=$1::bytea, patchAlgorithm=%i where fileID=%s;", patchAlgorithm, fileID);
|
|
uploadResult = PQexecParams(pgConn, buff, 1,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
// Done with this patch data
|
|
delete [] patch;
|
|
|
|
//sqlCommandMutex.Unlock();
|
|
if (IsResultSuccessful(uploadResult, true)==false)
|
|
{
|
|
// rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
Rollback();
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
PQclear(result);
|
|
PQclear(uploadResult);
|
|
|
|
printf(" Done.\n");
|
|
}
|
|
|
|
}
|
|
PQclear(fileRows);
|
|
|
|
// rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
// newContent=0;
|
|
|
|
// Add totally new files
|
|
sprintf_s(query, "INSERT INTO FileVersionHistory (applicationID, filename, fileLength, pathToContent, contentHash, createFile, changeSetID, userName) "
|
|
"VALUES ("
|
|
"%i,"
|
|
"$1::text,"
|
|
"%i,"
|
|
"$2::text,"
|
|
"$3::bytea,"
|
|
"TRUE,"
|
|
"%i,"
|
|
"'%s'"
|
|
");", applicationID, hardDriveDataLength, changeSetId, GetEscapedString(userName).C_String());
|
|
|
|
outTemp[0]=hardDriveFilename;
|
|
outTemp[1]=pathToNewContent;
|
|
outTemp[2]=hardDriveHash;
|
|
outLengths[0]=(int)strlen(hardDriveFilename);
|
|
outLengths[1]=(int)strlen(pathToNewContent);
|
|
outLengths[2]=HASH_LENGTH;
|
|
formats[0]=PQEXECPARAM_FORMAT_TEXT;
|
|
formats[1]=PQEXECPARAM_FORMAT_TEXT;
|
|
RakAssert(formats[2]==PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
// Upload the new file
|
|
//sqlCommandMutex.Lock();
|
|
uploadResult = PQexecParams(pgConn, query,3,0,outTemp,outLengths,formats,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
//sqlCommandMutex.Unlock();
|
|
if( !uploadResult )
|
|
{
|
|
// Libpq had a problem inserting the file to the table. Most likely due to it running out of
|
|
// memory or a buffer being too small.
|
|
RakAssert( 0 );
|
|
newFiles.Clear();
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
if (IsResultSuccessful(uploadResult, true)==false)
|
|
{
|
|
newFiles.Clear();
|
|
PQclear(uploadResult);
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
|
|
// Clear the upload result
|
|
PQclear(uploadResult);
|
|
}
|
|
|
|
printf("DB COMMIT\n");
|
|
|
|
// If ExecuteBlockingCommand fails then it does a rollback
|
|
//sqlCommandMutex.Lock();
|
|
if (ExecuteBlockingCommand("COMMIT;", &result, true)==false)
|
|
{
|
|
//sqlCommandMutex.Unlock();
|
|
printf("COMMIT Failed!\n");
|
|
PQclear(result);
|
|
return false;
|
|
}
|
|
//sqlCommandMutex.Unlock();
|
|
PQclear(result);
|
|
|
|
printf("COMMIT Success\n");
|
|
|
|
return true;
|
|
}
|
|
int AutopatcherPostgreRepository2::MakePatch(const char *oldFile, const char *newFile, char **patch, unsigned int *patchLength, int *patchAlgorithm)
|
|
{
|
|
*patchAlgorithm=0;
|
|
|
|
FILE *fpOld;
|
|
if (fopen_s(&fpOld, oldFile, "rb") != 0)
|
|
return 1;
|
|
fseek(fpOld, 0, SEEK_END);
|
|
int contentLengthOld = ftell(fpOld);
|
|
FILE *fpNew;
|
|
if (fopen_s(&fpNew, newFile, "rb") != 0)
|
|
{
|
|
fclose(fpOld);
|
|
return 2;
|
|
}
|
|
fseek(fpNew, 0, SEEK_END);
|
|
int contentLengthNew = ftell(fpNew);
|
|
|
|
bool b = MakePatchBSDiff(fpOld, contentLengthOld, fpNew, contentLengthNew, patch, patchLength);
|
|
fclose(fpOld);
|
|
fclose(fpNew);
|
|
if (b==false)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
bool AutopatcherPostgreRepository2::MakePatchBSDiff(FILE *fpOld, int contentLengthOld, FILE *fpNew, int contentLengthNew, char **patch, unsigned int *patchLength)
|
|
{
|
|
char *newContent, *oldContent;
|
|
|
|
oldContent = (char*) rakMalloc_Ex(contentLengthOld, _FILE_AND_LINE_);
|
|
RakAssert(oldContent);
|
|
fseek(fpOld, 0, SEEK_SET);
|
|
fread(oldContent, contentLengthOld, 1, fpOld);
|
|
|
|
newContent = (char*) rakMalloc_Ex(contentLengthNew, _FILE_AND_LINE_);
|
|
RakAssert(newContent);
|
|
fseek(fpNew, 0, SEEK_SET);
|
|
fread(newContent, contentLengthNew, 1, fpNew);
|
|
|
|
bool b = CreatePatch(oldContent, contentLengthOld, newContent, contentLengthNew, patch, patchLength);
|
|
|
|
if (b==false)
|
|
{
|
|
printf(
|
|
"AutopatcherPostgreRepository2::MakePatchBSDiff failed.\n"
|
|
"contentLengthOld=%i\n"
|
|
"contentLengthNew=%i\n",
|
|
contentLengthOld, contentLengthNew
|
|
);
|
|
}
|
|
|
|
rakFree_Ex(oldContent, _FILE_AND_LINE_);
|
|
rakFree_Ex(newContent, _FILE_AND_LINE_);
|
|
return b;
|
|
}
|
|
const char *AutopatcherPostgreRepository::GetLastError(void) const
|
|
{
|
|
return PostgreSQLInterface::GetLastError();
|
|
}
|
|
unsigned int AutopatcherPostgreRepository::GetPatchPart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
|
|
{
|
|
// unused parameters
|
|
(void)filename;
|
|
|
|
PGresult *result;
|
|
char query[512];
|
|
char *patch, *contentHash;
|
|
int patchLength;
|
|
|
|
bool firstBlock=startReadBytes==0;
|
|
if (firstBlock)
|
|
sprintf_s(query, "SELECT substring(patch from %i for %i) FROM FileVersionHistory WHERE fileId=%i;", startReadBytes+1,numBytesToRead-HASH_LENGTH,context.flnc_extraData3);
|
|
else
|
|
sprintf_s(query, "SELECT substring(patch from %i for %i) FROM FileVersionHistory WHERE fileId=%i;", startReadBytes+1-HASH_LENGTH,numBytesToRead,context.flnc_extraData3);
|
|
|
|
|
|
// 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
|
|
filePartConnectionMutex.Lock();
|
|
if (filePartConnection==0)
|
|
filePartConnection=PQconnectdb(_conninfo);
|
|
|
|
result = PQexecParams(filePartConnection, query,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
if (IsResultSuccessful(result, true)==false)
|
|
{
|
|
PQclear(result);
|
|
// #med - review/change return value
|
|
return static_cast<unsigned int>(-1);
|
|
}
|
|
|
|
int patchColumnIndex = PQfnumber(result, "substring");
|
|
|
|
patch = PQgetvalue(result, 0, patchColumnIndex);
|
|
patchLength = PQgetlength(result, 0, patchColumnIndex);
|
|
|
|
if (firstBlock)
|
|
{
|
|
PGresult *result2;
|
|
char query2[512];
|
|
sprintf_s(query2, "SELECT contentHash FROM FileVersionHistory WHERE fileId=%i;", context.flnc_extraData1);
|
|
|
|
result2 = PQexecParams(filePartConnection, query2,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
int contentHashColumnIndex = PQfnumber(result2, "contentHash");
|
|
contentHash = PQgetvalue(result2, 0, contentHashColumnIndex);
|
|
|
|
memcpy(preallocatedDestination, contentHash, HASH_LENGTH);
|
|
memcpy((char*) preallocatedDestination+HASH_LENGTH, patch, patchLength);
|
|
PQclear(result);
|
|
PQclear(result2);
|
|
filePartConnectionMutex.Unlock();
|
|
return patchLength+HASH_LENGTH;
|
|
}
|
|
else
|
|
{
|
|
memcpy(preallocatedDestination,patch,patchLength);
|
|
PQclear(result);
|
|
filePartConnectionMutex.Unlock();
|
|
return patchLength;
|
|
}
|
|
}
|
|
unsigned int AutopatcherPostgreRepository::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
|
|
{
|
|
if (context.op==PC_HASH_1_WITH_PATCH)
|
|
{
|
|
return GetPatchPart(filename, startReadBytes, numBytesToRead, preallocatedDestination, context);
|
|
}
|
|
else
|
|
{
|
|
PGresult *result;
|
|
char query[512];
|
|
char *content;
|
|
int contentLength;
|
|
|
|
// Seems that substring is 1 based for its index, so add 1 to startReadBytes
|
|
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
|
|
filePartConnectionMutex.Lock();
|
|
if (filePartConnection==0)
|
|
filePartConnection=PQconnectdb(_conninfo);
|
|
|
|
result = PQexecParams(filePartConnection, query,0,0,0,0,0,PQEXECPARAM_FORMAT_BINARY);
|
|
|
|
content = PQgetvalue(result, 0, 0);
|
|
contentLength=PQgetlength(result, 0, 0);
|
|
memcpy(preallocatedDestination,content,contentLength);
|
|
PQclear(result);
|
|
|
|
filePartConnectionMutex.Unlock();
|
|
|
|
|
|
return contentLength;
|
|
|
|
}
|
|
}
|
|
unsigned int AutopatcherPostgreRepository2::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
|
|
{
|
|
if (context.op==PC_HASH_1_WITH_PATCH)
|
|
{
|
|
return GetPatchPart(filename, startReadBytes, numBytesToRead, preallocatedDestination, context);
|
|
}
|
|
else
|
|
{
|
|
PGresult *result;
|
|
char query[512];
|
|
unsigned int bytesRead;
|
|
|
|
RakAssert(context.op==PC_WRITE_FILE)
|
|
|
|
sprintf_s(query, "SELECT pathToContent FROM FileVersionHistory WHERE fileId=%i;", context.flnc_extraData1);
|
|
ExecuteBlockingCommand(query, &result, true);
|
|
char *path;
|
|
path = PQgetvalue(result,0,0);
|
|
|
|
FILE *fp;
|
|
fopen_s(&fp, path, "rb");
|
|
fseek(fp, 0, SEEK_END);
|
|
unsigned int fileLength = ftell(fp);
|
|
fseek(fp, startReadBytes, SEEK_SET);
|
|
if (startReadBytes+numBytesToRead>fileLength)
|
|
bytesRead=fileLength-startReadBytes;
|
|
else
|
|
bytesRead=numBytesToRead;
|
|
|
|
bytesRead=(unsigned int) fread(preallocatedDestination,1,bytesRead,fp);
|
|
fclose(fp);
|
|
PQclear(result);
|
|
|
|
|
|
return bytesRead;
|
|
}
|
|
}
|
|
const int AutopatcherPostgreRepository::GetIncrementalReadChunkSize(void) const
|
|
{
|
|
return 262144*4*16;
|
|
}
|