885 lines
31 KiB
C++
885 lines
31 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-2020, 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 The server plugin for the autopatcher. Must be running for the client to get patches.
|
|
|
|
|
|
#include "AutopatcherServer.h"
|
|
#include "slikenet/DirectoryDeltaTransfer.h"
|
|
#include "slikenet/FileList.h"
|
|
#include "slikenet/StringCompressor.h"
|
|
#include "slikenet/peerinterface.h"
|
|
#include "slikenet/FileListTransfer.h"
|
|
#include "slikenet/FileListTransferCBInterface.h"
|
|
#include "slikenet/BitStream.h"
|
|
#include "slikenet/MessageIdentifiers.h"
|
|
#include "slikenet/AutopatcherRepositoryInterface.h"
|
|
#include "slikenet/assert.h"
|
|
#include "slikenet/AutopatcherPatchContext.h"
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
using namespace SLNet;
|
|
|
|
const static unsigned HASH_LENGTH=4;
|
|
|
|
void AutopatcherServerLoadNotifier_Printf::OnQueueUpdate(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::RequestType requestType, AutopatcherServerLoadNotifier::QueueOperation queueOperation, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
|
|
{
|
|
char *operationString;
|
|
char *requestTypeString;
|
|
char systemAddressString[32];
|
|
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
|
|
if (requestType==ASUMC_GET_CHANGELIST)
|
|
requestTypeString="GetChangelist";
|
|
else
|
|
requestTypeString="GetPatch";
|
|
if (queueOperation==QO_WAS_ADDED)
|
|
operationString="added";
|
|
else if (queueOperation==QO_POPPED_ONTO_TO_PROCESSING_THREAD)
|
|
operationString="processing";
|
|
else // otherwise queueOperation == QO_WAS_ABORTED
|
|
operationString="aborted";
|
|
|
|
printf("%s %s %s. %i queued. %i working.\n", systemAddressString, requestTypeString, operationString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
|
|
}
|
|
void AutopatcherServerLoadNotifier_Printf::OnGetChangelistCompleted(
|
|
SystemAddress remoteSystem,
|
|
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
|
|
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
|
|
{
|
|
char systemAddressString[32];
|
|
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
|
|
|
|
char *changelistString;
|
|
if (getChangelistResult==GCR_DELETE_FILES)
|
|
changelistString="Delete files";
|
|
else if (getChangelistResult==GCR_ADD_FILES)
|
|
changelistString="Add files";
|
|
else if (getChangelistResult==GCR_ADD_AND_DELETE_FILES)
|
|
changelistString="Add and delete files";
|
|
else if (getChangelistResult==GCR_NOTHING_TO_DO)
|
|
changelistString="No files in changelist";
|
|
else // otherwise getChangelistResult == GCR_REPOSITORY_ERROR
|
|
changelistString="Repository error";
|
|
|
|
printf("%s GetChangelist complete. %s. %i queued. %i working.\n", systemAddressString, changelistString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
|
|
}
|
|
void AutopatcherServerLoadNotifier_Printf::OnGetPatchCompleted(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::PatchResult patchResult, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
|
|
{
|
|
char systemAddressString[32];
|
|
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
|
|
|
|
char *patchResultString;
|
|
if (patchResult==PR_NO_FILES_NEEDED_PATCHING)
|
|
patchResultString="No files needed patching";
|
|
else if (patchResult==PR_REPOSITORY_ERROR)
|
|
patchResultString="Repository error";
|
|
else if (patchResult==PR_DISALLOWED_DOWNLOADING_ORIGINAL_FILES)
|
|
patchResultString="Disallowed downloading original files";
|
|
else if (patchResult==PR_PATCHES_WERE_SENT)
|
|
patchResultString="Files pushed for patching";
|
|
else if (patchResult==PR_ABORTED_FROM_INPUT_THREAD)
|
|
patchResultString="Aborted from input thread";
|
|
else // otherwise patchResult == PR_ABORTED_FROM_DOWNLOAD_THREAD
|
|
patchResultString="Aborted from download thread";
|
|
|
|
printf("%s GetPatch complete. %s. %i queued. %i working.\n", systemAddressString, patchResultString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
|
|
}
|
|
|
|
AutopatcherServer::AutopatcherServer()
|
|
{
|
|
fileListTransfer=0;
|
|
priority=HIGH_PRIORITY;
|
|
orderingChannel=0;
|
|
// repository=0;
|
|
maxConcurrentUsers=0;
|
|
loadNotifier=0;
|
|
cache_minTime=0;
|
|
cache_maxTime=0;
|
|
cacheLoaded=false;
|
|
allowDownloadOfOriginalUnmodifiedFiles=true;
|
|
}
|
|
AutopatcherServer::~AutopatcherServer()
|
|
{
|
|
Clear();
|
|
}
|
|
void AutopatcherServer::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel)
|
|
{
|
|
priority=_priority;
|
|
orderingChannel=_orderingChannel;
|
|
}
|
|
void AutopatcherServer::SetFileListTransferPlugin(FileListTransfer *flt)
|
|
{
|
|
if (fileListTransfer)
|
|
fileListTransfer->RemoveCallback(this);
|
|
fileListTransfer=flt;
|
|
if (fileListTransfer)
|
|
fileListTransfer->AddCallback(this);
|
|
}
|
|
void AutopatcherServer::StartThreads(int numThreads, int numSQLConnections, AutopatcherRepositoryInterface **sqlConnectionPtrArray)
|
|
{
|
|
RakAssert(numSQLConnections >= numThreads);
|
|
|
|
connectionPoolMutex.Lock();
|
|
for (int i=0; i < numSQLConnections; i++)
|
|
{
|
|
// Test the pointers passed, in case the user incorrectly casted an array of a different type
|
|
sqlConnectionPtrArray[i]->GetLastError();
|
|
connectionPool.Push(sqlConnectionPtrArray[i],_FILE_AND_LINE_);
|
|
}
|
|
connectionPoolMutex.Unlock();
|
|
threadPool.SetThreadDataInterface(this,0);
|
|
threadPool.StartThreads(numThreads, 0);
|
|
}
|
|
void AutopatcherServer::CacheMostRecentPatch(const char *applicationName)
|
|
{
|
|
if (connectionPool.Size()>0)
|
|
{
|
|
if (applicationName)
|
|
cache_appName=applicationName;
|
|
else
|
|
cache_appName.Clear();
|
|
cache_patchedFiles.Clear();
|
|
cache_addedFiles.Clear();
|
|
cache_deletedFiles.Clear();
|
|
cache_addedOrModifiedFileHashes.Clear();
|
|
cache_minTime=0;
|
|
cache_maxTime=0;
|
|
|
|
cacheLoaded = connectionPool[0]->GetMostRecentChangelistWithPatches(cache_appName, &cache_patchedFiles, &cache_addedFiles, &cache_addedOrModifiedFileHashes, &cache_deletedFiles, &cache_minTime, &cache_maxTime);
|
|
if (cacheLoaded==false)
|
|
{
|
|
printf("Warning: Cache not loaded. This is OK if no patch was ever saved.\n");
|
|
}
|
|
}
|
|
}
|
|
void AutopatcherServer::OnAttach(void)
|
|
{
|
|
}
|
|
void AutopatcherServer::OnDetach(void)
|
|
{
|
|
Clear();
|
|
}
|
|
void AutopatcherServer::Update(void)
|
|
{
|
|
while (PatchingUserLimitReached()==false && userRequestWaitingQueue.Size()>0)
|
|
{
|
|
Packet *packet = PopOffWaitingQueue();
|
|
switch (packet->data[0])
|
|
{
|
|
case ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE:
|
|
OnGetChangelistSinceDateInt(packet);
|
|
break;
|
|
// Client sends ID_AUTOPATCHER_GET_PATCH with files that they have different or missing
|
|
case ID_AUTOPATCHER_GET_PATCH:
|
|
OnGetPatchInt(packet);
|
|
break;
|
|
}
|
|
DeallocPacketUnified(packet);
|
|
}
|
|
}
|
|
PluginReceiveResult AutopatcherServer::OnReceive(Packet *packet)
|
|
{
|
|
switch (packet->data[0])
|
|
{
|
|
case ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE:
|
|
return OnGetChangelistSinceDate(packet);
|
|
case ID_AUTOPATCHER_GET_PATCH:
|
|
return OnGetPatch(packet);
|
|
}
|
|
|
|
return RR_CONTINUE_PROCESSING;
|
|
}
|
|
void AutopatcherServer::OnShutdown(void)
|
|
{
|
|
Clear();
|
|
}
|
|
void AutopatcherServer::Clear(void)
|
|
{
|
|
// Clear the waiting input and output from the thread pool.
|
|
unsigned i;
|
|
threadPool.StopThreads();
|
|
for (i=0; i < threadPool.InputSize(); i++)
|
|
{
|
|
if (DecrementPatchingUserCount(threadPool.GetInputAtIndex(i).systemAddress))
|
|
CallPatchCompleteCallback(threadPool.GetInputAtIndex(i).systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_INPUT_THREAD);
|
|
SLNet::OP_DELETE(threadPool.GetInputAtIndex(i).clientList, _FILE_AND_LINE_);
|
|
}
|
|
threadPool.ClearInput();
|
|
for (i=0; i < threadPool.OutputSize(); i++)
|
|
{
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->patchList, _FILE_AND_LINE_);
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->deletedFiles, _FILE_AND_LINE_);
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->addedOrModifiedFilesWithHashData, _FILE_AND_LINE_);
|
|
}
|
|
threadPool.ClearOutput();
|
|
|
|
while (userRequestWaitingQueue.Size())
|
|
DeallocPacketUnified(AbortOffWaitingQueue());
|
|
|
|
patchingUsers.Clear(true, _FILE_AND_LINE_);
|
|
}
|
|
void AutopatcherServer::OnStartup(RakPeerInterface *peer)
|
|
{
|
|
// unused parameters
|
|
(void)peer;
|
|
}
|
|
void AutopatcherServer::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
|
|
{
|
|
// unused parameters
|
|
(void)rakNetGUID;
|
|
(void)lostConnectionReason;
|
|
|
|
RemoveFromThreadPool(systemAddress);
|
|
|
|
unsigned int i=0;
|
|
patchingUsersMutex.Lock();
|
|
while (i < patchingUsers.Size())
|
|
{
|
|
if (patchingUsers[i]==systemAddress)
|
|
patchingUsers.RemoveAtIndexFast(i);
|
|
else
|
|
i++;
|
|
}
|
|
patchingUsersMutex.Unlock();
|
|
|
|
i=0;
|
|
while (i < userRequestWaitingQueue.Size())
|
|
{
|
|
if (userRequestWaitingQueue[i]->systemAddress==systemAddress)
|
|
userRequestWaitingQueue.RemoveAtIndex(i);
|
|
else
|
|
i++;
|
|
}
|
|
}
|
|
void AutopatcherServer::RemoveFromThreadPool(SystemAddress systemAddress)
|
|
{
|
|
unsigned int i = 0;
|
|
threadPool.LockInput();
|
|
while (i < threadPool.InputSize())
|
|
{
|
|
if (threadPool.GetInputAtIndex(i).systemAddress==systemAddress)
|
|
{
|
|
if (DecrementPatchingUserCount(systemAddress))
|
|
CallPatchCompleteCallback(threadPool.GetInputAtIndex(i).systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_INPUT_THREAD);
|
|
SLNet::OP_DELETE(threadPool.GetInputAtIndex(i).clientList, _FILE_AND_LINE_);
|
|
threadPool.RemoveInputAtIndex(i);
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
threadPool.UnlockInput();
|
|
|
|
i=0;
|
|
threadPool.LockOutput();
|
|
while (i < threadPool.OutputSize())
|
|
{
|
|
if (threadPool.GetOutputAtIndex(i)->systemAddress==systemAddress)
|
|
{
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->patchList, _FILE_AND_LINE_);
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->deletedFiles, _FILE_AND_LINE_);
|
|
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->addedOrModifiedFilesWithHashData, _FILE_AND_LINE_);
|
|
threadPool.RemoveOutputAtIndex(i);
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
threadPool.UnlockOutput();
|
|
}
|
|
namespace SLNet
|
|
{
|
|
AutopatcherServer::ResultTypeAndBitstream* GetChangelistSinceDateCB(AutopatcherServer::ThreadData threadData, bool *returnOutput, void* perThreadData)
|
|
{
|
|
AutopatcherRepositoryInterface *repository = (AutopatcherRepositoryInterface*)perThreadData;
|
|
|
|
FileList addedOrModifiedFilesWithHashData, deletedFiles;
|
|
AutopatcherServer *server = threadData.server;
|
|
|
|
//AutopatcherServer::ResultTypeAndBitstream *rtab = SLNet::OP_NEW<AutopatcherServer::ResultTypeAndBitstream>( _FILE_AND_LINE_ );
|
|
AutopatcherServer::ResultTypeAndBitstream rtab;
|
|
rtab.systemAddress=threadData.systemAddress;
|
|
// rtab.deletedFiles=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
|
|
// rtab.addedFiles=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
|
|
rtab.deletedFiles=&deletedFiles;
|
|
rtab.addedOrModifiedFilesWithHashData=&addedOrModifiedFilesWithHashData;
|
|
|
|
// Query the database for a changelist since this date
|
|
RakAssert(server);
|
|
//if (server->repository->GetChangelistSinceDate(threadData.applicationName.C_String(), rtab.addedFiles, rtab.deletedFiles, threadData.lastUpdateDate.C_String(), currentDate))
|
|
if (repository->GetChangelistSinceDate(threadData.applicationName.C_String(), rtab.addedOrModifiedFilesWithHashData, rtab.deletedFiles, threadData.lastUpdateDate))
|
|
{
|
|
rtab.resultCode=1;
|
|
}
|
|
else
|
|
{
|
|
rtab.resultCode=0;
|
|
}
|
|
|
|
rtab.operation=AutopatcherServer::ResultTypeAndBitstream::GET_CHANGELIST_SINCE_DATE;
|
|
rtab.currentDate=(double) time(nullptr);
|
|
// *returnOutput=true;
|
|
// return rtab;
|
|
|
|
if (rtab.resultCode==1)
|
|
{
|
|
if (rtab.deletedFiles->fileList.Size())
|
|
{
|
|
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_DELETION_LIST);
|
|
rtab.deletedFiles->Serialize(&rtab.bitStream1);
|
|
}
|
|
|
|
if (rtab.addedOrModifiedFilesWithHashData->fileList.Size())
|
|
{
|
|
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_CREATION_LIST);
|
|
rtab.addedOrModifiedFilesWithHashData->Serialize(&rtab.bitStream2);
|
|
rtab.bitStream2.Write(rtab.currentDate);
|
|
rtab.bitStream2.WriteCasted<double>(0);
|
|
|
|
rtab.addedOrModifiedFilesWithHashData->Clear();
|
|
}
|
|
else
|
|
{
|
|
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
|
|
rtab.bitStream2.Write(rtab.currentDate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR);
|
|
StringCompressor::Instance()->EncodeString(repository->GetLastError(), 256, &rtab.bitStream2);
|
|
}
|
|
// SLNet::OP_DELETE(rtab.deletedFiles, _FILE_AND_LINE_);
|
|
// SLNet::OP_DELETE(rtab.addedFiles, _FILE_AND_LINE_);
|
|
|
|
*returnOutput=false;
|
|
|
|
if (server->DecrementPatchingUserCount(rtab.systemAddress))
|
|
{
|
|
if (rtab.bitStream1.GetNumberOfBitsUsed()>0)
|
|
server->SendUnified(&(rtab.bitStream1), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
|
|
if (rtab.bitStream2.GetNumberOfBitsUsed()>0)
|
|
server->SendUnified(&(rtab.bitStream2), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
|
|
|
|
if (server->loadNotifier)
|
|
{
|
|
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
|
|
autopatcherState.requestsQueued=server->userRequestWaitingQueue.Size();
|
|
autopatcherState.requestsWorking=server->patchingUsers.Size();
|
|
|
|
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult;
|
|
if (rtab.resultCode!=1)
|
|
getChangelistResult=AutopatcherServerLoadNotifier::GCR_REPOSITORY_ERROR;
|
|
else if (rtab.deletedFiles->fileList.Size()==0 && rtab.addedOrModifiedFilesWithHashData->fileList.Size()==0)
|
|
getChangelistResult=AutopatcherServerLoadNotifier::GCR_NOTHING_TO_DO;
|
|
else if (rtab.deletedFiles->fileList.Size()==0)
|
|
getChangelistResult=AutopatcherServerLoadNotifier::GCR_ADD_FILES;
|
|
else if (rtab.addedOrModifiedFilesWithHashData->fileList.Size()==0)
|
|
getChangelistResult=AutopatcherServerLoadNotifier::GCR_DELETE_FILES;
|
|
else
|
|
getChangelistResult=AutopatcherServerLoadNotifier::GCR_ADD_AND_DELETE_FILES;
|
|
|
|
server->loadNotifier->OnGetChangelistCompleted(rtab.systemAddress, getChangelistResult, &autopatcherState);
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
PluginReceiveResult AutopatcherServer::OnGetChangelistSinceDate(Packet *packet)
|
|
{
|
|
SLNet::BitStream inBitStream(packet->data, packet->length, false);
|
|
ThreadData threadData;
|
|
threadData.clientList=0;
|
|
inBitStream.IgnoreBits(8);
|
|
inBitStream.ReadCompressed(threadData.applicationName);
|
|
inBitStream.Read(threadData.lastUpdateDate);
|
|
|
|
if (cacheLoaded && threadData.lastUpdateDate!=0 && threadData.applicationName==cache_appName)
|
|
{
|
|
SLNet::BitStream bitStream1;
|
|
SLNet::BitStream bitStream2;
|
|
double currentDate=(double) time(nullptr);
|
|
if (cache_maxTime!=0 && threadData.lastUpdateDate>cache_maxTime)
|
|
{
|
|
bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
|
|
bitStream2.Write(currentDate);
|
|
SendUnified(&bitStream2, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
|
|
// Check in-memory cache, use if possible rather than accessing database
|
|
if (cache_minTime!=0 && threadData.lastUpdateDate>cache_minTime)
|
|
{
|
|
if (cache_deletedFiles.fileList.Size())
|
|
{
|
|
bitStream1.Write((unsigned char) ID_AUTOPATCHER_DELETION_LIST);
|
|
cache_deletedFiles.Serialize(&bitStream1);
|
|
SendUnified(&bitStream1, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
|
|
}
|
|
if (cache_addedOrModifiedFileHashes.fileList.Size())
|
|
{
|
|
bitStream2.Write((unsigned char) ID_AUTOPATCHER_CREATION_LIST);
|
|
cache_addedOrModifiedFileHashes.Serialize(&bitStream2);
|
|
bitStream2.Write(currentDate);
|
|
bitStream2.Write(threadData.lastUpdateDate);
|
|
}
|
|
else
|
|
{
|
|
bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
|
|
bitStream2.Write(currentDate);
|
|
}
|
|
SendUnified(&bitStream2, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
|
|
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
}
|
|
|
|
if (PatchingUserLimitReached())
|
|
{
|
|
AddToWaitingQueue(packet);
|
|
return RR_STOP_PROCESSING;
|
|
}
|
|
|
|
OnGetChangelistSinceDateInt(packet);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
void AutopatcherServer::OnGetChangelistSinceDateInt(Packet *packet)
|
|
{
|
|
SLNet::BitStream inBitStream(packet->data, packet->length, false);
|
|
ThreadData threadData;
|
|
threadData.clientList=0;
|
|
inBitStream.IgnoreBits(8);
|
|
inBitStream.ReadCompressed(threadData.applicationName);
|
|
inBitStream.Read(threadData.lastUpdateDate);
|
|
|
|
if (IncrementPatchingUserCount(packet->systemAddress))
|
|
{
|
|
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_POPPED_ONTO_TO_PROCESSING_THREAD);
|
|
|
|
threadData.server=this;
|
|
threadData.systemAddress=packet->systemAddress;
|
|
threadPool.AddInput(GetChangelistSinceDateCB, threadData);
|
|
}
|
|
}
|
|
namespace SLNet {
|
|
AutopatcherServer::ResultTypeAndBitstream* GetPatchCB(AutopatcherServer::ThreadData threadData, bool *returnOutput, void* perThreadData)
|
|
{
|
|
AutopatcherServer *server = threadData.server;
|
|
AutopatcherRepositoryInterface *repository = (AutopatcherRepositoryInterface*)perThreadData;
|
|
|
|
// AutopatcherServer::ResultTypeAndBitstream *rtab = SLNet::OP_NEW<AutopatcherServer::ResultTypeAndBitstream>( _FILE_AND_LINE_ );
|
|
AutopatcherServer::ResultTypeAndBitstream rtab;
|
|
rtab.systemAddress=threadData.systemAddress;
|
|
FileList fileList;
|
|
// rtab.patchList=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
|
|
rtab.patchList=&fileList;
|
|
RakAssert(server);
|
|
// RakAssert(server->repository);
|
|
// if (server->repository->GetPatches(threadData.applicationName.C_String(), threadData.clientList, rtab.patchList, currentDate))
|
|
rtab.resultCode = repository->GetPatches(threadData.applicationName.C_String(), threadData.clientList, server->allowDownloadOfOriginalUnmodifiedFiles, rtab.patchList);
|
|
rtab.operation=AutopatcherServer::ResultTypeAndBitstream::GET_PATCH;
|
|
rtab.setId=threadData.setId;
|
|
rtab.currentDate=(double) time(nullptr);
|
|
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
|
|
if (rtab.resultCode==1)
|
|
{
|
|
if (rtab.patchList->fileList.Size())
|
|
{
|
|
//server->fileListTransfer->Send(rtab.patchList, 0, rtab.systemAddress, rtab.setId, server->priority, server->orderingChannel, false, server->repository);
|
|
server->fileListTransfer->Send(rtab.patchList, 0, rtab.systemAddress, rtab.setId, server->priority, server->orderingChannel, repository, repository->GetIncrementalReadChunkSize());
|
|
}
|
|
else
|
|
{
|
|
// No files needed to send
|
|
if (server->DecrementPatchingUserCount(rtab.systemAddress))
|
|
server->CallPatchCompleteCallback(rtab.systemAddress, AutopatcherServerLoadNotifier::PR_NO_FILES_NEEDED_PATCHING);
|
|
}
|
|
|
|
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_FINISHED_INTERNAL);
|
|
rtab.bitStream1.Write(rtab.currentDate);
|
|
}
|
|
else
|
|
{
|
|
AutopatcherServerLoadNotifier::PatchResult pr;
|
|
if (rtab.resultCode==0)
|
|
{
|
|
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR);
|
|
StringCompressor::Instance()->EncodeString(repository->GetLastError(), 256, &rtab.bitStream1);
|
|
pr = AutopatcherServerLoadNotifier::PR_REPOSITORY_ERROR;
|
|
}
|
|
else
|
|
{
|
|
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_CANNOT_DOWNLOAD_ORIGINAL_UNMODIFIED_FILES);
|
|
pr = AutopatcherServerLoadNotifier::PR_DISALLOWED_DOWNLOADING_ORIGINAL_FILES;
|
|
}
|
|
|
|
if (server->DecrementPatchingUserCount(rtab.systemAddress))
|
|
{
|
|
server->CallPatchCompleteCallback(rtab.systemAddress, pr);
|
|
}
|
|
else
|
|
{
|
|
*returnOutput=false;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
*returnOutput=false;
|
|
|
|
if (rtab.bitStream1.GetNumberOfBitsUsed()>0)
|
|
server->SendUnified(&(rtab.bitStream1), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
|
|
if (rtab.bitStream2.GetNumberOfBitsUsed()>0)
|
|
server->SendUnified(&(rtab.bitStream2), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
|
|
|
|
// 12/1/2010 This doesn't scale well. Changing to allocating a connection object per request
|
|
/*
|
|
// Wait for repository to finish
|
|
// This is so that the same sql connection is not used between two different plugins, which causes thrashing and bad performance
|
|
// Plus if fileListTransfer uses multiple threads, this will keep this thread and the fileListTransfer thread from using the same connection at the same time
|
|
// PostgreSQL possibly MySQL are not threadsafe for multiple threads on the same connection
|
|
int pendingFiles = server->fileListTransfer->GetPendingFilesToAddress(rtab.systemAddress);
|
|
while (pendingFiles>0)
|
|
{
|
|
RakSleep(pendingFiles*10);
|
|
pendingFiles = server->fileListTransfer->GetPendingFilesToAddress(rtab.systemAddress);
|
|
}
|
|
*/
|
|
|
|
// *returnOutput=true;
|
|
// return rtab;
|
|
return 0;
|
|
}
|
|
}
|
|
PluginReceiveResult AutopatcherServer::OnGetPatch(Packet *packet)
|
|
{
|
|
SLNet::BitStream inBitStream(packet->data, packet->length, false);
|
|
|
|
ThreadData threadData;
|
|
inBitStream.IgnoreBits(8);
|
|
inBitStream.Read(threadData.setId);
|
|
double lastUpdateDate;
|
|
inBitStream.Read(lastUpdateDate);
|
|
inBitStream.ReadCompressed(threadData.applicationName);
|
|
threadData.clientList=0;
|
|
|
|
// Check in-memory cache, use if possible rather than accessing database
|
|
if (threadData.applicationName==cache_appName && lastUpdateDate!=0 && cacheLoaded && cache_minTime!=0 && lastUpdateDate>cache_minTime)
|
|
{
|
|
threadData.systemAddress=packet->systemAddress;
|
|
threadData.server=this;
|
|
threadData.clientList= SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
|
|
|
|
if (threadData.clientList->Deserialize(&inBitStream)==false)
|
|
{
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
if (threadData.clientList->fileList.Size()==0)
|
|
{
|
|
RakAssert(0);
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
|
|
char *userHash;
|
|
SLNet::RakString userFilename;
|
|
FileList patchList;
|
|
bool cacheUpdateFailed=false;
|
|
|
|
unsigned int i,j;
|
|
// FileList is the list of all files missing or changed as determined by the client
|
|
for (i=0; i < threadData.clientList->fileList.Size(); i++)
|
|
{
|
|
userHash=threadData.clientList->fileList[i].data;
|
|
userFilename=threadData.clientList->fileList[i].filename;
|
|
|
|
if (userHash)
|
|
{
|
|
// If the user has a hash, check for this file in cache_patchedFiles. If not found, or hash is wrong, use DB
|
|
if (threadData.clientList->fileList[i].dataLengthBytes!=HASH_LENGTH)
|
|
{
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
|
|
for (j=0; j < cache_patchedFiles.fileList.Size(); j++)
|
|
{
|
|
if (userFilename == cache_patchedFiles.fileList[j].filename)
|
|
{
|
|
if (memcmp(cache_patchedFiles.fileList[j].data, userHash, HASH_LENGTH)==0)
|
|
{
|
|
// Send patch
|
|
RakAssert(cache_patchedFiles.fileList[j].context.op==PC_HASH_2_WITH_PATCH);
|
|
patchList.AddFile(userFilename,userFilename, 0, cache_patchedFiles.fileList[j].dataLengthBytes, cache_patchedFiles.fileList[j].fileLengthBytes, cache_patchedFiles.fileList[j].context, true, false);
|
|
}
|
|
else
|
|
{
|
|
// Bad hash
|
|
cacheUpdateFailed=true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j==cache_patchedFiles.fileList.Size())
|
|
{
|
|
// Didn't find the patch even though the client has an older version of the file
|
|
cacheUpdateFailed=true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the user does not have a hash, check for this file in cache_addedFiles. If not found, use DB
|
|
for (j=0; j < cache_addedFiles.fileList.Size(); j++)
|
|
{
|
|
if (userFilename == cache_addedFiles.fileList[j].filename)
|
|
{
|
|
// Send added file
|
|
patchList.AddFile(userFilename,userFilename, 0, cache_addedFiles.fileList[j].dataLengthBytes, cache_addedFiles.fileList[j].fileLengthBytes, cache_addedFiles.fileList[j].context, true, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j==cache_addedFiles.fileList.Size())
|
|
{
|
|
// Didn't find the file in the cache even though the client asked for it
|
|
cacheUpdateFailed=true;
|
|
}
|
|
}
|
|
|
|
if (cacheUpdateFailed==true)
|
|
{
|
|
// Failure to find file in cache
|
|
// Will fall to use database
|
|
patchList.Clear();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (patchList.fileList.Size()>0)
|
|
{
|
|
if (IncrementPatchingUserCount(packet->systemAddress))
|
|
{
|
|
fileListTransfer->Send(&patchList, 0, packet->systemAddress, threadData.setId, priority, orderingChannel, this, 262144*4*4);
|
|
SLNet::BitStream bitStream1;
|
|
bitStream1.Write((unsigned char) ID_AUTOPATCHER_FINISHED_INTERNAL);
|
|
double t =(double) time(nullptr);
|
|
bitStream1.Write(t);
|
|
SendUnified(&bitStream1, priority, RELIABLE_ORDERED, orderingChannel, packet->systemAddress, false);
|
|
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
|
|
if (PatchingUserLimitReached())
|
|
{
|
|
AddToWaitingQueue(packet);
|
|
return RR_STOP_PROCESSING;
|
|
}
|
|
|
|
OnGetPatchInt(packet);
|
|
return RR_STOP_PROCESSING_AND_DEALLOCATE;
|
|
}
|
|
void AutopatcherServer::OnGetPatchInt(Packet *packet)
|
|
{
|
|
SLNet::BitStream inBitStream(packet->data, packet->length, false);
|
|
|
|
ThreadData threadData;
|
|
inBitStream.IgnoreBits(8);
|
|
inBitStream.Read(threadData.setId);
|
|
double lastUpdateDate;
|
|
inBitStream.Read(lastUpdateDate);
|
|
inBitStream.ReadCompressed(threadData.applicationName);
|
|
threadData.systemAddress=packet->systemAddress;
|
|
threadData.server=this;
|
|
threadData.clientList= SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
|
|
|
|
if (threadData.clientList->Deserialize(&inBitStream)==false)
|
|
{
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return;
|
|
}
|
|
if (threadData.clientList->fileList.Size()==0)
|
|
{
|
|
RakAssert(0);
|
|
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
|
|
return;
|
|
}
|
|
|
|
if (IncrementPatchingUserCount(packet->systemAddress))
|
|
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_POPPED_ONTO_TO_PROCESSING_THREAD);
|
|
|
|
threadPool.AddInput(GetPatchCB, threadData);
|
|
}
|
|
void* AutopatcherServer::PerThreadFactory(void *context)
|
|
{
|
|
(void)context;
|
|
|
|
AutopatcherRepositoryInterface* p;
|
|
connectionPoolMutex.Lock();
|
|
p=connectionPool.Pop();
|
|
connectionPoolMutex.Unlock();
|
|
return p;
|
|
}
|
|
void AutopatcherServer::PerThreadDestructor(void* factoryResult, void *context)
|
|
{
|
|
(void)context;
|
|
(void)factoryResult;
|
|
}
|
|
void AutopatcherServer::OnFilePushesComplete( SystemAddress systemAddress, unsigned short setID )
|
|
{
|
|
// unused parameters
|
|
(void)setID;
|
|
|
|
if (DecrementPatchingUserCount(systemAddress))
|
|
CallPatchCompleteCallback(systemAddress, AutopatcherServerLoadNotifier::PR_PATCHES_WERE_SENT);
|
|
}
|
|
void AutopatcherServer::OnSendAborted( SystemAddress systemAddress )
|
|
{
|
|
if (DecrementPatchingUserCount(systemAddress))
|
|
CallPatchCompleteCallback(systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_DOWNLOAD_THREAD);
|
|
}
|
|
bool AutopatcherServer::IncrementPatchingUserCount(SystemAddress sa)
|
|
{
|
|
// A system address may exist more than once in patchingUsers
|
|
patchingUsersMutex.Lock();
|
|
patchingUsers.Insert(sa, _FILE_AND_LINE_);
|
|
patchingUsersMutex.Unlock();
|
|
return true;
|
|
}
|
|
bool AutopatcherServer::DecrementPatchingUserCount(SystemAddress sa)
|
|
{
|
|
unsigned int i;
|
|
patchingUsersMutex.Lock();
|
|
for (i=0; i < patchingUsers.Size(); i++)
|
|
{
|
|
if (patchingUsers[i]==sa)
|
|
{
|
|
patchingUsers.RemoveAtIndexFast(i);
|
|
patchingUsersMutex.Unlock();
|
|
return true;
|
|
}
|
|
}
|
|
patchingUsersMutex.Unlock();
|
|
return false;
|
|
}
|
|
bool AutopatcherServer::PatchingUserLimitReached(void) const
|
|
{
|
|
if (maxConcurrentUsers==0)
|
|
return false;
|
|
|
|
return patchingUsers.Size()>=maxConcurrentUsers;
|
|
}
|
|
void AutopatcherServer::SetMaxConurrentUsers(unsigned int _maxConcurrentUsers)
|
|
{
|
|
maxConcurrentUsers=_maxConcurrentUsers;
|
|
}
|
|
unsigned int AutopatcherServer::GetMaxConurrentUsers(void) const
|
|
{
|
|
return maxConcurrentUsers;
|
|
}
|
|
void AutopatcherServer::CallPacketCallback(Packet *packet, AutopatcherServerLoadNotifier::QueueOperation queueOperation)
|
|
{
|
|
if (loadNotifier)
|
|
{
|
|
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
|
|
autopatcherState.requestsQueued=userRequestWaitingQueue.Size();
|
|
autopatcherState.requestsWorking=patchingUsers.Size();
|
|
|
|
AutopatcherServerLoadNotifier::RequestType requestType;
|
|
if (packet->data[0]==ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE)
|
|
requestType=AutopatcherServerLoadNotifier::ASUMC_GET_CHANGELIST;
|
|
else
|
|
requestType=AutopatcherServerLoadNotifier::ASUMC_GET_PATCH;
|
|
|
|
loadNotifier->OnQueueUpdate(packet->systemAddress, requestType, queueOperation, &autopatcherState);
|
|
}
|
|
}
|
|
void AutopatcherServer::CallPatchCompleteCallback(const SystemAddress &systemAddress, AutopatcherServerLoadNotifier::PatchResult patchResult)
|
|
{
|
|
if (loadNotifier)
|
|
{
|
|
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
|
|
autopatcherState.requestsQueued=userRequestWaitingQueue.Size();
|
|
autopatcherState.requestsWorking=patchingUsers.Size();
|
|
|
|
loadNotifier->OnGetPatchCompleted(systemAddress, patchResult, &autopatcherState);
|
|
}
|
|
}
|
|
void AutopatcherServer::AddToWaitingQueue(Packet *packet)
|
|
{
|
|
userRequestWaitingQueue.Push(packet, _FILE_AND_LINE_);
|
|
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_WAS_ADDED);
|
|
}
|
|
Packet *AutopatcherServer::AbortOffWaitingQueue(void)
|
|
{
|
|
Packet *packet = userRequestWaitingQueue.Pop();
|
|
CallPacketCallback(packet,AutopatcherServerLoadNotifier::QO_WAS_ABORTED);
|
|
return packet;
|
|
}
|
|
Packet *AutopatcherServer::PopOffWaitingQueue(void)
|
|
{
|
|
return userRequestWaitingQueue.Pop();;
|
|
}
|
|
void AutopatcherServer::SetLoadManagementCallback(AutopatcherServerLoadNotifier *asumc)
|
|
{
|
|
loadNotifier=asumc;
|
|
}
|
|
void AutopatcherServer::SetAllowDownloadOfOriginalUnmodifiedFiles(bool allow)
|
|
{
|
|
allowDownloadOfOriginalUnmodifiedFiles = allow;
|
|
}
|
|
unsigned int AutopatcherServer::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
|
|
{
|
|
// unused parameters
|
|
(void)filename;
|
|
|
|
/*
|
|
int offset;
|
|
if (context.op==PC_HASH_1_WITH_PATCH)
|
|
offset=HASH_LENGTH;
|
|
else if (context.op==PC_HASH_2_WITH_PATCH)
|
|
offset=HASH_LENGTH*2;
|
|
else
|
|
offset=0;
|
|
|
|
int bytesToRead;
|
|
if (startReadBytes + numBytesToRead > context.dataLength-offset)
|
|
bytesToRead=(context.dataLength-offset)-startReadBytes;
|
|
else
|
|
bytesToRead=numBytesToRead;
|
|
|
|
memcpy(preallocatedDestination, ((char*)context.dataPtr)+offset, bytesToRead);
|
|
*/
|
|
|
|
int bytesToRead;
|
|
if (startReadBytes + numBytesToRead > context.dataLength)
|
|
bytesToRead=(context.dataLength)-startReadBytes;
|
|
else
|
|
bytesToRead=numBytesToRead;
|
|
|
|
memcpy(preallocatedDestination, context.dataPtr, bytesToRead);
|
|
return bytesToRead;
|
|
}
|