1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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 //-----------------------------------------------------------------------------
22 //-----------------------------------------------------------------------------
25 #include "nel/misc/mem_stream.h"
26 #include "nel/misc/variable.h"
29 #include "game_share/utils.h"
32 #include "file_receiver.h"
33 #include "module_admin_itf.h"
34 #include "patchman_constants.h"
37 //-------------------------------------------------------------------------------------------------
39 //-------------------------------------------------------------------------------------------------
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 //-----------------------------------------------------------------------------
56 //-----------------------------------------------------------------------------
61 //-----------------------------------------------------------------------------
62 // methods CFileReceiver - basics
63 //-----------------------------------------------------------------------------
65 CFileReceiver::CFileReceiver()
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
);
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
)
96 // no idle proxies found...
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
;
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
;
245 // failed to find a match so clear out the file info record and 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
);
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
;
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
);
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()));
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
) );
383 CFileRepositoryProxy
fr(sender
);
384 fr
.requestFileData(_Parent
,fileName
,theRequest
->TotalDataRequested
,requestSize
);
386 // register info about the request we sent
387 theRequest
->TotalDataRequested
+= requestSize
;
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
);
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
);
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
));
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())
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
)
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
)
548 _downloadLog(theRequest
->FileName
,0,theRequest
->ExpectedFileSize
);
551 void CFileReceiver::_dealWithReceivedFile(TProxyPtr sender
,TFileRequestPtr theRequest
,const NLNET::TBinBuffer
& data
)
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());
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
);
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
)
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
)
626 _logState(theProxy
.CurrentRequest
==NULL
?"Idle":"Busy");
629 } // end of namespace