Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / tools / client / client_data_check / data_scan.cpp
blob7031aaa927a0b2c5b8e141ff7d887ecf32be9ca2
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
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.
8 //
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/>.
18 // Includes
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"
32 // Namespaces
35 using namespace std;
36 using namespace NLMISC;
39 // ***************************************************************************
40 static ucstring dummyI18N(const std::string &s)
42 return s;
46 // ****************************************************************************
47 // ****************************************************************************
48 // ****************************************************************************
49 // CPatchManager
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/";
72 VerboseLog = true;
74 ScanDataThread = NULL;
75 thread = 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()
90 try
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);
111 else
112 DescFilename = "unknown";
113 ValidDescFile = true;
115 catch(Exception &)
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)
126 return false;
128 // clear output
129 stateOut.clear();
130 stateLogOut.clear();
132 // Get access to the state
133 bool changed= false;
135 CSynchronized<CState>::CAccessor as(&State);
136 CState &rState= as.value();
137 if (rState.StateChanged)
139 // and retrieve info
140 changed= true;
141 stateOut = rState.State;
142 stateLogOut = rState.StateLog;
143 // clear state
144 rState.StateLog.clear();
145 rState.StateChanged= false;
149 // verbose log
150 if (isVerboseLog() && !stateLogOut.empty())
151 for (uint32 i = 0; i < stateLogOut.size(); ++i)
152 nlinfo("%s", stateLogOut[i].toString().c_str());
154 return changed;
157 // ****************************************************************************
158 int CPatchManager::getTotalFilesToGet()
160 if (ScanDataThread != NULL)
161 return ScanDataThread->TotalFileToScan;
163 return 1;
166 // ****************************************************************************
167 int CPatchManager::getCurrentFilesToGet()
169 if (ScanDataThread != NULL)
170 return ScanDataThread->CurrentFileScanned;
172 return 1;
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;
183 if(bOutputToLog)
184 rState.StateLog.push_back(ucsNewState);
185 rState.StateChanged= true;
189 // ****************************************************************************
190 string CPatchManager::getClientVersion()
192 if (!ValidDescFile)
193 return "";
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;
203 DescFile.clear();
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)
212 uint32 j;
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();
241 else
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;
313 // Go to the version
314 for (j = 0; j < rFile.versionCount(); ++j)
315 if (rFile.getVersion(j).getVersionNumber() == nVersionFound)
316 break;
318 nlassert(j != rFile.versionCount()); // Not normal if we cant find the version we found previously
320 // Point on the next version
321 j++;
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");
348 return;
350 if (thread != NULL)
352 nlwarning ("a thread is already running");
353 return;
356 // Reset result
357 clearDataScanLog();
359 // Read now the client version and Desc File.
360 readClientVersionAndDescFile();
362 // start thread
363 ScanDataThread = new CScanDataThread();
364 nlassert (ScanDataThread != NULL);
366 thread = IThread::create (ScanDataThread);
367 nlassert (thread != NULL);
368 thread->start ();
371 // ****************************************************************************
372 bool CPatchManager::isScanDataThreadEnded(bool &ok)
374 if (ScanDataThread == NULL)
376 ok = false;
377 return true;
380 bool end = ScanDataThread->Ended;
381 if (end)
383 ok = ScanDataThread->CheckOk;
384 stopScanDataThread();
387 return end;
390 // ****************************************************************************
391 void CPatchManager::stopScanDataThread()
393 if(ScanDataThread && thread)
395 thread->wait();
396 delete thread;
397 thread = NULL;
398 delete ScanDataThread;
399 ScanDataThread = NULL;
403 // ***************************************************************************
404 void CPatchManager::askForStopScanDataThread()
406 if(!ScanDataThread)
407 return;
409 ScanDataThread->AskForCancel= true;
412 // ***************************************************************************
413 bool CPatchManager::getDataScanLog(ucstring &text)
415 text.clear();
416 bool changed= false;
418 TSyncDataScanState::CAccessor ac(&DataScanState);
419 CDataScanState &val= ac.value();
420 changed= val.Changed;
421 // if changed, build the log
422 if(changed)
424 for(uint i=0;i<val.FilesWithScanDataError.size();i++)
426 ucstring str;
427 getCorruptedFileInfo(val.FilesWithScanDataError[i], str);
428 text+= str + "\n";
431 // then reset
432 val.Changed= false;
435 return changed;
438 // ***************************************************************************
439 void CPatchManager::addDataScanLogCorruptedFile(const SFileToPatch &ftp)
442 TSyncDataScanState::CAccessor ac(&DataScanState);
443 CDataScanState &val= ac.value();
444 val.FilesWithScanDataError.push_back(ftp);
445 val.Changed= true;
449 // ***************************************************************************
450 void CPatchManager::clearDataScanLog()
453 TSyncDataScanState::CAccessor ac(&DataScanState);
454 CDataScanState &val= ac.value();
455 val.FilesWithScanDataError.clear();
456 val.Changed= true;
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 // ****************************************************************************
471 // CScanDataThread
472 // ****************************************************************************
473 // ****************************************************************************
474 // ****************************************************************************
476 // ****************************************************************************
477 CScanDataThread::CScanDataThread()
479 AskForCancel= false;
480 Ended = false;
481 CheckOk = false;
482 TotalFileToScan = 1;
483 CurrentFileScanned = 1;
486 // ****************************************************************************
487 void CScanDataThread::run ()
489 CPatchManager *pPM = CPatchManager::getInstance();
492 uint32 i;
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
520 if(AskForCancel)
521 break;
524 sTranslate = dummyI18N("Checking file ended with no error");
525 pPM->setState(true, sTranslate);
526 CheckOk = true;
527 Ended = true;
529 catch (Exception &e)
531 ucstring sTranslate = dummyI18N("Checking file ended with errors :") + " " + e.what();
532 pPM->setState(true, sTranslate);
533 CheckOk = false;
534 Ended = true;