// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//-----------------------------------------------------------------------------
// includes
//-----------------------------------------------------------------------------
// nel
#include "nel/misc/debug.h"
#include "nel/misc/singleton.h"
#include "nel/misc/variable.h"
// game share
#include "game_share/utils.h"
// local
#include "file_manager.h"
#include "file_repository.h"
#include "patchman_constants.h"
//-------------------------------------------------------------------------------------------------
// namespaces
//-------------------------------------------------------------------------------------------------
using namespace std;
using namespace NLMISC;
using namespace NLNET;
using namespace PATCHMAN;
//-------------------------------------------------------------------------------------------------
// namespace PATCHMAN
//-------------------------------------------------------------------------------------------------
namespace PATCHMAN
{
//-----------------------------------------------------------------------------
// methods CFileRepository
//-----------------------------------------------------------------------------
// std::string CFileRepository::buildModuleManifest() const
// {
// // make sure we've been initialised
//// nlassert(_Parent!=NULL);
//
// return "isFileRepository";
// }
CFileRepository::CFileRepository()
{
_FileRequestCount= 0;
_FileInfoCount= 0;
_MaxHistorySize= 10;
_FileRequestHistorySize= 0;
_FileInfoHistorySize= 0;
}
void CFileRepository::init(NLNET::IModule* parent,const NLMISC::CSString& rootDirectory)
{
CFileRepositorySkel::init(parent);
_Interceptor.init(this, parent);
_Parent= parent;
_Directory= CFileManager::getInstance().getRepositoryDirectory(rootDirectory);
_AdministeredModuleWrapper.init(dynamic_cast(parent));
}
void CFileRepository::onModuleUp(IModuleProxy *module)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
if (NLMISC::CSString(module->getModuleManifest()).contains(ManifestEntryIsFileReceiver))
{
CFileReceiverProxy rr(module);
rr.setupSubscriptions(_Parent);
_AdministeredModuleWrapper.registerProgress("Receiver up: "+module->getModuleName());
}
}
void CFileRepository::onModuleDown(IModuleProxy *module)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// if the module has subscribed to listen to stuff then end its subscriptions
onFileRepositoryModuleDown(module);
}
void CFileRepository::onFileRepositoryModuleDown(IModuleProxy *module)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// if the module has subscribed to listen to stuff then end its subscriptions
unsubscribeAll(module);
}
void CFileRepository::onModuleUpdate()
{
H_AUTO(CFileRepository_onModuleUpdate);
// make sure we've been initialised
nlassert(_Parent!=NULL);
// update subscriptions
_broadcastFileInfoChanges(_FileInfoChanges);
// clear out the changes container (now that it's been treated)
if (!_FileInfoChanges.empty())
{
_AdministeredModuleWrapper.registerProgress(NLMISC::toString("updated %d files",_FileInfoChanges.size()));
_FileInfoChanges.clear();
}
}
std::string CFileRepository::buildModuleManifest() const
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
return ManifestEntryIsFileRepository;
}
void CFileRepository::rescanFull()
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// delegate to the _Directory object
_Directory->rescanFull(this);
}
void CFileRepository::rescanPartial()
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// delegate to the _Directory object
_Directory->rescanPartial(this);
}
void CFileRepository::updateFile(const NLMISC::CSString& fileName)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// delegate to the _Directory object
_Directory->updateFile(fileName,SFileInfo::RECALCULATE_IF_CHANGED,this);
}
void CFileRepository::getFileInfo(const NLMISC::CSString& fileSpec,TFileInfoVector& result,const NLNET::IModuleProxy *sender) const
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// delegate to the _Directory object
_Directory->getFileInfo(fileSpec,result,const_cast(this),sender);
}
TRepositoryDirectoryPtr CFileRepository::getRepositoryDirectory()
{
return _Directory;
}
void CFileRepository::getFile(const NLMISC::CSString& fileName,NLMISC::CSString& resultData,const NLNET::IModuleProxy* sender) const
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// clear out the result container before we begin...
resultData.clear();
// delegate to the _Directory object
_Directory->getFile(fileName,resultData,const_cast(this),sender);
}
void CFileRepository::dump(NLMISC::CLog& log)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
log.displayNL("-----------------------------------");
log.displayNL("Recent Info requests (%u in all)",_FileInfoCount);
log.displayNL("-----------------------------------");
for (THistory::iterator it=_FileInfoHistory.begin(); it!=_FileInfoHistory.end();++it)
{
log.displayNL(" '%s'",it->c_str());
}
log.displayNL("-----------------------------------");
log.displayNL("Recent Download requests (%u in all)",_FileRequestCount);
log.displayNL("-----------------------------------");
for (THistory::iterator it=_FileRequestHistory.begin(); it!=_FileRequestHistory.end();++it)
{
log.displayNL(" '%s'",it->c_str());
}
log.displayNL("-----------------------------------");
log.displayNL("Active Subscriptions");
log.displayNL("-----------------------------------");
for (TSubscribers::iterator it=_Subscribers.begin(); it!=_Subscribers.end();++it)
{
log.displayNL(" '%s'",it->first.c_str());
}
}
void CFileRepository::setMaxHistorySize(uint32 maxHistorySize)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
_MaxHistorySize= maxHistorySize;
// if the new limit is bigger than the request history size then prune down the history list
while (_FileRequestHistorySize > _MaxHistorySize)
{
_FileRequestHistory.pop_back();
--_FileRequestHistorySize;
}
// if the new limit is bigger than the info history size then prune down the history list
while (_FileInfoHistorySize > _MaxHistorySize)
{
_FileInfoHistory.pop_front();
--_FileInfoHistorySize;
}
}
uint32 CFileRepository::getMaxHistorySize() const
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
return _MaxHistorySize;
}
void CFileRepository::_broadcastFileInfoChanges(const TFileInfoVector& fileInfoChanges)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// update subscriptions
for (TSubscribers::iterator sit=_Subscribers.begin(); sit!=_Subscribers.end();++sit)
{
// setup a filespec object for this subscriber
CFileSpec fileSpec(sit->first.splitFrom('@').strip());
// build a vector of files that matches the subscription's request
TFileInfoVector infoVector;
// run through the changes that we've accumulated since the last update...
for (TFileInfoVector::const_iterator cit=fileInfoChanges.begin(); cit!=fileInfoChanges.end();++cit)
{
// if this change is for a file that matches the subscriber's filespec then addit to our result vector
if (fileSpec.matches(cit->FileName) && cbValidateFileInfoRequest(sit->second,cit->FileName))
{
infoVector.push_back(*cit);
}
}
// if we found changes that interest this subscriber then dispatch them
if (!infoVector.empty())
{
CFileReceiverProxy client(sit->second);
client.cbFileInfo(_Parent,infoVector);
}
}
}
void CFileRepository::requestFileInfo(NLNET::IModuleProxy *sender,const NLMISC::CSString& fileSpec)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// setup an object to receive the file info and fill it in
TFileInfoVector result;
getFileInfo(fileSpec,result,sender);
// get hold of a proxy for the module who sent the request and return the file info to them
CFileReceiverProxy fr(sender);
fr.cbFileInfo(_Parent,result);
// update our stats
++_FileInfoCount;
// add our new history record
_FileInfoHistory.push_front((sender==NULL?" ":"<"+sender->getModuleName()+"> ")+fileSpec);
++_FileInfoHistorySize;
// if we're grown too big then prune the oldest entry
if (_FileInfoHistorySize>_MaxHistorySize)
{
_FileInfoHistory.pop_back();
--_FileInfoHistorySize;
}
// deal with progress / state info
_AdministeredModuleWrapper.registerProgress("File info request: "+sender->getModuleName()+" "+fileSpec);
_AdministeredModuleWrapper.setStateVariable("InfoReq",NLMISC::toString(_FileInfoCount));
}
void CFileRepository::requestFileData(NLNET::IModuleProxy *sender, const NLMISC::CSString &fileName, uint32 startOffset, uint32 numBytes)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// load the file chunk that's being requested
CSString result;
bool ok;
// allow the overloadable validation callback a chance to prohibit read
ok= cbValidateDownloadRequest(sender,fileName);
// load the file (if validation was favorable)
CSString fullFileName= _Directory->getRootDirectory()+fileName;
ok&= CFileManager::getInstance().load(fullFileName,startOffset,numBytes,result);
// get hold of a proxy for the module who sent the request and return the file info to them
CFileReceiverProxy rr(sender);
if (ok && !result.empty())
{
rr.cbFileData(_Parent,fileName,startOffset,NLNET::TBinBuffer((const uint8 *)&result[0],result.size()));
}
else
{
rr.cbFileDataFailure(_Parent,fileName);
}
// update our stats
++_FileRequestCount;
// add our new history record
_FileRequestHistory.push_front((sender==NULL?" ":"<"+sender->getModuleName()+"> ")+fileName);
++_FileRequestHistorySize;
// if we're grown too big then prune the oldest entry
if (_FileRequestHistorySize>_MaxHistorySize)
{
_FileRequestHistory.pop_back();
--_FileRequestHistorySize;
}
// deal with progress / state info
uint32 fileLen=CFileManager::getInstance().getFileSize(fullFileName);
_AdministeredModuleWrapper.registerProgress("File data: "+sender->getModuleName()+" "+fileName+NLMISC::toString("(%d..%d/%d)",startOffset,startOffset+numBytes,fileLen));
_AdministeredModuleWrapper.setStateVariable("DataReq",NLMISC::toString(_FileRequestCount));
// set state variables to reflect 'end of file reached'
if (fileLengetModuleName()+" for: '"+fileName+"'");
_AdministeredModuleWrapper.registerProgress("Failed file: "+fileName);
}
else if (fileLen==startOffset+numBytes)
{
_AdministeredModuleWrapper.registerProgress("Finished file: "+fileName);
}
}
void CFileRepository::subscribe(NLNET::IModuleProxy *sender, const NLMISC::CSString &fileSpec)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// build the string that we'll use to represent this subscription
NLMISC::CSString subscriptionString= sender->getModuleName()+" @ "+fileSpec;
// make sure the entry doesn't exist in our subscribers set
DROP_IF(_Subscribers.find(subscriptionString)!=_Subscribers.end(),"Ignoring dumplicate request for the same subscription: '"+subscriptionString+'\'',return);
// display a fancy message
_AdministeredModuleWrapper.registerProgress("Subscribe '"+subscriptionString+"'");
// get hold of the info on the requested files as it stands right now
TFileInfoVector fileInfoVector;
getFileInfo(fileSpec,fileInfoVector,sender);
// dispatch the info to the sender
CFileReceiverProxy client(sender);
client.cbFileInfo(_Parent,fileInfoVector);
// add this entry to our subscriptions set
_Subscribers[subscriptionString]= sender;
}
void CFileRepository::unsubscribe(NLNET::IModuleProxy *sender, const NLMISC::CSString &fileSpec)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// build the string that we'll use to represent this subscription
NLMISC::CSString subscriptionString= sender->getModuleName()+" @ "+fileSpec;
// display a fancy message
_AdministeredModuleWrapper.registerProgress("Unsubscribe '"+subscriptionString+"'");
// make sure that this subscription really exists
DROP_IF(_Subscribers.find(subscriptionString)==_Subscribers.end(),"Ignoring unsubscribe for unknown subscription: '"+subscriptionString+'\'',return);
// remove the subscription
_Subscribers.erase(subscriptionString);
}
void CFileRepository::unsubscribeAll(NLNET::IModuleProxy *sender)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// build the string that this module's subscriptions will contain
NLMISC::CSString subscriptionString= sender->getModuleName()+" @ ";
// iterate over our subscriptions looking for matches to erase
TSubscribers::iterator last;
TSubscribers::iterator it;
for (it=_Subscribers.begin();it!=_Subscribers.end();)
{
// take a copy of the iterator and increment to point at the next element to be processed
last=it;
++it;
// see if the last element needs to be erased
if (last->second==sender)
{
// delegate to standard 'unsubscribe' to do the work
unsubscribe(sender,last->first.splitFrom('@').strip());
}
}
}
void CFileRepository::getInfo(NLNET::IModuleProxy *sender, const NLMISC::CSString &fileSpec)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
_AdministeredModuleWrapper.registerProgress("getInfo "+sender->getModuleName()+" "+fileSpec);
subscribe(sender,fileSpec);
unsubscribe(sender,fileSpec);
}
void CFileRepository::cbFileInfoUpdate(const SFileInfo& fileInfo)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
_AdministeredModuleWrapper.registerProgress(NLMISC::toString("cbFileInfoUpdate: %s (%d bytes)",fileInfo.FileName.c_str(),fileInfo.FileSize));
// add an entry to our changes list
_FileInfoChanges.push_back(fileInfo);
}
void CFileRepository::cbFileInfoErased(const NLMISC::CSString& fileName)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
_AdministeredModuleWrapper.registerProgress("cbFileInfoErased: "+fileName);
// add an entry to our changes list (this is an empty entry with just the filename set to represent a deleted file)
SFileInfo fileInfo;
fileInfo.FileName= fileName;
_FileInfoChanges.push_back(fileInfo);
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, incRescan)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
if (args.size()!=0)
return false;
CFileRepository::rescanPartial();
return true;
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, fullRescan)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
if (args.size()!=0)
return false;
CFileRepository::rescanFull();
return true;
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, getFile)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
if (args.size()!=1)
return true;
NLMISC::CSString data;
CFileRepository::getFile(args[0],data,NULL);
log.displayNL("Retrieved %u bytes of data for file %s, starting: %s",data.size(), args[0].c_str(), data.left(20).quote().c_str());
return true;
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, getFileInfo)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
// make sure a filespec was given
if (args.size()!=1)
return true;
// lookup the set of files
TFileInfoVector fileInfoVector;
getFileInfo(args[0],fileInfoVector,NULL);
// display a summary info message
log.displayNL("Result of info request '%s': %d matches",args[0].c_str(),fileInfoVector.size());
log.displayNL("- %-32s %10s %10s %s","checksum","time","size","name");
// iterate over results, displaying the info
for (TFileInfoVector::iterator it= fileInfoVector.begin(); it!= fileInfoVector.end(); ++it)
{
log.displayNL("- %-32s %10u %10u %s",it->Checksum.toString().c_str(),it->FileTime,it->FileSize,it->FileName.c_str());
}
return true;
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, updateFile)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
if (args.size()!=1)
return true;
CFileRepository::updateFile(args[0]);
return true;
}
NLMISC_CLASS_COMMAND_IMPL(CFileRepository, MaxHistorySize)
{
// make sure we've been initialised
nlassert(_Parent!=NULL);
switch(args.size())
{
case 1:
{
uint32 newVal= NLMISC::CSString(args[0]).atoui();
if (newVal==0 && args[0]!="0")
break;
CFileRepository::setMaxHistorySize(newVal);
}
// drop through...
case 0:
log.displayNL("MaxHistorySize %u",_MaxHistorySize);
return true;
}
return false;
}
} // end of namespace