Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / patchman_service / file_receiver.cpp
blobf66e404c88eef529ee0de743438e0c38f0f75c8f
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 //-----------------------------------------------------------------------------
21 // includes
22 //-----------------------------------------------------------------------------
24 // nel
25 #include "nel/misc/mem_stream.h"
26 #include "nel/misc/variable.h"
28 // game share
29 #include "game_share/utils.h"
31 // local
32 #include "file_receiver.h"
33 #include "module_admin_itf.h"
34 #include "patchman_constants.h"
37 //-------------------------------------------------------------------------------------------------
38 // namespaces
39 //-------------------------------------------------------------------------------------------------
41 using namespace std;
42 using namespace NLMISC;
43 using namespace NLNET;
46 //-----------------------------------------------------------------------------
47 // some NLMISC Variable
48 //-----------------------------------------------------------------------------
50 NLMISC::CVariable<uint32> FileReceiverMaxMessageCount("patchman","FileReceiverMaxMessageCount", "number of packets we're allowed to send at a time", 100, 0, true );
51 NLMISC::CVariable<uint32> FileReceiverDataBlockSize("patchman","FileReceiverDataBlockSize", "maximum size of each data packet", 10480, 0, true );
54 //-----------------------------------------------------------------------------
55 // namespace PATCHMAN
56 //-----------------------------------------------------------------------------
58 namespace PATCHMAN
61 //-----------------------------------------------------------------------------
62 // methods CFileReceiver - basics
63 //-----------------------------------------------------------------------------
65 CFileReceiver::CFileReceiver()
67 _Parent=NULL;
70 void CFileReceiver::init(NLNET::IModule *parent, const std::string &fileSpec)
72 std::vector<std::string> fileSpecs;
73 fileSpecs.push_back(fileSpec);
74 init(parent, fileSpecs);
77 void CFileReceiver::init(NLNET::IModule *parent, const std::vector<std::string> &fileSpecs)
79 CFileReceiverSkel::init(parent);
80 _Parent = parent;
81 _FileSpecs.clear();
82 for (std::vector<std::string>::const_iterator it(fileSpecs.begin()), end(fileSpecs.end()); it != end; ++it)
83 _FileSpecs.push_back(*it);
84 _AdministeredModuleWrapper.init(dynamic_cast<CAdministeredModuleBase *>(parent));
87 bool CFileReceiver::haveIdleProxies() const
89 // if we can find an idle proxy then return true
90 for (TProxies::const_iterator pit= _Proxies.begin(); pit!=_Proxies.end();++pit)
92 if (pit->second.CurrentRequest==NULL)
93 return true;
96 // no idle proxies found...
97 return false;
100 void CFileReceiver::dump(NLMISC::CLog& log) const
102 log.displayNL("-----------------------------------");
103 log.displayNL("File requests");
104 log.displayNL("-----------------------------------");
105 for (TFileRequests::const_iterator fit= _FileRequests.begin(); fit!= _FileRequests.end(); ++fit)
107 SFileRequest& request= *(*fit);
108 log.displayNL("- File: '%s' %s (%d..%d/%d)",
109 request.FileName.c_str(),
110 (request.Emitter==NULL)? "No emitter": request.Emitter->getModuleName().c_str(),
111 request.DataSoFar.size(),
112 request.TotalDataRequested,
113 request.ExpectedFileSize );
116 log.displayNL("-----------------------------------");
117 log.displayNL("Connected proxies");
118 log.displayNL("-----------------------------------");
119 for (TProxies::const_iterator pit= _Proxies.begin(); pit!= _Proxies.end(); ++pit)
121 log.displayNL("- Repository %s (%d files): Current Request: %s",
122 pit->second.Proxy->getModuleName().c_str(),
123 pit->second.FileInfo.size(),
124 (pit->second.CurrentRequest==NULL)? "None": pit->second.CurrentRequest->FileName.c_str());
126 log.displayNL("-----------------------------------");
129 void CFileReceiver::dumpFileInfo(const std::string &fileSpec,NLMISC::CLog& log) const
131 // setup a vector to hold fileInfo results and call getFileInfo() to fill it in
132 TFileInfoVector result;
133 getFileInfo(fileSpec,result);
135 // display a summary info message
136 log.displayNL("Result of info request '%s': %d matches",fileSpec.c_str(),result.size());
137 log.displayNL("- %-32s %10s %10s %s","checksum","time","size","name");
139 // iterate over results, displaying the info
140 for (TFileInfoVector::iterator it= result.begin(); it!=result.end(); ++it)
142 log.displayNL("- %-32s %10u %10u %s",it->Checksum.toString().c_str(),it->FileTime,it->FileSize,it->FileName.c_str());
147 //-----------------------------------------------------------------------------
148 // methods CFileReceiver - called from CModuleBase specialisations
149 //-----------------------------------------------------------------------------
151 void CFileReceiver::onModuleUp(NLNET::IModuleProxy *module)
153 // make sure we've been initialised
154 nlassert(_Parent!=NULL);
156 if (NLMISC::CSString(module->getModuleManifest()).contains(ManifestEntryIsFileRepository))
158 CFileRepositoryProxy spr(module);
159 for (std::vector<NLMISC::CSString>::const_iterator it(_FileSpecs.begin()), end(_FileSpecs.end()); it != end; ++it)
160 spr.subscribe(_Parent, *it);
161 _log("Repository up: "+module->getModuleName());
162 _Proxies[module].Proxy= module;
166 void CFileReceiver::onModuleDown(NLNET::IModuleProxy *module)
168 // make sure we've been initialised
169 nlassert(_Parent!=NULL);
171 if (_Proxies.find(module)!=_Proxies.end())
173 _log("Repository down: "+module->getModuleName());
175 // get a refference to the proxy
176 SProxyInfo& theProxy= _Proxies[module];
178 // signal the change of state of files that appear in the proxy file list
179 for (TFileInfoMap::const_iterator fit= theProxy.FileInfo.begin(); fit!=theProxy.FileInfo.end();++fit)
181 // call a user callback (if there is one), signalling the change of info for the given file
182 cbFileInfoChange(fit->second.FileName);
185 // grab the request that was running (if there was one)
186 SFileRequest* theRequest= _Proxies[module].CurrentRequest;
188 // remove the proxy from the proxies map
189 _Proxies.erase(module);
191 // try to reassign the request to someone else
192 if (theRequest!=NULL)
194 // cleanup the broken file request and re-dispatch if possible
195 _treatBrokenFileRequest(theRequest);
200 void CFileReceiver::onModuleUpdate()
204 const std::string &CFileReceiver::getModuleManifest() const
206 static std::string manifest= ManifestEntryIsFileReceiver;
207 return manifest;
211 //-----------------------------------------------------------------------------
212 // methods CFileReceiver - main API
213 //-----------------------------------------------------------------------------
215 void CFileReceiver::requestFile(const std::string &fileName)
217 // make sure we've been initialised
218 nlassert(_Parent!=NULL);
219 _log("Registering request for: "+fileName);
221 // create our new file request record
222 TFileRequestPtr newRequest= new SFileRequest;
223 newRequest->FileName= fileName;
224 newRequest->ExpectedFileSize= ~0u;
225 _FileRequests.push_back(newRequest);
227 // try to dispatch the request to one of our proxies
228 _requestFile(newRequest);
231 bool CFileReceiver::getSingleFileInfo(const std::string &fileName,SFileInfo& fileInfo) const
233 // run through the attached proxies to find a match for the file...
234 for (TProxies::const_iterator pit= _Proxies.begin(); pit!=_Proxies.end();++pit)
236 // if there's a match for this proxy then it'll do
237 TFileInfoMap::const_iterator fit= pit->second.FileInfo.find(fileName);
238 if (fit!=pit->second.FileInfo.end())
240 fileInfo= fit->second;
241 return true;
245 // failed to find a match so clear out the file info record and return false
246 fileInfo.clear();
247 return false;
250 void CFileReceiver::getFileInfo(const std::string &fileSpec,TFileInfoVector& result) const
252 // setup an object for testing matches for a given filespec
253 CFileSpec pattern(fileSpec);
255 // run through the attached proxies to find a match for the file...
256 for (TProxies::const_iterator pit= _Proxies.begin(); pit!=_Proxies.end();++pit)
258 // if this is a request for a particular file then just do a lookup
259 if (!pattern.isWild())
261 // if there's a match for this proxy then it'll do
262 TFileInfoMap::const_iterator fit= pit->second.FileInfo.find(fileSpec);
263 if (fit!=pit->second.FileInfo.end())
265 result.push_back(fit->second);
267 continue;
270 // iterate over all of the entries for the proxy
271 for (TFileInfoMap::const_iterator fit=pit->second.FileInfo.begin(); fit!= pit->second.FileInfo.end(); ++fit)
273 // if the pattern is set to 'all' then iterate
274 if (pattern.matches(fit->second.FileName))
276 result.push_back(fit->second);
283 //-----------------------------------------------------------------------------
284 // methods CFileReceiver - message callbacks
285 //-----------------------------------------------------------------------------
287 void CFileReceiver::setupSubscriptions(NLNET::IModuleProxy *sender)
289 // make sure we've been initialised
290 nlassert(_Parent!=NULL);
292 // make sure the proxy that sent this list exist
293 DROP_IF(_Proxies.find(sender)==_Proxies.end(),"Ignoring unexpected SetupSubscriptions from module "+sender->getModuleName(),return);
295 // send the subscription request
296 CFileRepositoryProxy spr(sender);
297 for (std::vector<NLMISC::CSString>::const_iterator it(_FileSpecs.begin()), end(_FileSpecs.end()); it != end; ++it)
298 spr.subscribe(_Parent, *it);
299 _log(NLMISC::toString("setupSubscriptions from: %s",sender->getModuleName().c_str()));
302 void CFileReceiver::cbFileInfo(NLNET::IModuleProxy *sender, const TFileInfoVector &files)
304 // make sure we've been initialised
305 nlassert(_Parent!=NULL);
306 _log(NLMISC::toString("List (%d files) from: %s",files.size(),sender->getModuleName().c_str()));
308 // make sure the proxy that sent this list exist
309 DROP_IF(_Proxies.find(sender)==_Proxies.end(),"Ignoring unexpected file list from module "+sender->getModuleName(),return);
311 // get a refference to the proxy
312 SProxyInfo& theProxy= _Proxies[sender];
314 // store away the proxy's file list
315 for (TFileInfoVector::const_iterator fit= files.begin(); fit!=files.end();++fit)
317 if (fit->FileTime!=0)
319 theProxy.FileInfo[fit->FileName]= *fit;
321 else
323 theProxy.FileInfo.erase(fit->FileName);
326 // call a user callback (if there is one), signalling the change of info for the given file
327 cbFileInfoChange(fit->FileName);
330 // if the proxy was idle then look for something to do
331 if (theProxy.CurrentRequest==NULL)
333 _lookForNewJob(theProxy);
337 void CFileReceiver::cbFileData(NLNET::IModuleProxy *sender, const std::string &fileName, uint32 startOffset, const NLNET::TBinBuffer &data)
339 // make sure we've been initialised
340 nlassert(_Parent!=NULL);
342 // look for the request that this data block corresponds to
343 DROP_IF(_Proxies.find(sender)==_Proxies.end(),"Ignoring unexpected file data for file '"+fileName+"' from module "+sender->getModuleName(),return);
345 SProxyInfo& theSender= _Proxies[sender];
346 TFileRequestPtr theRequest= theSender.CurrentRequest;
347 DROP_IF(theRequest==NULL,"Ignoring unexpected file data for file '"+fileName+"' from module "+sender->getModuleName(),return);
348 BOMB_IF(theRequest->Emitter!=theSender.Proxy,"Ignoring file data for file '"+fileName+"' from broken module "+sender->getModuleName(),return);
349 DROP_IF(theRequest->FileName!=fileName,"Ignoring unexpected file data for file '"+fileName+"' when expecting file '"+theRequest->FileName+"' from module "+sender->getModuleName(),return);
351 // clear the download log for this file in case we've finished ... we'll set it again at the end of the routine
352 _clearDownloadLog(fileName);
354 // did we just deal the whole file in a single block?
355 if (data.getBufferSize()>=theRequest->ExpectedFileSize)
357 // we've reached the end of file
358 _dealWithReceivedFile(sender,theRequest,data);
359 return;
362 // add the data to the file
363 CSString& theBuffer= theRequest->DataSoFar;
364 uint32 oldSize= (uint32)theBuffer.size();
365 theBuffer.resize(oldSize+data.getBufferSize());
366 memcpy(&(theBuffer[oldSize]), data.getBuffer(), data.getBufferSize());
368 // have we reached the end of file?
369 if (theRequest->DataSoFar.size()>=theRequest->ExpectedFileSize)
371 // we've reached the end of file
372 _dealWithReceivedFile(sender,theRequest,NLNET::TBinBuffer((const uint8 *)&theRequest->DataSoFar[0],(uint32)theRequest->DataSoFar.size()));
373 return;
376 // we're not at the end of file so think about adding a request for another data block
377 if (theRequest->TotalDataRequested< theRequest->ExpectedFileSize)
379 // work out size of block to request
380 uint32 requestSize= min( uint32(theRequest->ExpectedFileSize- theRequest->TotalDataRequested), uint32(FileReceiverDataBlockSize) );
382 // send the request
383 CFileRepositoryProxy fr(sender);
384 fr.requestFileData(_Parent,fileName,theRequest->TotalDataRequested,requestSize);
386 // register info about the request we sent
387 theRequest->TotalDataRequested+= requestSize;
390 // log our progress
391 _downloadLog(fileName,(uint32)theRequest->DataSoFar.size(),theRequest->ExpectedFileSize);
394 void CFileReceiver::cbFileDataFailure(NLNET::IModuleProxy *sender, const std::string &fileName)
396 // make sure we've been initialised
397 nlassert(_Parent!=NULL);
398 _logError("Read Failure "+sender->getModuleName()+": "+fileName);
400 // clear the download log for this file as it's all broken
401 _clearDownloadLog(fileName);
403 // look for the request that this data block corresponds to
404 DROP_IF(_Proxies.find(sender)==_Proxies.end(),"Ignoring unexpected file read failure for file '"+fileName+"' from module "+sender->getModuleName(),return);
406 SProxyInfo& theSender= _Proxies[sender];
407 TFileRequestPtr theRequest= theSender.CurrentRequest;
409 DROP_IF(theRequest==NULL,"Ignoring unexpected file data for file '"+fileName+"' from module "+sender->getModuleName(),return);
410 DROP_IF(theRequest->Emitter==NULL,"Ignoring already treaded file error for '"+fileName+"' from module "+sender->getModuleName(),return);
411 BOMB_IF(theRequest->Emitter!=theSender.Proxy,"Ignoring file read failure for file '"+fileName+"' from broken module "+sender->getModuleName(),return);
412 DROP_IF(theRequest->FileName!=fileName,"Ignoring unexpected file read failure for file '"+fileName+"' when expecting file '"+theRequest->FileName+"' from module "+sender->getModuleName(),return);
414 // cleanup the broken file request and re-dispatch if possible
415 _treatBrokenFileRequest(theRequest);
419 //-----------------------------------------------------------------------------
420 // methods CFileReceiver - private methods
421 //-----------------------------------------------------------------------------
423 void CFileReceiver::_log(const NLMISC::CSString& msg) const
425 const CAdministeredModuleBase* adminModule= dynamic_cast<const CAdministeredModuleBase*>(_Parent);
426 if (adminModule!=NULL)
428 adminModule->registerProgress(msg);
430 else
432 nldebug("CFileReceiver_%s",msg.c_str());
436 void CFileReceiver::_logError(const NLMISC::CSString& msg) const
438 const CAdministeredModuleBase* adminModule= dynamic_cast<const CAdministeredModuleBase*>(_Parent);
439 if (adminModule!=NULL)
441 adminModule->registerError(msg);
443 else
445 nlwarning("CFileReceiver: %s",msg.c_str());
449 void CFileReceiver::_logState(const NLMISC::CSString& state) const
451 const CAdministeredModuleBase* adminModule= dynamic_cast<const CAdministeredModuleBase*>(_Parent);
452 if (adminModule!=NULL)
454 adminModule->setStateVariable("state",state);
458 void CFileReceiver::_downloadLog(const NLMISC::CSString& fileName,uint32 bytesSoFar, uint32 bytesExpected) const
460 const CAdministeredModuleBase* adminModule= dynamic_cast<const CAdministeredModuleBase*>(_Parent);
461 if (adminModule!=NULL)
463 adminModule->setStateVariable(fileName,NLMISC::toString("%d/%d",bytesSoFar,bytesExpected));
465 else
467 nldebug("CFileReceiver_Download: %s: %d/%d",fileName.c_str(),bytesSoFar,bytesExpected);
471 void CFileReceiver::_clearDownloadLog(const NLMISC::CSString& fileName) const
473 const CAdministeredModuleBase* adminModule= dynamic_cast<const CAdministeredModuleBase*>(_Parent);
474 if (adminModule!=NULL)
476 adminModule->clearStateVariable(fileName);
480 void CFileReceiver::_requestFile(TFileRequestPtr theRequest)
482 // if all of the proxies are busy then giveup
483 if (!haveIdleProxies())
484 return;
486 // use a little set for the proxies capable of responding to my request
487 TFileRequestMatches requestMatches;
489 // give each of our connected proxys a chance to take on this new file
490 for (TProxies::iterator pit= _Proxies.begin(); pit!=_Proxies.end();++pit)
492 // see whether the proxy has a record for the file that we're after
493 TFileInfoMap::iterator fit= pit->second.FileInfo.find(theRequest->FileName);
494 if (fit!=pit->second.FileInfo.end())
496 requestMatches[pit->first]=fit->second;
500 // if we found no candidates for our file then throw a warning and give up
501 DROP_IF(requestMatches.empty(),"No connected emitters found for requested file: "+theRequest->FileName,return);
503 // validate the set of request matches
504 // this routine will strip out any requests that the derived class doesn't like
505 // the set may be empty on return
506 cbValidateRequestMatches(requestMatches);
508 // we're good to go if we can find a proxy who's not busy already...
509 for (TFileRequestMatches::iterator rit= requestMatches.begin(); rit!=requestMatches.end(); ++rit)
511 // if the proxy doesn't exist then skip it
512 BOMB_IF(_Proxies.find(rit->first)==_Proxies.end(),"ERROR: Ignoring bad proxy value in _requestFile()",continue);
513 SProxyInfo& theProxy= _Proxies[rit->first];
515 // if the proxy is busy then just skip on forwards...
516 if (theProxy.CurrentRequest!=NULL)
517 continue;
519 // we've found a proxy who's not busy so we can dispatch messages...
520 CFileRepositoryProxy fr(_Proxies[rit->first].Proxy);
522 // set the job that the proxy is working on
523 theProxy.CurrentRequest= theRequest;
524 theRequest->Emitter= theProxy.Proxy;
525 theRequest->ExpectedFileSize= theProxy.FileInfo[theRequest->FileName].FileSize;
526 theRequest->DataSoFar.reserve(theRequest->ExpectedFileSize);
528 // dispatch as many packets as we're allowed to...
529 for (uint32 i=0;i<FileReceiverMaxMessageCount;++i)
531 // work out size of block to request
532 uint32 requestSize= min((uint32)FileReceiverDataBlockSize,theRequest->ExpectedFileSize-theRequest->TotalDataRequested);
534 // dispatch the request
535 fr.requestFileData(_Parent,theRequest->FileName,theRequest->TotalDataRequested,requestSize);
537 // update the count of data requested
538 theRequest->TotalDataRequested+= requestSize;
540 // make sure we don't try to send more info than the file contains
541 nlassert(theRequest->ExpectedFileSize>=theRequest->TotalDataRequested);
543 // if we've requested all of the data that there is in the file then break out now
544 if (theRequest->ExpectedFileSize==theRequest->TotalDataRequested)
545 break;
548 _downloadLog(theRequest->FileName,0,theRequest->ExpectedFileSize);
551 void CFileReceiver::_dealWithReceivedFile(TProxyPtr sender,TFileRequestPtr theRequest,const NLNET::TBinBuffer& data)
553 // log progress..
554 _clearDownloadLog(theRequest->FileName);
555 _log("receivedFile: "+theRequest->FileName+NLMISC::toString("(%d bytes)",data.getBufferSize()));
557 // call the user callback for the file data
558 NLMISC::CMemStream memStream;
559 memStream.fill(data.getBuffer(),data.getBufferSize());
560 memStream.invert();
561 cbFileDownloadSuccess(theRequest->FileName,memStream);
563 // look for the proxy record for the emitter
564 TProxies::iterator pit= _Proxies.find(sender);
565 BOMB_IF(pit==_Proxies.end(),"ERROR: Failed to identify the sender for the received file: "+theRequest->FileName,return);
567 // liberate this request
568 for (TFileRequests::iterator fit=_FileRequests.begin(); fit!=_FileRequests.end();++fit)
570 if (*fit==theRequest)
572 _FileRequests.erase(fit);
573 break;
577 // cleanup the emitter
578 pit->second.CurrentRequest= NULL;
580 // look for a new job for the sender
581 _lookForNewJob(pit->second);
584 void CFileReceiver::_treatBrokenFileRequest(TFileRequestPtr theRequest)
586 // log progress..
587 _clearDownloadLog(theRequest->FileName);
588 _logError("treatBrokenFile: "+theRequest->FileName);
590 // call the user callback
591 cbRetryAfterFileDownloadFailure(theRequest->FileName);
593 // remove the file entry from the map of the sender to ensure that we don't lock up
594 if (_Proxies.find(theRequest->Emitter)!= _Proxies.end())
596 _Proxies[theRequest->Emitter].FileInfo.erase(theRequest->FileName);
599 // cleanup proxy usage
600 if (_Proxies.find(theRequest->Emitter) != _Proxies.end())
602 _Proxies[theRequest->Emitter].CurrentRequest = NULL;
605 // cleanup the request in order to be able to reassign it
606 theRequest->Emitter= NULL;
607 theRequest->ExpectedFileSize= 0;
608 theRequest->DataSoFar.clear();
609 theRequest->TotalDataRequested = 0;
611 // try to reassign the request...
612 _requestFile(theRequest);
615 void CFileReceiver::_lookForNewJob(SProxyInfo& theProxy)
617 // run through the request set looking for requests that haven't yet been handled...
618 for (TFileRequests::iterator fit=_FileRequests.begin(); fit!=_FileRequests.end() && theProxy.CurrentRequest==NULL;++fit)
620 if ((*fit)->Emitter==NULL)
622 _requestFile(*fit);
626 _logState(theProxy.CurrentRequest==NULL?"Idle":"Busy");
629 } // end of namespace