1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "nel/misc/debug.h"
22 #include "nel/misc/path.h"
23 #include "nel/misc/thread.h"
24 #include "nel/misc/sha1.h"
25 #include "nel/misc/big_file.h"
26 #include "nel/misc/i18n.h"
28 #include "data_scan.h"
36 using namespace NLMISC
;
39 // ***************************************************************************
40 static ucstring
dummyI18N(const std::string
&s
)
46 // ****************************************************************************
47 // ****************************************************************************
48 // ****************************************************************************
50 // ****************************************************************************
51 // ****************************************************************************
52 // ****************************************************************************
54 struct EPatchDownloadException
: public Exception
56 EPatchDownloadException() : Exception( "Download Error" ) {}
57 EPatchDownloadException( const std::string
& str
) : Exception( str
) {}
58 virtual ~EPatchDownloadException() throw() {}
61 CPatchManager
*CPatchManager::_Instance
= NULL
;
63 // ****************************************************************************
64 CPatchManager::CPatchManager() : State("t_state"), DataScanState("t_data_scan_state")
66 DescFilename
= "ryzom_xxxxx.idx";
68 ClientPatchPath
= "./unpack/";
69 ClientDataPath
= "./data/";
74 ScanDataThread
= NULL
;
77 ValidDescFile
= false;
80 // ****************************************************************************
81 void CPatchManager::init()
83 // retrieve the current client version, according to .idx
84 readClientVersionAndDescFile();
87 // ***************************************************************************
88 void CPatchManager::readClientVersionAndDescFile()
92 ValidDescFile
= false;
93 vector
<string
> vFiles
;
94 CPath::getPathContent(ClientPatchPath
, false, false, true, vFiles
);
95 uint32 nVersion
= 0xFFFFFFFF;
96 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
98 string sName
= CFile::getFilename(vFiles
[i
]);
99 string sExt
= CFile::getExtension(sName
);
100 string sBase
= sName
.substr(0, sName
.rfind('_'));
101 if ((sExt
== "idx") && (sBase
== "ryzom"))
103 string val
= sName
.substr(sName
.rfind('_')+1, 5);
104 uint32 nNewVersion
= atoi(val
.c_str());
105 if ((nNewVersion
> nVersion
) || (nVersion
== 0xFFFFFFFF))
106 nVersion
= nNewVersion
;
109 if (nVersion
!= 0xFFFFFFFF)
110 readDescFile(nVersion
);
112 DescFilename
= "unknown";
113 ValidDescFile
= true;
117 // Not important that there is no desc file
121 // ****************************************************************************
122 // Called in main thread
123 bool CPatchManager::getThreadState (ucstring
&stateOut
, vector
<ucstring
> &stateLogOut
)
125 if (ScanDataThread
==NULL
)
132 // Get access to the state
135 CSynchronized
<CState
>::CAccessor
as(&State
);
136 CState
&rState
= as
.value();
137 if (rState
.StateChanged
)
141 stateOut
= rState
.State
;
142 stateLogOut
= rState
.StateLog
;
144 rState
.StateLog
.clear();
145 rState
.StateChanged
= false;
150 if (isVerboseLog() && !stateLogOut
.empty())
151 for (uint32 i
= 0; i
< stateLogOut
.size(); ++i
)
152 nlinfo("%s", stateLogOut
[i
].toString().c_str());
157 // ****************************************************************************
158 int CPatchManager::getTotalFilesToGet()
160 if (ScanDataThread
!= NULL
)
161 return ScanDataThread
->TotalFileToScan
;
166 // ****************************************************************************
167 int CPatchManager::getCurrentFilesToGet()
169 if (ScanDataThread
!= NULL
)
170 return ScanDataThread
->CurrentFileScanned
;
175 // ****************************************************************************
176 // Take care this function is called by the thread
177 void CPatchManager::setState (bool bOutputToLog
, const ucstring
&ucsNewState
)
180 CSynchronized
<CState
>::CAccessor
as(&State
);
181 CState
&rState
= as
.value();
182 rState
.State
= ucsNewState
;
184 rState
.StateLog
.push_back(ucsNewState
);
185 rState
.StateChanged
= true;
189 // ****************************************************************************
190 string
CPatchManager::getClientVersion()
195 return toString("%05d", DescFile
.getFiles().getVersionNumber());
198 // ****************************************************************************
199 void CPatchManager::readDescFile(sint32 nVersion
)
201 DescFilename
= toString("ryzom_%05d.idx", nVersion
);
202 string srcName
= ClientPatchPath
+ DescFilename
;
204 if (!DescFile
.load(srcName
))
205 throw Exception ("Can't open file '%s'", srcName
.c_str ());
208 // ****************************************************************************
209 // Get all the patches that need to be applied to a file from the description of this file given by the server
210 void CPatchManager::getPatchFromDesc(SFileToPatch
&ftpOut
, const CBNPFile
&fIn
, bool forceCheckSumTest
)
213 const CBNPFile rFile
= fIn
;
214 const string
&rFilename
= rFile
.getFileName();
215 // Does the BNP exists ?
216 string sFilePath
= CPath::lookup(rFilename
);
217 if (sFilePath
.empty())
219 if (NLMISC::CFile::fileExists(ClientPatchPath
+ rFilename
))
220 sFilePath
= ClientPatchPath
+ rFilename
;
223 // if file not found anywhere
224 if (sFilePath
.empty())
226 ftpOut
.FileName
= rFilename
;
227 ftpOut
.LocalFileToDelete
= false;
228 ftpOut
.LocalFileExists
= false;
229 // It happens some time (maybe a bug) that the versionCount is 0... =>
230 // it happens if the BNP file is empty (8 bytes)
231 ftpOut
.FinalFileSize
= EmptyBnpFileSize
;
232 // BNP does not exists : get all the patches version
233 for (j
= 0; j
< rFile
.versionCount(); ++j
)
235 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
236 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
237 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
238 ftpOut
.FinalFileSize
= rFile
.getVersion(j
).getFileSize();
243 // The local BNP file exists : find its version
244 uint32 nLocalSize
= CFile::getFileSize(sFilePath
);
245 uint32 nLocalTime
= CFile::getFileModificationDate(sFilePath
);
246 // From the couple time, size look the version of the file
247 uint32 nVersionFound
= 0xFFFFFFFF;
248 // If forceChecksum is wanted (slow), then don't do the test with filesize/date
249 if(!forceCheckSumTest
)
251 for (j
= 0; j
< rFile
.versionCount(); ++j
)
253 const CBNPFileVersion
&rVersion
= rFile
.getVersion(j
);
254 uint32 nServerSize
= rVersion
.getFileSize();
255 uint32 nServerTime
= rVersion
.getTimeStamp();
256 // Does the time and size match a version ?
257 if ((nServerSize
== nLocalSize
) && (abs((sint32
)(nServerTime
- nLocalTime
)) <= 2) )
259 nVersionFound
= rVersion
.getVersionNumber();
260 // break; // ace -> get the last good version (if more than one version of the same file exists)
265 // If the version cannot be found with size and time try with sha1
266 if (nVersionFound
== 0xFFFFFFFF)
268 ucstring sTranslate
= dummyI18N("Checking Integrity :") + " " + rFilename
;
269 setState(true, sTranslate
);
270 CHashKey hkLocalSHA1
= getSHA1(sFilePath
);
271 for (j
= 0; j
< rFile
.versionCount(); ++j
)
273 const CBNPFileVersion
&rVersion
= rFile
.getVersion(j
);
274 CHashKey hkServerSHA1
= rVersion
.getHashKey();
275 // Does the sha1 match a version ?
276 if (hkServerSHA1
== hkLocalSHA1
)
278 nVersionFound
= rVersion
.getVersionNumber();
279 // break; // ace -> same as above
284 // No version available found
285 if (nVersionFound
== 0xFFFFFFFF)
287 ucstring sTranslate
= dummyI18N("No Version Found");
288 setState(true, sTranslate
);
289 // Get all patches from beginning (first patch is reference file)
290 ftpOut
.FileName
= rFilename
;
291 ftpOut
.LocalFileToDelete
= true;
292 ftpOut
.LocalFileExists
= true;
293 // It happens some time (maybe a bug) that the versionCount is 0... =>
294 // it happens if the BNP file is empty (8 bytes)
295 ftpOut
.FinalFileSize
= EmptyBnpFileSize
;
296 // Get all the patches version
297 for (j
= 0; j
< rFile
.versionCount(); ++j
)
299 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
300 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
301 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
302 ftpOut
.FinalFileSize
= rFile
.getVersion(j
).getFileSize();
305 else // A version of the file has been found
307 ucstring sTranslate
= dummyI18N("Version Found :") + " " + toString(nVersionFound
);
308 setState(true, sTranslate
);
309 // Get All patches from this version !
310 ftpOut
.FileName
= rFilename
;
311 ftpOut
.LocalFileToDelete
= false;
312 ftpOut
.LocalFileExists
= true;
314 for (j
= 0; j
< rFile
.versionCount(); ++j
)
315 if (rFile
.getVersion(j
).getVersionNumber() == nVersionFound
)
318 nlassert(j
!= rFile
.versionCount()); // Not normal if we cant find the version we found previously
320 // Point on the next version
322 // If there are newer versions
323 if (j
!= rFile
.versionCount())
325 // Add all version until the last one
326 for (; j
< rFile
.versionCount(); ++j
)
328 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
329 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
330 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
333 // Else this file is up to date !
335 // For info, get its final file size
336 ftpOut
.FinalFileSize
= rFile
.getVersion(rFile
.versionCount()-1).getFileSize();
338 } // end of else local BNP file exists
342 // ***************************************************************************
343 void CPatchManager::startScanDataThread()
345 if (ScanDataThread
!= NULL
)
347 nlwarning ("scan data thread is already running");
352 nlwarning ("a thread is already running");
359 // Read now the client version and Desc File.
360 readClientVersionAndDescFile();
363 ScanDataThread
= new CScanDataThread();
364 nlassert (ScanDataThread
!= NULL
);
366 thread
= IThread::create (ScanDataThread
);
367 nlassert (thread
!= NULL
);
371 // ****************************************************************************
372 bool CPatchManager::isScanDataThreadEnded(bool &ok
)
374 if (ScanDataThread
== NULL
)
380 bool end
= ScanDataThread
->Ended
;
383 ok
= ScanDataThread
->CheckOk
;
384 stopScanDataThread();
390 // ****************************************************************************
391 void CPatchManager::stopScanDataThread()
393 if(ScanDataThread
&& thread
)
398 delete ScanDataThread
;
399 ScanDataThread
= NULL
;
403 // ***************************************************************************
404 void CPatchManager::askForStopScanDataThread()
409 ScanDataThread
->AskForCancel
= true;
412 // ***************************************************************************
413 bool CPatchManager::getDataScanLog(ucstring
&text
)
418 TSyncDataScanState::CAccessor
ac(&DataScanState
);
419 CDataScanState
&val
= ac
.value();
420 changed
= val
.Changed
;
421 // if changed, build the log
424 for(uint i
=0;i
<val
.FilesWithScanDataError
.size();i
++)
427 getCorruptedFileInfo(val
.FilesWithScanDataError
[i
], str
);
438 // ***************************************************************************
439 void CPatchManager::addDataScanLogCorruptedFile(const SFileToPatch
&ftp
)
442 TSyncDataScanState::CAccessor
ac(&DataScanState
);
443 CDataScanState
&val
= ac
.value();
444 val
.FilesWithScanDataError
.push_back(ftp
);
449 // ***************************************************************************
450 void CPatchManager::clearDataScanLog()
453 TSyncDataScanState::CAccessor
ac(&DataScanState
);
454 CDataScanState
&val
= ac
.value();
455 val
.FilesWithScanDataError
.clear();
460 // ***************************************************************************
461 void CPatchManager::getCorruptedFileInfo(const SFileToPatch
&ftp
, ucstring
&sTranslate
)
463 sTranslate
= dummyI18N("Corrupted File: ") + ftp
.FileName
+ " (" +
464 toString("%.1f ", (float)ftp
.FinalFileSize
/1000000.f
) + dummyI18N("Mb") + ")";
468 // ****************************************************************************
469 // ****************************************************************************
470 // ****************************************************************************
472 // ****************************************************************************
473 // ****************************************************************************
474 // ****************************************************************************
476 // ****************************************************************************
477 CScanDataThread::CScanDataThread()
483 CurrentFileScanned
= 1;
486 // ****************************************************************************
487 void CScanDataThread::run ()
489 CPatchManager
*pPM
= CPatchManager::getInstance();
493 // Check if the client version is the same as the server version
494 string sClientVersion
= pPM
->getClientVersion();
495 ucstring sTranslate
= dummyI18N("Client Version") + " (" + sClientVersion
+ ") ";
496 pPM
->setState(true, sTranslate
);
498 // For all bnp in the description file get all patches to apply
499 // depending on the version of the client bnp files
500 const CBNPFileSet
&rDescFiles
= pPM
->DescFile
.getFiles();
501 TotalFileToScan
= rDescFiles
.fileCount();
502 for (i
= 0; i
< rDescFiles
.fileCount(); ++i
)
504 sTranslate
= dummyI18N("Checking File") + " " + rDescFiles
.getFile(i
).getFileName();
505 pPM
->setState(true, sTranslate
);
507 // get list of file to apply to this patch, performing a full checksum test (slow...)
508 CPatchManager::SFileToPatch ftp
;
509 pPM
->getPatchFromDesc(ftp
, rDescFiles
.getFile(i
), true);
510 // if the file has been found but don't correspond to any local version (SHA1)
511 if (ftp
.LocalFileExists
&& ftp
.LocalFileToDelete
)
513 pPM
->addDataScanLogCorruptedFile(ftp
);
514 CPatchManager::getCorruptedFileInfo(ftp
, sTranslate
);
515 pPM
->setState(true, sTranslate
);
517 CurrentFileScanned
= i
;
519 // if the user ask to cancel the thread, stop now
524 sTranslate
= dummyI18N("Checking file ended with no error");
525 pPM
->setState(true, sTranslate
);
531 ucstring sTranslate
= dummyI18N("Checking file ended with errors :") + " " + e
.what();
532 pPM
->setState(true, sTranslate
);