1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2020 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014 Matthew LAGOE (Botanic) <cyberempires@gmail.com>
6 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
34 #include "app_bundle_utils.h"
43 #include <curl/curl.h>
48 #include "nel/misc/debug.h"
49 #include "nel/misc/path.h"
50 #include "nel/misc/thread.h"
51 #include "nel/misc/sha1.h"
52 #include "nel/misc/big_file.h"
53 #include "nel/misc/i18n.h"
54 #include "nel/misc/cmd_args.h"
55 #include "nel/misc/seven_zip.h"
56 #include "nel/web/curl_certificates.h"
58 #include "game_share/bg_downloader_msg.h"
60 #include "login_patch.h"
62 #include "user_agent.h"
65 #ifndef RY_BG_DOWNLOADER
66 #include "client_cfg.h"
68 #include "../client_background_downloader/client_background_downloader.h"
69 #define __CLIENT_INSTALL_EXE__
82 using namespace NLMISC
;
85 extern string VersionName
;
86 extern string R2ServerVersion
;
88 #ifdef __CLIENT_INSTALL_EXE__
89 extern std::string TheTmpInstallDirectory
;
90 extern std::string ClientLauncherUrl
;
92 std::string TheTmpInstallDirectory
= "patch/client_install";
95 extern NLMISC::CCmdArgs Args
;
97 // ****************************************************************************
98 // ****************************************************************************
99 // ****************************************************************************
101 // ****************************************************************************
102 // ****************************************************************************
103 // ****************************************************************************
105 struct EPatchDownloadException
: public Exception
107 EPatchDownloadException() : Exception( "Download Error" ) {}
108 EPatchDownloadException( const std::string
& str
) : Exception( str
) {}
109 virtual ~EPatchDownloadException() throw(){}
113 CPatchManager
*CPatchManager::_Instance
= NULL
;
115 static std::string ClientRootPath
;
117 // ****************************************************************************
118 CPatchManager::CPatchManager() : State("t_state"), DataScanState("t_data_scan_state")
120 DescFilename
= "ryzom_xxxxx.idx";
123 UpdateBatchFilename
= "updt_nl.bat";
124 UpgradeBatchFilename
= "upgd_nl.bat";
126 UpdateBatchFilename
= "updt_nl.sh";
127 UpgradeBatchFilename
= "upgd_nl.sh";
130 std::string rootPath
;
132 if (ClientCfg
.getDefaultConfigLocation(rootPath
))
134 // use same directory as client_default.cfg
135 rootPath
= CFile::getPath(rootPath
);
139 // use current directory
140 rootPath
= CPath::getCurrentPath();
143 setClientRootPath(rootPath
);
149 InstallThread
= NULL
;
150 ScanDataThread
= NULL
;
151 DownloadThread
= NULL
;
155 ValidDescFile
= false;
157 MustLaunchBatFile
= false;
159 DownloadInProgress
= false;
160 _AsyncDownloader
= NULL
;
161 _StateListener
= NULL
;
162 _StartRyzomAtEnd
= true;
165 // ****************************************************************************
166 void CPatchManager::setClientRootPath(const std::string
& clientRootPath
)
168 ClientRootPath
= CPath::standardizePath(clientRootPath
);
169 ClientPatchPath
= CPath::standardizePath(ClientRootPath
+ "unpack");
171 // Delete the .sh file because it's not useful anymore
172 std::string fullUpdateBatchFilename
= ClientRootPath
+ UpdateBatchFilename
;
174 if (NLMISC::CFile::fileExists(fullUpdateBatchFilename
))
175 NLMISC::CFile::deleteFile(fullUpdateBatchFilename
);
177 WritableClientDataPath
= CPath::standardizePath(ClientRootPath
+ "data");
180 ReadableClientDataPath
= CPath::standardizePath(getAppBundlePath() + "/Contents/Resources/data");
181 #elif defined(NL_OS_UNIX)
182 ReadableClientDataPath
= CPath::standardizePath(getRyzomSharePrefix() + "/data");
183 if (CFile::isDirectory(ReadableClientDataPath
)) ReadableClientDataPath
.clear();
185 ReadableClientDataPath
.clear();
188 if (ReadableClientDataPath
.empty()) ReadableClientDataPath
= WritableClientDataPath
;
191 // ****************************************************************************
192 void CPatchManager::setErrorMessage(const std::string
&message
)
194 _ErrorMessage
= message
;
197 // ****************************************************************************
198 void CPatchManager::forceStopCheckThread()
200 PatchThread
->StopAsked
= true;
203 // ****************************************************************************
204 void CPatchManager::forceStopPatchThread()
206 CheckThread
->StopAsked
= true;
209 // ****************************************************************************
210 void CPatchManager::init(const std::vector
<std::string
>& patchURIs
, const std::string
&sServerPath
, const std::string
&sServerVersion
)
213 PatchServers
.clear();
215 for (i
=0; i
<patchURIs
.size(); ++i
)
217 PatchServers
.push_back(CPatchServer(patchURIs
[i
]));
220 srand(NLMISC::CTime::getSecondsSince1970());
221 UsedServer
= (sint
)(((double)rand() / ((double)RAND_MAX
+1.0)) * (double)PatchServers
.size());
223 ServerPath
= CPath::standardizePath (sServerPath
);
224 ServerVersion
= sServerVersion
;
226 string::size_type pos
= ServerPath
.find ("@");
227 if (pos
!= string::npos
)
228 DisplayedServerPath
= "http://" + ServerPath
.substr (pos
+1);
230 DisplayedServerPath
= ServerPath
;
232 NLMISC::CFile::createDirectory(ClientPatchPath
);
233 NLMISC::CFile::createDirectory(WritableClientDataPath
);
236 // try to read the version file from the server (that will replace the version number)
241 #ifdef RY_BG_DOWNLOADER
242 cf
= &theApp
.ConfigFile
;
244 cf
= &ClientCfg
.ConfigFile
;
247 // App name matches Domain on the SQL server
248 std::string appName
= cf
->getVarPtr("Application")
249 ? cf
->getVar("Application").asString(0)
252 std::string versionFileName
= appName
+ ".version";
253 getServerFile(versionFileName
);
255 // ok, we have the file, extract version number (aka build number) and the
256 // version name if present
258 CIFile
versionFile(ClientPatchPath
+ versionFileName
);
260 versionFile
.getline(buffer
, 1024);
261 CSString
line(buffer
);
264 CConfigFile::CVar
*forceVersion
= cf
->getVarPtr("ForceVersion");
266 if (forceVersion
!= NULL
)
268 line
= forceVersion
->asString();
272 // Use the version specified in this file, if the file does not contain an asterisk
275 ServerVersion
= line
.firstWord(true);
276 VersionName
= line
.firstWord(true);
279 // force the R2ServerVersion
280 R2ServerVersion
= ServerVersion
;
282 #ifdef __CLIENT_INSTALL_EXE__
284 //The install program load a the url of the mini web site in the patch directory
286 std::string clientLauncherUrl
= "client_launcher_url.txt";
290 uint32 nServerVersion
;
291 fromString(ServerVersion
, nServerVersion
);
292 std::string url
= toString("%05u/%s", nServerVersion
, clientLauncherUrl
.c_str());
293 // The client version is different from the server version : download new description file
294 getServerFile(url
.c_str(), false); // For the moment description file is not zipped
298 // fallback to patch root directory
301 getServerFile(clientLauncherUrl
.c_str(), false); // For the moment description file is not zipped
312 if (versionFile
.open(ClientPatchPath
+clientLauncherUrl
) )
315 versionFile
.getline(buffer
, 1024);
316 ClientLauncherUrl
= std::string(buffer
);
328 // retrieve the current client version, according to .idx
329 readClientVersionAndDescFile();
332 // ***************************************************************************
333 void CPatchManager::readClientVersionAndDescFile()
337 ValidDescFile
= false;
338 vector
<string
> vFiles
;
339 CPath::getPathContent(ClientPatchPath
, false, false, true, vFiles
);
340 uint32 nVersion
= 0xFFFFFFFF;
342 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
344 string sName
= NLMISC::CFile::getFilename(vFiles
[i
]);
345 string sExt
= NLMISC::CFile::getExtension(sName
);
346 string sBase
= sName
.substr(0, sName
.rfind('_'));
347 if ((sExt
== "idx") && (sBase
== "ryzom"))
349 string val
= sName
.substr(sName
.rfind('_')+1, 5);
350 if (fromString(val
, nNewVersion
) && ((nNewVersion
> nVersion
) || (nVersion
== 0xFFFFFFFF)))
351 nVersion
= nNewVersion
;
354 if (nVersion
!= 0xFFFFFFFF)
355 readDescFile(nVersion
);
357 DescFilename
= "unknown";
358 ValidDescFile
= true;
360 catch(const Exception
&)
362 nlwarning("EXCEPTION CATCH: readClientVersionAndDescFile() failed - not important");
363 // Not important that there is no desc file
367 // ****************************************************************************
368 void CPatchManager::startCheckThread(bool includeBackgroundPatch
)
370 if (CheckThread
!= NULL
)
372 nlwarning ("check thread is already running");
377 nlwarning ("a thread is already running");
381 _ErrorMessage
.clear();
383 CheckThread
= new CCheckThread(includeBackgroundPatch
);
384 nlassert (CheckThread
!= NULL
);
386 Thread
= IThread::create (CheckThread
);
387 nlassert (Thread
!= NULL
);
391 // ****************************************************************************
392 bool CPatchManager::isCheckThreadEnded(bool &ok
)
394 if (CheckThread
== NULL
)
400 bool end
= CheckThread
->Ended
;
403 ok
= CheckThread
->CheckOk
;
410 // ****************************************************************************
411 void CPatchManager::stopCheckThread()
413 if(CheckThread
&& Thread
)
423 // Return the position of the inserted/found category
424 sint32
updateCat(vector
<CPatchManager::SPatchInfo::SCat
> &rOutVec
, CPatchManager::SPatchInfo::SCat
&rInCat
)
427 for (i
= 0; i
< rOutVec
.size(); ++i
)
429 if (rOutVec
[i
].Name
== rInCat
.Name
)
433 if (i
== rOutVec
.size())
435 rOutVec
.push_back(rInCat
);
439 rOutVec
[i
].Size
+= rInCat
.Size
;
440 rOutVec
[i
].FinalFileSize
+= rInCat
.FinalFileSize
;
441 rOutVec
[i
].SZipSize
+= rInCat
.SZipSize
;
447 // ****************************************************************************
448 void CPatchManager::getInfoToDisp(SPatchInfo
&piOut
)
450 piOut
.NonOptCat
.clear();
451 piOut
.OptCat
.clear();
452 piOut
.ReqCat
.clear();
454 // Convert FilesToPatch vector that must be initialized into something human readable
456 for (i
= 0; i
< FilesToPatch
.size(); ++i
)
458 SFileToPatch
&rFTP
= FilesToPatch
[i
];
459 // Find the category of the file
461 const CBNPCategorySet
&rAllCats
= DescFile
.getCategories();
462 for (uint32 j
= 0; j
< rAllCats
.categoryCount(); ++j
)
464 const CBNPCategory
&rCat
= rAllCats
.getCategory(j
);
465 for (uint32 k
= 0; k
< rCat
.fileCount(); ++k
)
467 if (rCat
.getFile(k
) == rFTP
.FileName
)
480 // Add the category found, if already there just update the size
481 const CBNPCategory
&rCat
= rAllCats
.getCategory(nCat
);
482 string sCatName
= rCat
.getName();
483 // Size of all patches
484 uint32 nTotalPatchesSize
= 0;
485 for (uint32 j
= 0; j
< rFTP
.PatcheSizes
.size(); ++j
)
486 nTotalPatchesSize
+= rFTP
.PatcheSizes
[j
];
490 c
.Size
= nTotalPatchesSize
;
491 c
.SZipSize
= rFTP
.SZFileSize
;
492 c
.FinalFileSize
= rFTP
.FinalFileSize
;
493 if (!rCat
.getCatRequired().empty())
495 // Ensure the required cat exists
497 rc
.Name
= rCat
.getCatRequired();
498 c
.Req
= updateCat(piOut
.ReqCat
, rc
);
501 // In which category of category should we add it ?
502 if (rCat
.isOptional())
505 updateCat(piOut
.ReqCat
, c
);
507 updateCat(piOut
.OptCat
, c
);
511 updateCat(piOut
.NonOptCat
, c
);
516 for (i
= 0; i
< OptionalCat
.size(); ++i
)
518 const CBNPCategory
*pCat
= DescFile
.getCategories().getCategory(OptionalCat
[i
]);
519 nlassert(pCat
!= NULL
);
522 c
.Name
= pCat
->getName();
524 if (!pCat
->getCatRequired().empty())
526 // Ensure the required cat exists
528 rc
.Name
= pCat
->getCatRequired();
529 c
.Req
= updateCat(piOut
.ReqCat
, rc
);
532 updateCat(piOut
.OptCat
, c
);
536 void stopSoundMngr();
538 // ****************************************************************************
539 // TODO : use selected categories to patch a list of files
540 void CPatchManager::startPatchThread(const vector
<string
> &CategoriesSelected
, bool applyPatch
)
542 if (PatchThread
!= NULL
)
544 nlwarning ("check thread is already running");
549 nlwarning ("a thread is already running");
553 _ErrorMessage
.clear();
555 PatchThread
= new CPatchThread(applyPatch
);
556 nlassert (PatchThread
!= NULL
);
558 // Select all the files we have to patch depending on non-optional categories and selected categories
560 // Add non-optional categories
561 vector
<string
> CatsSelected
= CategoriesSelected
;
562 PatchThread
->clear();
563 const CBNPCategorySet
&rAllCats
= DescFile
.getCategories();
564 for (i
= 0; i
< rAllCats
.categoryCount(); ++i
)
566 const CBNPCategory
&rCat
= rAllCats
.getCategory(i
);
567 if (!rCat
.isOptional())
568 CatsSelected
.push_back(rCat
.getName());
571 // Add all required categories
572 uint32 nSize
= (uint32
)CatsSelected
.size();
575 nSize
= (uint32
)CatsSelected
.size();
577 for (i
= 0; i
< CatsSelected
.size(); ++i
)
579 const CBNPCategory
*pCat
= rAllCats
.getCategory(CatsSelected
[i
]);
580 if (pCat
== NULL
) continue;
581 if (pCat
->getCatRequired().empty()) continue;
582 // Check if the category required is already present
583 for (j
= 0; j
< CatsSelected
.size(); ++j
)
585 const CBNPCategory
*pCat2
= rAllCats
.getCategory(CatsSelected
[j
]);
586 if (pCat2
->getName() == pCat
->getCatRequired())
590 if (j
== CatsSelected
.size())
592 CatsSelected
.push_back(pCat
->getCatRequired());
597 while(nSize
!= CatsSelected
.size());
600 for (i
= 0; i
< CatsSelected
.size(); ++i
)
602 // Find the category from the name
603 const CBNPCategory
*pCat
= rAllCats
.getCategory(CatsSelected
[i
]);
606 for (j
= 0; j
< pCat
->fileCount(); ++j
)
608 const string
&rFilename
= pCat
->getFile(j
);
609 const CBNPFileSet
&rFileSet
= DescFile
.getFiles();
610 const CBNPFile
*pFile
= rFileSet
.getFileByName(rFilename
);
613 // Look if it's a file to patch
614 for (k
= 0; k
< FilesToPatch
.size(); ++k
)
615 if (FilesToPatch
[k
].FileName
== pFile
->getFileName())
618 if (k
< FilesToPatch
.size())
620 FilesToPatch
[k
].Incremental
= pCat
->isIncremental();
621 if (!pCat
->getUnpackTo().empty())
622 FilesToPatch
[k
].ExtractPath
= CPath::standardizePath(pCat
->getUnpackTo());
624 PatchThread
->add(FilesToPatch
[k
]);
626 // Close opened big files
627 CBigFile::getInstance().remove(FilesToPatch
[k
].FileName
);
629 if (NLMISC::startsWith(FilesToPatch
[k
].FileName
, "sound"))
631 // Stop sound playback
641 Thread
= IThread::create (PatchThread
);
642 nlassert (Thread
!= NULL
);
646 // ****************************************************************************
647 bool CPatchManager::isPatchThreadEnded (bool &ok
)
649 if (PatchThread
== NULL
)
655 bool end
= PatchThread
->Ended
;
658 ok
= PatchThread
->PatchOk
;
665 // ****************************************************************************
666 // Called in main thread
667 bool CPatchManager::getThreadState (std::string
&stateOut
, vector
<string
> &stateLogOut
)
669 if ((PatchThread
== NULL
) && (CheckThread
== NULL
) && (ScanDataThread
==NULL
))
676 // Get access to the state
679 CSynchronized
<CState
>::CAccessor
as(&State
);
680 CState
&rState
= as
.value();
681 if (rState
.StateChanged
)
685 stateOut
= rState
.State
;
686 stateLogOut
= rState
.StateLog
;
688 rState
.StateLog
.clear();
689 rState
.StateChanged
= false;
694 if (isVerboseLog() && !stateLogOut
.empty())
695 for (uint32 i
= 0; i
< stateLogOut
.size(); ++i
)
696 nlinfo("%s", stateLogOut
[i
].c_str());
701 // ****************************************************************************
702 void CPatchManager::stopPatchThread()
704 if(PatchThread
&& Thread
)
714 // ****************************************************************************
715 void CPatchManager::deleteBatchFile()
717 deleteFile(ClientRootPath
+ UpdateBatchFilename
, false, false);
720 // ****************************************************************************
721 void CPatchManager::createBatchFile(CProductDescriptionForClient
&descFile
, bool wantRyzomRestart
, bool useBatchFile
)
727 // Unpack files with category ExtractPath non empty
728 const CBNPCategorySet
&rDescCats
= descFile
.getCategories();
731 for (uint32 i
= 0; i
< rDescCats
.categoryCount(); ++i
)
733 // For all optional categories check if there is a 'file to patch' in it
734 const CBNPCategory
&rCat
= rDescCats
.getCategory(i
);
736 nlinfo("Category = %s", rCat
.getName().c_str());
738 if (!rCat
.getUnpackTo().empty())
739 for (uint32 j
= 0; j
< rCat
.fileCount(); ++j
)
741 string rFilename
= ClientPatchPath
+ rCat
.getFile(j
);
743 nlinfo("\tFileName = %s", rFilename
.c_str());
746 vector
<string
> vFilenames
;
752 result
= bnpUnpack(rFilename
, ClientPatchPath
, vFilenames
);
761 // TODO: handle exception?
762 string err
= toString("Error unpacking %s", rFilename
.c_str());
764 throw Exception (err
);
768 for (uint32 fff
= 0; fff
< vFilenames
.size (); fff
++)
770 // this file must be moved
771 string fullDstPath
= CPath::standardizePath(rCat
.getUnpackTo()); // to be sure there is a / at the end
772 NLMISC::CFile::createDirectoryTree(fullDstPath
);
774 std::string FileName
= vFilenames
[fff
];
776 bool succeeded
= false;
780 // don't check result, because it's possible the olk file doesn't exist
781 CFile::deleteFile(fullDstPath
+ FileName
);
783 // try to move it, if fails move it later in a script
784 if (CFile::moveFile(fullDstPath
+ FileName
, ClientPatchPath
+ FileName
))
788 // if we didn't succeed to delete or move the file, create a batch file anyway
791 string batchRelativeDstPath
;
793 // should be always true
794 if (fullDstPath
.compare(0, ClientRootPath
.length(), ClientRootPath
) == 0)
796 batchRelativeDstPath
= fullDstPath
.substr(ClientRootPath
.length()) + FileName
;
800 batchRelativeDstPath
= fullDstPath
+ FileName
;
804 // only fix backslashes for .bat
805 batchRelativeDstPath
= CPath::standardizeDosPath(batchRelativeDstPath
);
807 // use DSTPATH and SRCPATH variables and append filenames
808 string realDstPath
= toString("\"%%ROOTPATH%%\\%s\"", batchRelativeDstPath
.c_str());
809 string realSrcPath
= toString("\"%%UNPACKPATH%%\\%s\"", FileName
.c_str());
811 content
+= toString(":loop%u\n", nblab
);
812 content
+= toString("attrib -r -a -s -h %s\n", realDstPath
.c_str());
813 content
+= toString("del %s\n", realDstPath
.c_str());
814 content
+= toString("if exist %s goto loop%u\n", realDstPath
.c_str(), nblab
);
815 content
+= toString("move %s %s\n", realSrcPath
.c_str(), realDstPath
.c_str());
817 // use DSTPATH and SRCPATH variables and append filenames
818 string realDstPath
= toString("\"$ROOTPATH/%s\"", batchRelativeDstPath
.c_str());
819 string realSrcPath
= toString("\"$UNPACKPATH/%s\"", FileName
.c_str());
821 content
+= toString("rm -rf %s\n", realDstPath
.c_str());
822 content
+= toString("mv %s %s\n", realSrcPath
.c_str(), realDstPath
.c_str());
834 std::string patchDirectory
= CPath::standardizePath(ClientRootPath
+ "patch");
836 // Finalize batch file
837 if (NLMISC::CFile::isExists(patchDirectory
) && NLMISC::CFile::isDirectory(patchDirectory
))
839 std::string patchContent
;
841 vector
<string
> vFileList
;
842 CPath::getPathContent (patchDirectory
, false, false, true, vFileList
, NULL
, false);
844 for(uint32 i
= 0; i
< vFileList
.size(); ++i
)
846 bool succeeded
= false;
850 if (CFile::deleteFile(vFileList
[i
]))
854 // if we didn't succeed to delete, create a batch file anyway
858 patchContent
+= toString("del \"%%ROOTPATH%%\\patch\\%s\"\n", vFileList
[i
].c_str());
860 patchContent
+= toString("rm -f \"$ROOTPATH/patch/%s\"\n", vFileList
[i
].c_str());
865 if (!patchContent
.empty())
868 content
+= ":looppatch\n";
870 content
+= patchContent
;
872 content
+= "rd /Q /S \"%%ROOTPATH%%\\patch\"\n";
873 content
+= "if exist \"%%ROOTPATH%%\\patch\" goto looppatch\n";
875 content
+= "rm -rf \"$ROOTPATH/patch\"\n";
880 CFile::deleteDirectory(patchDirectory
);
884 if (!content
.empty())
889 std::string batchFilename
= ClientRootPath
+ UpdateBatchFilename
;
891 // write windows .bat format else write sh format
892 FILE *fp
= nlfopen (batchFilename
, "wt");
896 string err
= toString("Can't open file '%s' for writing: code=%d %s (error code 29)", batchFilename
.c_str(), errno
, strerror(errno
));
897 throw Exception (err
);
901 nlinfo("Creating %s...", batchFilename
.c_str());
904 string contentPrefix
;
906 //use bat if windows if not use sh
908 contentPrefix
+= "@echo off\n";
909 contentPrefix
+= "set RYZOM_CLIENT=%~1\n";
910 contentPrefix
+= "set UNPACKPATH=%~2\n";
911 contentPrefix
+= "set ROOTPATH=%~3\n";
912 contentPrefix
+= "set STARTUPPATH=%~4\n";
913 contentPrefix
+= toString("set UPGRADE_FILE=%%ROOTPATH%%\\%s\n", UpgradeBatchFilename
.c_str());
914 contentPrefix
+= "\n";
915 contentPrefix
+= "set LOGIN=%~5\n";
916 contentPrefix
+= "set PASSWORD=%~6\n";
917 contentPrefix
+= "set SHARDID=%~7\n";
919 contentPrefix
+= "#!/bin/sh\n";
920 contentPrefix
+= "export RYZOM_CLIENT=\"$1\"\n";
921 contentPrefix
+= "export UNPACKPATH=\"$2\"\n";
922 contentPrefix
+= "export ROOTPATH=\"$3\"\n";
923 contentPrefix
+= "export STARTUPPATH=\"$4\"\n";
924 contentPrefix
+= toString("export UPGRADE_FILE=$ROOTPATH/%s\n", UpgradeBatchFilename
.c_str());
925 contentPrefix
+= "\n";
926 contentPrefix
+= "LOGIN=\"$5\"\n";
927 contentPrefix
+= "PASSWORD=\"$6\"\n";
928 contentPrefix
+= "SHARDID=\"$7\"\n";
931 contentPrefix
+= "\n";
933 string contentSuffix
;
935 // if we need to restart Ryzom, we need to launch it in batch
936 std::string additionalParams
;
938 if (Args
.haveLongArg("profile"))
940 additionalParams
= "--profile " + Args
.getLongArg("profile").front();
944 // launch upgrade script if present (it'll execute additional steps like moving or deleting files)
945 contentSuffix
+= "if exist \"%UPGRADE_FILE%\" call \"%UPGRADE_FILE%\"\n";
947 if (wantRyzomRestart
)
949 // client shouldn't be in memory anymore else it couldn't be overwritten
950 contentSuffix
+= toString("start \"\" /D \"%%STARTUPPATH%%\" \"%%RYZOM_CLIENT%%\" %s \"%%LOGIN%%\" \"%%PASSWORD%%\" \"%%SHARDID%%\"\n", additionalParams
.c_str());
953 if (wantRyzomRestart
)
955 // wait until client not in memory anymore
956 contentSuffix
+= toString("until ! pgrep -x \"%s\" > /dev/null; do sleep 1; done\n", CFile::getFilename(RyzomFilename
).c_str());
959 // launch upgrade script if present (it'll execute additional steps like moving or deleting files)
960 contentSuffix
+= "if [ -e \"$UPGRADE_FILE\" ]; then chmod +x \"$UPGRADE_FILE\" && \"$UPGRADE_FILE\"; fi\n\n";
962 // be sure file is executable
963 contentSuffix
+= "chmod +x \"$RYZOM_CLIENT\"\n\n";
965 if (wantRyzomRestart
)
967 // change to previous client directory
968 contentSuffix
+= "cd \"$STARTUPPATH\"\n\n";
972 // use exec command under OS X
973 contentSuffix
+= toString("exec \"$RYZOM_CLIENT\" %s \"$LOGIN\" \"$PASSWORD\" \"$SHARDID\"\n", additionalParams
.c_str());
975 contentSuffix
+= toString("\"$RYZOM_CLIENT\" %s \"$LOGIN\" \"$PASSWORD\" \"$SHARDID\" &\n", additionalParams
.c_str());
980 // append content of script
981 fputs(contentPrefix
.c_str(), fp
);
982 fputs(content
.c_str(), fp
);
983 fputs(contentSuffix
.c_str(), fp
);
985 bool writeError
= ferror(fp
) != 0;
986 bool diskFull
= ferror(fp
) && errno
== 28 /* ENOSPC */;
990 throw NLMISC::EDiskFullError(batchFilename
.c_str());
994 throw NLMISC::EWriteError(batchFilename
.c_str());
999 // ****************************************************************************
1000 void CPatchManager::executeBatchFile()
1003 extern void quitCrashReport ();
1006 bool r2Mode
= false;
1008 #ifndef RY_BG_DOWNLOADER
1009 r2Mode
= ClientCfg
.R2Mode
;
1012 std::string batchFilename
;
1014 std::vector
<std::string
> arguments
;
1016 std::string startupPath
= Args
.getStartupPath();
1018 // 3 first parameters are Ryzom client full path, patch directory full path and client root directory full path
1019 #ifdef NL_OS_WINDOWS
1020 batchFilename
= CPath::standardizeDosPath(ClientRootPath
);
1022 arguments
.push_back(CPath::standardizeDosPath(RyzomFilename
));
1023 arguments
.push_back(CPath::standardizeDosPath(ClientPatchPath
));
1024 arguments
.push_back(CPath::standardizeDosPath(ClientRootPath
));
1025 arguments
.push_back(CPath::standardizeDosPath(startupPath
));
1027 batchFilename
= ClientRootPath
;
1029 arguments
.push_back(RyzomFilename
);
1030 arguments
.push_back(ClientPatchPath
);
1031 arguments
.push_back(ClientRootPath
);
1032 arguments
.push_back(startupPath
);
1035 // log parameters passed to Ryzom client
1036 nlinfo("Restarting Ryzom...");
1037 nlinfo("RyzomFilename = %s", RyzomFilename
.c_str());
1038 nlinfo("ClientPatchPath = %s", ClientPatchPath
.c_str());
1039 nlinfo("ClientRootPath = %s", ClientRootPath
.c_str());
1040 nlinfo("StartupPath = %s", startupPath
.c_str());
1042 batchFilename
+= UpdateBatchFilename
;
1044 // make script executable
1045 CFile::setRWAccess(batchFilename
);
1046 CFile::setExecutable(batchFilename
);
1048 // append login, password and shard
1049 if (!LoginLogin
.empty())
1051 arguments
.push_back(LoginLogin
);
1053 if (!LoginPassword
.empty())
1055 // encode password in hexadecimal to avoid invalid characters on command-line
1056 arguments
.push_back("0x" + toHexa(LoginPassword
));
1060 arguments
.push_back(toString(LoginShardId
));
1065 // launchProgram with array of strings as argument will escape arguments with spaces
1066 if (!launchProgramArray(batchFilename
, arguments
, false))
1068 // error occurs during the launch
1069 string str
= toString("Can't execute '%s': code=%d %s (error code 30)", batchFilename
.c_str(), errno
, strerror(errno
));
1070 throw Exception (str
);
1074 // ****************************************************************************
1075 void CPatchManager::reboot()
1077 onFileInstallFinished(); //In install program wait for the player to select in the gui of the install program if we want to Launch Ryzom or not
1078 createBatchFile(DescFile
, _StartRyzomAtEnd
);
1083 // ****************************************************************************
1084 int CPatchManager::getTotalFilesToGet()
1086 if (CheckThread
!= NULL
)
1087 return CheckThread
->TotalFileToCheck
;
1089 if (PatchThread
!= NULL
)
1090 return PatchThread
->getNbFileToPatch();
1092 if (ScanDataThread
!= NULL
)
1093 return ScanDataThread
->TotalFileToScan
;
1098 // ****************************************************************************
1099 int CPatchManager::getCurrentFilesToGet()
1101 if (CheckThread
!= NULL
)
1102 return CheckThread
->CurrentFileChecked
;
1104 if (PatchThread
!= NULL
)
1105 return PatchThread
->getCurrentFilePatched();
1107 if (ScanDataThread
!= NULL
)
1108 return ScanDataThread
->CurrentFileScanned
;
1113 // ****************************************************************************
1114 int CPatchManager::getPatchingSize()
1116 if (PatchThread
!= NULL
)
1117 return PatchThread
->getPatchingSize();
1121 // ****************************************************************************
1122 float CPatchManager::getCurrentFileProgress() const
1124 if (PatchThread
!= NULL
)
1125 return PatchThread
->getCurrentFileProgress();
1129 // ****************************************************************************
1130 void CPatchManager::setRWAccess (const string
&filename
, bool bThrowException
)
1132 string s
= CI18N::get("uiSetAttrib") + " " + CFile::getFilename(filename
);
1135 if (!NLMISC::CFile::setRWAccess(filename
) && bThrowException
)
1137 s
= CI18N::get("uiAttribErr") + " " + CFile::getFilename(filename
) + " (" + toString(errno
) + "," + strerror(errno
) + ")";
1139 throw Exception (s
);
1143 // ****************************************************************************
1144 string
CPatchManager::deleteFile (const string
&filename
, bool bThrowException
, bool bWarning
)
1146 string s
= CI18N::get("uiDelFile") + " " + CFile::getFilename(filename
);
1149 if (!NLMISC::CFile::fileExists(filename
))
1151 s
= CI18N::get("uiDelNoFile");
1156 if (!NLMISC::CFile::deleteFile(filename
))
1158 s
= CI18N::get("uiDelErr") + " " + CFile::getFilename(filename
) + " (" + toString(errno
) + "," + strerror(errno
) + ")";
1162 throw Exception (s
);
1168 // ****************************************************************************
1169 void CPatchManager::renameFile (const string
&src
, const string
&dst
)
1171 string s
= CI18N::get("uiRenameFile") + " " + NLMISC::CFile::getFilename(src
);
1174 if (!NLMISC::CFile::moveFile(dst
, src
))
1176 s
= CI18N::get("uiRenameErr") + " " + src
+ " -> " + dst
+ " (" + toString(errno
) + "," + strerror(errno
) + ")";
1178 throw Exception (s
);
1182 // ****************************************************************************
1183 // Take care this function is called by the thread
1184 void CPatchManager::setState (bool bOutputToLog
, const string
&ucsNewState
)
1187 CSynchronized
<CState
>::CAccessor
as(&State
);
1188 CState
&rState
= as
.value();
1189 rState
.State
= ucsNewState
;
1191 rState
.StateLog
.push_back(ucsNewState
);
1192 rState
.StateChanged
= true;
1196 _StateListener
->setState(bOutputToLog
, ucsNewState
);
1200 // ****************************************************************************
1201 void CPatchManager::touchState ()
1204 CSynchronized
<CState
>::CAccessor
as(&State
);
1205 as
.value().StateChanged
= true;
1209 // ****************************************************************************
1210 string
CPatchManager::getClientVersion()
1215 return toString(DescFile
.getFiles().getVersionNumber());
1218 // ****************************************************************************
1219 void CPatchManager::readDescFile(sint32 nVersion
)
1221 DescFilename
= toString("ryzom_%05d.idx", nVersion
);
1222 string srcName
= ClientPatchPath
+ DescFilename
;
1224 if (!DescFile
.load(srcName
))
1225 throw Exception ("Can't open file '%s'", srcName
.c_str ());
1229 if (ClientRootPath
!= "./")
1231 // fix relative paths
1232 for (cat
= 0; cat
< DescFile
.getCategories().categoryCount(); ++cat
)
1234 CBNPCategory
&category
= const_cast<CBNPCategory
&>(DescFile
.getCategories().getCategory(cat
));
1236 std::string unpackTo
= category
.getUnpackTo();
1238 if (unpackTo
.substr(0, 1) == ".")
1240 unpackTo
= CPath::makePathAbsolute(unpackTo
, ClientRootPath
, true);
1241 category
.setUnpackTo(unpackTo
);
1246 // patch category for current platform
1247 std::string platformPatchCategory
;
1249 #if defined(NL_OS_WIN64)
1250 platformPatchCategory
= "main_exedll_win64";
1251 #elif defined(NL_OS_WINDOWS)
1252 platformPatchCategory
= "main_exedll_win32";
1253 #elif defined(NL_OS_MAC)
1254 platformPatchCategory
= "main_exedll_osx";
1255 #elif defined(NL_OS_UNIX) && defined(_LP64)
1256 platformPatchCategory
= "main_exedll_linux64";
1258 platformPatchCategory
= "main_exedll_linux32";
1261 // check if we are using main_exedll or specific main_exedll_* for platform
1262 bool foundPlatformPatchCategory
= false;
1264 for (cat
= 0; cat
< DescFile
.getCategories().categoryCount(); ++cat
)
1266 CBNPCategory
&category
= const_cast<CBNPCategory
&>(DescFile
.getCategories().getCategory(cat
));
1268 if (category
.getName() == platformPatchCategory
)
1270 foundPlatformPatchCategory
= true;
1275 if (foundPlatformPatchCategory
)
1277 std::set
<std::string
> forceRemovePatchCategories
;
1279 // only download binaries for current platform
1280 forceRemovePatchCategories
.insert("main_exedll");
1281 forceRemovePatchCategories
.insert("main_exedll_win32");
1282 forceRemovePatchCategories
.insert("main_exedll_win64");
1283 forceRemovePatchCategories
.insert("main_exedll_linux32");
1284 forceRemovePatchCategories
.insert("main_exedll_linux64");
1285 forceRemovePatchCategories
.insert("main_exedll_osx");
1287 // remove current platform category from remove list
1288 forceRemovePatchCategories
.erase(platformPatchCategory
);
1290 CBNPFileSet
&bnpFS
= const_cast<CBNPFileSet
&>(DescFile
.getFiles());
1292 // TODO: .ref files are expected to follow platform category naming (they are in 'main' category)
1293 std::set
<std::string
>::const_iterator it
;
1294 for(it
= forceRemovePatchCategories
.begin(); it
!= forceRemovePatchCategories
.end(); ++it
)
1296 std::string name
= *it
;
1297 std::string::size_type pos
= name
.find("_");
1298 if (pos
!= std::string::npos
)
1300 name
= name
.substr(pos
+1) + "_.ref";
1301 bnpFS
.removeFile(name
);
1305 for (cat
= 0; cat
< DescFile
.getCategories().categoryCount();)
1307 const CBNPCategory
&bnpCat
= DescFile
.getCategories().getCategory(cat
);
1309 if (std::find(forceRemovePatchCategories
.begin(), forceRemovePatchCategories
.end(),
1310 bnpCat
.getName()) != forceRemovePatchCategories
.end())
1312 for (uint file
= 0; file
< bnpCat
.fileCount(); ++file
)
1314 std::string fileName
= bnpCat
.getFile(file
);
1315 bnpFS
.removeFile(fileName
);
1317 const_cast<CBNPCategorySet
&>(DescFile
.getCategories()).deleteCategory(cat
);
1327 // ****************************************************************************
1328 void CPatchManager::getServerFile (const std::string
&name
, bool bZipped
, const std::string
& specifyDestName
, NLMISC::IProgressCallback
*progress
)
1330 string srcName
= name
;
1331 if (bZipped
) srcName
+= ".ngz";
1334 if (specifyDestName
.empty())
1336 dstName
= ClientPatchPath
+ NLMISC::CFile::getFilename(name
);
1340 dstName
= specifyDestName
;
1342 if (bZipped
) dstName
+= ".ngz";
1344 bool downloadSuccess
= false;
1346 while (!downloadSuccess
)
1348 std::string serverPath
;
1349 std::string serverDisplayPath
;
1351 if (UsedServer
>= 0 && !PatchServers
.empty())
1353 // first use main patch servers
1354 serverPath
= PatchServers
[UsedServer
].ServerPath
;
1355 serverDisplayPath
= PatchServers
[UsedServer
].DisplayedServerPath
;
1359 // else use alternative emergency patch server
1360 serverPath
= ServerPath
;
1361 serverDisplayPath
= DisplayedServerPath
;
1367 string s
= CI18N::get("uiLoginGetFile") + " " + NLMISC::CFile::getFilename(srcName
);
1371 downloadFile (serverPath
+srcName
, dstName
, progress
);
1373 downloadSuccess
= true;
1375 catch (const EPatchDownloadException
& e
)
1377 //nlwarning("EXCEPTION CATCH: getServerFile() failed - try to find an alternative: %i: %s",UsedServer,PatchServers[UsedServer].DisplayedServerPath.c_str());
1379 nlwarning("EXCEPTION CATCH: getServerFile() failed - try to find an alternative : %s", (serverPath
+srcName
).c_str());
1380 nlwarning("%i", UsedServer
);
1381 if (UsedServer
>= 0 && UsedServer
< (int) PatchServers
.size())
1383 nlwarning("%s", PatchServers
[UsedServer
].DisplayedServerPath
.c_str());
1386 // if emergency patch server, this is a real issue, rethrow exception
1389 string s
= CI18N::get("uiDLFailed");
1392 throw Exception(e
.what());
1395 string s
= CI18N::get("uiDLURIFailed") + " " + serverDisplayPath
;
1398 // this server is unavailable
1399 PatchServers
[UsedServer
].Available
= false;
1401 sint nextServer
= (UsedServer
+1) % PatchServers
.size();
1403 while (nextServer
!= UsedServer
&& !PatchServers
[nextServer
].Available
)
1404 nextServer
= (nextServer
+1) % PatchServers
.size();
1406 // scanned all servers? use alternative
1407 if (nextServer
== UsedServer
)
1409 string s
= CI18N::get("uiNoMoreURI");
1412 nlwarning("EXCEPTION CATCH: getServerFile() failed - no alternative found");
1416 UsedServer
= nextServer
;
1417 nlwarning("EXCEPTION CATCH: getServerFile() failed - trying server: %i: %s",UsedServer
,PatchServers
[UsedServer
].DisplayedServerPath
.c_str());
1424 decompressFile (dstName
);
1427 // ****************************************************************************
1428 void CPatchManager::downloadFileWithCurl (const string
&source
, const string
&dest
, NLMISC::IProgressCallback
*progress
)
1430 DownloadInProgress
= true;
1434 string s
= CI18N::get("uiDLWithCurl") + " " + CFile::getFilename(dest
);
1437 // user agent = nel_launcher
1442 string sTranslate
= CI18N::get("uiLoginGetFile") + " " + NLMISC::CFile::getFilename (source
);
1443 setState(true, sTranslate
);
1444 CurrentFile
= NLMISC::CFile::getFilename (source
);
1446 curl_global_init(CURL_GLOBAL_ALL
);
1447 curl
= curl_easy_init();
1450 // file not found, delete local file
1451 throw Exception ("curl init failed");
1454 curl_easy_setopt(curl
, CURLOPT_NOPROGRESS
, 0);
1455 curl_easy_setopt(curl
, CURLOPT_PROGRESSFUNCTION
, downloadProgressFunc
);
1456 curl_easy_setopt(curl
, CURLOPT_PROGRESSDATA
, (void *) progress
);
1457 curl_easy_setopt(curl
, CURLOPT_URL
, source
.c_str());
1458 if (source
.length() > 8 && (source
[4] == 's' || source
[4] == 'S')) // 01234 https
1460 NLWEB::CCurlCertificates::addCertificateFile("cacert.pem");
1461 NLWEB::CCurlCertificates::useCertificates(curl
);
1462 curl_easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 1L);
1463 curl_easy_setopt(curl
, CURLOPT_SSL_VERIFYHOST
, 2L);
1466 // create the local file
1467 if (NLMISC::CFile::fileExists(dest
))
1469 setRWAccess(dest
, false);
1470 NLMISC::CFile::deleteFile(dest
.c_str());
1473 FILE *fp
= nlfopen (dest
, "wb");
1477 curl_easy_setopt(curl
, CURLOPT_PROGRESSDATA
, NULL
);
1478 throw Exception ("Can't open file '%s' for writing: code=%d %s (error code 37)", dest
.c_str (), errno
, strerror(errno
));
1481 curl_easy_setopt(curl
, CURLOPT_WRITEDATA
, fp
);
1482 curl_easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, fwrite
);
1484 //CurrentFilesToGet++;
1486 res
= curl_easy_perform(curl
);
1488 curl_easy_setopt(curl
, CURLOPT_PROGRESSDATA
, NULL
);
1491 curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &r
);
1493 curl_easy_cleanup(curl
);
1495 bool diskFull
= ferror(fp
) && errno
== 28 /*ENOSPC*/;
1498 curl_global_cleanup();
1500 CurrentFile
.clear();
1504 NLMISC::CFile::deleteFile(dest
.c_str());
1505 throw NLMISC::EDiskFullError(dest
);
1508 if(CURLE_WRITE_ERROR
== res
)
1510 // file not found, delete local file
1511 NLMISC::CFile::deleteFile(dest
.c_str());
1512 throw NLMISC::EWriteError(dest
);
1515 if(CURLE_FTP_COULDNT_RETR_FILE
== res
)
1517 // file not found, delete local file
1518 NLMISC::CFile::deleteFile(dest
.c_str());
1519 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res
, r
));
1524 NLMISC::CFile::deleteFile(dest
.c_str());
1525 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res
, r
));
1530 // file not found, delete it
1531 NLMISC::CFile::deleteFile(dest
.c_str());
1532 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res
, r
));
1535 if(r
< 200 || r
>= 300)
1537 // file not found, delete it
1538 NLMISC::CFile::deleteFile(dest
.c_str());
1539 throw EPatchDownloadException (NLMISC::toString("curl download failed: (ec %d %d)", res
, r
));
1543 throw Exception("USE_CURL is not defined, no curl method");
1548 DownloadInProgress
= false;
1553 // set progress to 1
1554 validateProgress((void *) progress
, 1, 1, 0, 0);
1556 DownloadInProgress
= false;
1559 // ****************************************************************************
1560 void CPatchManager::downloadFile (const string
&source
, const string
&dest
, NLMISC::IProgressCallback
*progress
)
1562 // For the moment use only curl
1563 const string sourceLower
= toLowerAscii(source
.substr(0, 6));
1565 if (startsWith(sourceLower
, "http:")
1566 || startsWith(sourceLower
, "https:")
1567 || startsWith(sourceLower
, "ftp:")
1568 || startsWith(sourceLower
, "file:"))
1570 nldebug("Download patch file %s", source
.c_str());
1571 downloadFileWithCurl(source
, dest
, progress
);
1575 if (!NLMISC::CFile::copyFile(dest
, source
, false, progress
))
1579 throw NLMISC::EDiskFullError(dest
);
1581 throw Exception ("cannot copy file %s to %s", source
.c_str(), dest
.c_str());
1586 // ****************************************************************************
1587 // TODO : Review this uncompress routine to uncompress in a temp file before overwriting destination file
1589 void CPatchManager::decompressFile (const string
&filename
)
1591 string sTranslate
= CI18N::get("uiDecompressing") + " " + NLMISC::CFile::getFilename(filename
);
1592 setState(true, sTranslate
);
1594 //if(isVerboseLog()) nlinfo("Calling gzopen('%s','rb')", filename.c_str());
1595 gzFile gz
= gzopen (filename
.c_str (), "rb");
1598 string err
= toString("Can't open compressed file '%s' : ", filename
.c_str());
1603 const char *gzerr
= gzerror (gz
, &gzerrno
);
1604 err
+= toString("code=%d %s", gzerrno
, gzerr
);
1608 err
+= toString("code=%d %s", errno
, strerror (errno
));
1610 err
+= " (error code 31)";
1611 deleteFile (filename
);
1612 throw Exception (err
);
1615 string dest
= filename
.substr(0, filename
.size ()-4);
1616 setRWAccess(dest
, false);
1617 //if(isVerboseLog()) nlinfo("Calling nlfopen('%s','wb')", dest.c_str());
1618 FILE *fp
= nlfopen (dest
, "wb");
1621 string err
= toString("Can't open file '%s' : code=%d %s, (error code 32)", dest
.c_str(), errno
, strerror(errno
));
1624 deleteFile (filename
);
1625 throw Exception (err
);
1628 //if(isVerboseLog()) nlinfo("Entering the while loop decompression");
1630 uint32 currentSize
= 0;
1631 uint8 buffer
[10000];
1634 //if(isVerboseLog()) nlinfo("Calling gzread");
1635 int res
= gzread (gz
, buffer
, 10000);
1636 //if(isVerboseLog()) nlinfo("gzread returns %d", res);
1640 const char *gzerr
= gzerror (gz
, &gzerrno
);
1643 //deleteFile (filename);
1644 throw Exception ("Can't read compressed file '%s' (after %d bytes) : code=%d %s, (error code 33)", filename
.c_str(), currentSize
, gzerrno
, gzerr
);
1649 //if(isVerboseLog()) nlinfo("Calling fwrite for %d bytes", res);
1650 int res2
= (int)fwrite (buffer
, 1, res
, fp
);
1651 //if(isVerboseLog()) nlinfo("fwrite returns %d", res2);
1654 bool diskFull
= (errno
== 28 /* ENOSPC */);
1655 string err
= toString("Can't write file '%s' : code=%d %s (error code 34)", dest
.c_str(), errno
, strerror(errno
));
1659 deleteFile (filename
);
1662 throw NLMISC::EDiskFullError(err
);
1666 throw Exception (err
);
1671 //if(isVerboseLog()) nlinfo("Exiting the while loop decompression");
1673 //if(isVerboseLog()) nlinfo("Calling gzclose");
1675 //if(isVerboseLog()) nlinfo("Calling fclose");
1677 deleteFile(filename
);
1679 //if(isVerboseLog()) nlinfo("Exiting the decompressing file");
1682 // ****************************************************************************
1683 void CPatchManager::applyDate (const string
&sFilename
, uint32 nDate
)
1685 // change the file time
1688 setRWAccess(sFilename
, false);
1689 string s
= CI18N::get("uiChangeDate") + " " + NLMISC::CFile::getFilename(sFilename
) + " " + timestampToHumanReadable(NLMISC::CFile::getFileModificationDate (sFilename
)) +
1690 " -> " + timestampToHumanReadable(nDate
);
1693 if (!NLMISC::CFile::setFileModificationDate(sFilename
, nDate
))
1695 int err
= NLMISC::getLastError();
1696 s
= CI18N::get("uiChgDateErr") + " " + CFile::getFilename(sFilename
) + " (" + toString(err
) + ", " + formatErrorMessage(err
) + ")";
1699 s
= CI18N::get("uiNowDate") + " " + CFile::getFilename(sFilename
) + " " + timestampToHumanReadable(NLMISC::CFile::getFileModificationDate (sFilename
));
1704 // ****************************************************************************
1705 // Get all the patches that need to be applied to a file from the description of this file given by the server
1706 void CPatchManager::getPatchFromDesc(SFileToPatch
&ftpOut
, const CBNPFile
&fIn
, bool forceCheckSumTest
)
1709 const CBNPFile rFile
= fIn
;
1710 const string
&rFilename
= rFile
.getFileName();
1712 bool inPatchDir
= false;
1713 // first see if there's a version that was downloaded from the background downloader
1714 if (NLMISC::CFile::fileExists(ClientPatchPath
+ rFilename
+ ".tmp"))
1716 sFilePath
= ClientPatchPath
+ rFilename
+ ".tmp";
1717 ftpOut
.SrcFileName
= sFilePath
;
1721 bool needUnpack
= false;
1722 const CBNPCategory
*cat
= DescFile
.getCategories().getCategoryFromFile(rFilename
);
1725 needUnpack
= !cat
->getUnpackTo().empty();
1729 nlwarning("Can't find file category for %s:", rFilename
.c_str());
1732 // Does the BNP exists ?
1733 // following lines added by Sadge to ensure that the correct file is patched
1734 // Only look in data path if the file should not be unpack (otherwise it should only remains in the "unpack" directory)
1737 if (sFilePath
.empty())
1739 if (NLMISC::CFile::fileExists(WritableClientDataPath
+ rFilename
))
1741 // if file exists in writable directory, use it
1742 sFilePath
= WritableClientDataPath
+ rFilename
;
1744 else if (NLMISC::CFile::fileExists(ReadableClientDataPath
+ rFilename
))
1746 // if file exists in readable directory, use it
1747 sFilePath
= ReadableClientDataPath
+ rFilename
;
1752 if (sFilePath
.empty() && NLMISC::CFile::fileExists(ClientPatchPath
+ rFilename
))
1754 sFilePath
= ClientPatchPath
+ rFilename
;
1757 // following lines removed by Sadge to ensure that the correct file is patched
1758 // string sFilePath = CPath::lookup(rFilename, false, false);
1759 // if (sFilePath.empty())
1761 // if (NLMISC::CFile::fileExists(ClientPatchPath + rFilename))
1762 // sFilePath = ClientPatchPath + rFilename;
1765 // if file not found anywhere
1766 if (sFilePath
.empty())
1768 ftpOut
.FileName
= rFilename
;
1769 ftpOut
.LocalFileToDelete
= false;
1770 ftpOut
.LocalFileExists
= false;
1771 // It happens some time (maybe a bug) that the versionCount is 0... =>
1772 // it happens if the BNP file is empty (8 bytes)
1773 ftpOut
.FinalFileSize
= EmptyBnpFileSize
;
1774 // BNP does not exists : get all the patches version
1775 for (j
= 0; j
< rFile
.versionCount(); ++j
)
1777 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
1778 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
1779 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
1780 ftpOut
.FinalFileSize
= rFile
.getVersion(j
).getFileSize();
1781 ftpOut
.SZFileSize
= rFile
.getVersion(j
).get7ZFileSize();
1786 // The local BNP file exists : find its version
1787 uint32 nLocalSize
= NLMISC::CFile::getFileSize(sFilePath
);
1788 uint32 nLocalTime
= NLMISC::CFile::getFileModificationDate(sFilePath
);
1789 // From the couple time, size look the version of the file
1790 uint32 nVersionFound
= 0xFFFFFFFF;
1791 // If forceChecksum is wanted (slow), then don't do the test with filesize/date
1792 if(!forceCheckSumTest
)
1794 for (j
= 0; j
< rFile
.versionCount(); ++j
)
1796 const CBNPFileVersion
&rVersion
= rFile
.getVersion(j
);
1797 uint32 nServerSize
= rVersion
.getFileSize();
1798 uint32 nServerTime
= rVersion
.getTimeStamp();
1799 // Does the time and size match a version ?
1800 if ((nServerSize
== nLocalSize
) && (abs((sint32
)(nServerTime
- nLocalTime
)) <= 2) )
1802 nVersionFound
= rVersion
.getVersionNumber();
1803 // break; // ace -> get the last good version (if more than one version of the same file exists)
1808 // If the version cannot be found with size and time try with sha1
1809 if (nVersionFound
== 0xFFFFFFFF)
1811 string sTranslate
= CI18N::get("uiCheckInt") + " " + rFilename
;
1812 setState(true, sTranslate
);
1813 CHashKey hkLocalSHA1
= getSHA1(sFilePath
);
1814 for (j
= 0; j
< rFile
.versionCount(); ++j
)
1816 const CBNPFileVersion
&rVersion
= rFile
.getVersion(j
);
1817 CHashKey hkServerSHA1
= rVersion
.getHashKey();
1818 // Does the sha1 match a version ?
1819 if (hkServerSHA1
== hkLocalSHA1
)
1821 nVersionFound
= rVersion
.getVersionNumber();
1822 applyDate(sFilePath
, rVersion
.getTimeStamp());
1823 // break; // ace -> same as above
1828 // No version available found
1829 if (nVersionFound
== 0xFFFFFFFF)
1831 string sTranslate
= CI18N::get("uiNoVersionFound");
1832 setState(true, sTranslate
);
1833 // Get all patches from beginning (first patch is reference file)
1834 ftpOut
.FileName
= rFilename
;
1835 ftpOut
.LocalFileToDelete
= true;
1836 ftpOut
.LocalFileExists
= !inPatchDir
;
1837 // It happens some time (maybe a bug) that the versionCount is 0... =>
1838 // it happens if the BNP file is empty (8 bytes)
1839 ftpOut
.FinalFileSize
= EmptyBnpFileSize
;
1840 // Get all the patches version
1841 for (j
= 0; j
< rFile
.versionCount(); ++j
)
1843 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
1844 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
1845 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
1846 ftpOut
.FinalFileSize
= rFile
.getVersion(j
).getFileSize();
1847 ftpOut
.SZFileSize
= rFile
.getVersion(j
).get7ZFileSize();
1850 else // A version of the file has been found
1852 string sTranslate
= CI18N::get("uiVersionFound") + " " + toString(nVersionFound
);
1853 setState(true, sTranslate
);
1854 // Get All patches from this version !
1855 ftpOut
.FileName
= rFilename
;
1856 ftpOut
.LocalFileToDelete
= false;
1857 ftpOut
.LocalFileExists
= !inPatchDir
;
1858 // Go to the version
1859 for (j
= 0; j
< rFile
.versionCount(); ++j
)
1860 if (rFile
.getVersion(j
).getVersionNumber() == nVersionFound
)
1863 nlassert(j
!= rFile
.versionCount()); // Not normal if we cant find the version we found previously
1865 // Point on the next version
1867 // If there are newer versions
1868 if (j
!= rFile
.versionCount())
1870 // Add all version until the last one
1871 for (; j
< rFile
.versionCount(); ++j
)
1873 ftpOut
.Patches
.push_back(rFile
.getVersion(j
).getVersionNumber());
1874 ftpOut
.PatcheSizes
.push_back(rFile
.getVersion(j
).getPatchSize());
1875 ftpOut
.LastFileDate
= rFile
.getVersion(j
).getTimeStamp();
1876 ftpOut
.SZFileSize
= rFile
.getVersion(j
).get7ZFileSize();
1879 // Else this file is up to date !
1881 // For info, get its final file size
1882 ftpOut
.FinalFileSize
= rFile
.getVersion(rFile
.versionCount()-1).getFileSize();
1884 } // end of else local BNP file exists
1887 // ****************************************************************************
1888 bool CPatchManager::bnpUnpack(const string
&srcBigfile
, const string
&dstPath
, vector
<string
> &vFilenames
)
1890 nldebug("bnpUnpack: srcBigfile=%s", srcBigfile
.c_str());
1891 string SourceName
, DestPath
;
1893 // following lines added by Sadge to ensure that the correct file gets patched
1894 // if (NLMISC::CFile::fileExists(ClientDataPath + srcBigfile)) SourceName = ClientDataPath + srcBigfile;
1895 // else if (NLMISC::CFile::fileExists(srcBigfile)) SourceName = ClientDataPath + srcBigfile;
1896 SourceName
= srcBigfile
;
1898 // following lines removed by Sadge to ensure that the correct file gets patched
1899 // SourceName = CPath::lookup(srcBigfile, false, false);
1900 // if (SourceName.empty())
1901 // SourceName = ClientPatchPath + srcBigfile;
1903 if (dstPath
.empty())
1904 DestPath
= ClientRootPath
;
1906 DestPath
= CPath::standardizePath (dstPath
);
1908 string s
= CI18N::get("uiUnpack") + " " + NLMISC::CFile::getFilename(SourceName
);
1911 // Read Header of the BNP File
1912 CBigFile::BNP bnpFile
;
1913 bnpFile
.BigFileName
= SourceName
;
1915 if (!bnpFile
.readHeader())
1917 string s
= CI18N::get("uiUnpackErrHead") + " " + CFile::getFilename(SourceName
);
1923 if (!bnpFile
.unpack(DestPath
))
1929 for (uint32 i
= 0; i
< bnpFile
.SFiles
.size(); ++i
)
1930 vFilenames
.push_back(bnpFile
.SFiles
[i
].Name
);
1937 // ****************************************************************************
1938 int CPatchManager::downloadProgressFunc(void *foo
, double t
, double d
, double ultotal
, double ulnow
)
1942 // In the case of progress = 1, don't update because, this will be called in case of error to signal the end of the download, though
1943 // no download actually occurred. Instead, we set progress to 1.f at the end of downloadWithCurl if everything went fine
1944 return validateProgress(foo
, t
, d
, ultotal
, ulnow
);
1949 // ****************************************************************************
1950 int CPatchManager::validateProgress(void *foo
, double t
, double d
, double /* ultotal */, double /* ulnow */)
1952 static std::vector
<std::string
> units
;
1956 units
.push_back(CI18N::get("uiByte"));
1957 units
.push_back(CI18N::get("uiKb"));
1958 units
.push_back(CI18N::get("uiMb"));
1961 CPatchManager
*pPM
= CPatchManager::getInstance();
1962 double pour1
= t
!=0.0?d
*100.0/t
:0.0;
1963 string sTranslate
= CI18N::get("uiLoginGetFile") + toString(" %s : %s / %s (%.02f %%)", NLMISC::CFile::getFilename(pPM
->CurrentFile
).c_str(),
1964 NLMISC::bytesToHumanReadableUnits((uint64
)d
, units
).c_str(), NLMISC::bytesToHumanReadableUnits((uint64
)t
, units
).c_str(), pour1
);
1965 pPM
->setState(false, sTranslate
);
1968 ((NLMISC::IProgressCallback
*) foo
)->progress((float) (t
!= 0 ? d
/ t
: 0));
1973 // ****************************************************************************
1974 void CPatchManager::MyPatchingCB::progress(float f
)
1976 CPatchManager
*pPM
= CPatchManager::getInstance();
1978 string sTranslate
= CI18N::get("uiApplyingDelta") + toString(" %s (%.02f %%)", CFile::getFilename(patchFilename
).c_str(), p
);
1979 pPM
->setState(false, sTranslate
);
1982 // ***************************************************************************
1983 void CPatchManager::startScanDataThread()
1985 if (ScanDataThread
!= NULL
)
1987 nlwarning ("scan data thread is already running");
1992 nlwarning ("a thread is already running");
1996 _ErrorMessage
.clear();
2001 // Read now the client version and Desc File.
2002 readClientVersionAndDescFile();
2005 ScanDataThread
= new CScanDataThread();
2006 nlassert (ScanDataThread
!= NULL
);
2008 Thread
= IThread::create (ScanDataThread
);
2009 nlassert (Thread
!= NULL
);
2013 // ****************************************************************************
2014 bool CPatchManager::isScanDataThreadEnded(bool &ok
)
2016 if (ScanDataThread
== NULL
)
2022 bool end
= ScanDataThread
->Ended
;
2025 ok
= ScanDataThread
->CheckOk
;
2026 stopScanDataThread();
2032 // ****************************************************************************
2033 void CPatchManager::stopScanDataThread()
2035 if(ScanDataThread
&& Thread
)
2040 delete ScanDataThread
;
2041 ScanDataThread
= NULL
;
2045 // ***************************************************************************
2046 void CPatchManager::askForStopScanDataThread()
2051 ScanDataThread
->AskForCancel
= true;
2054 // ***************************************************************************
2055 uint
CPatchManager::applyScanDataResult()
2057 // if still running, abort
2064 TSyncDataScanState::CAccessor
ac(&DataScanState
);
2065 CDataScanState
&val
= ac
.value();
2066 numError
= (uint
)val
.FilesWithScanDataError
.size();
2068 // Touch the files with error (will be reloaded at next patch)
2069 for(uint i
=0;i
<numError
;i
++)
2071 SFileToPatch
&ftp
= val
.FilesWithScanDataError
[i
];
2073 // if the file was not found (just loggued for information)
2074 if(!ftp
.LocalFileExists
)
2078 // following lines added by Sadge to ensure that the correct file gets patched
2080 if (NLMISC::CFile::fileExists(WritableClientDataPath
+ ftp
.FileName
)) sFilePath
= WritableClientDataPath
+ ftp
.FileName
;
2081 if (sFilePath
.empty() && NLMISC::CFile::fileExists(ReadableClientDataPath
+ ftp
.FileName
)) sFilePath
= ReadableClientDataPath
+ ftp
.FileName
;
2082 if (sFilePath
.empty() && NLMISC::CFile::fileExists(ClientPatchPath
+ ftp
.FileName
)) sFilePath
= ClientPatchPath
+ ftp
.FileName
;
2084 // following lines removed by Sadge to ensure that the correct file gets patched
2085 // string sFilePath = CPath::lookup(ftp.FileName, false, false);
2086 // if (sFilePath.empty())
2088 // if (NLMISC::CFile::fileExists(ClientPatchPath + ftp.FileName))
2089 // sFilePath = ClientPatchPath + ftp.FileName;
2092 // Reset to a dummy date, so the patch will be fully/checked/patched next time
2093 if(!sFilePath
.empty())
2094 applyDate(sFilePath
, DefaultResetDate
);
2096 nlwarning("File '%s' Not Found. should exist...", ftp
.FileName
.c_str());
2103 // ***************************************************************************
2104 bool CPatchManager::getDataScanLog(string
&text
)
2107 bool changed
= false;
2109 TSyncDataScanState::CAccessor
ac(&DataScanState
);
2110 CDataScanState
&val
= ac
.value();
2111 changed
= val
.Changed
;
2112 // if changed, build the log
2115 for(uint i
=0;i
<val
.FilesWithScanDataError
.size();i
++)
2118 getCorruptedFileInfo(val
.FilesWithScanDataError
[i
], str
);
2129 // ***************************************************************************
2130 void CPatchManager::addDataScanLogCorruptedFile(const SFileToPatch
&ftp
)
2133 TSyncDataScanState::CAccessor
ac(&DataScanState
);
2134 CDataScanState
&val
= ac
.value();
2135 val
.FilesWithScanDataError
.push_back(ftp
);
2140 // ***************************************************************************
2141 void CPatchManager::clearDataScanLog()
2144 TSyncDataScanState::CAccessor
ac(&DataScanState
);
2145 CDataScanState
&val
= ac
.value();
2146 val
.FilesWithScanDataError
.clear();
2151 // ***************************************************************************
2152 void CPatchManager::getCorruptedFileInfo(const SFileToPatch
&ftp
, string
&sTranslate
)
2154 sTranslate
= CI18N::get("uiCorruptedFile") + " " + ftp
.FileName
+ " (" +
2155 toString("%.1f ", (float)ftp
.FinalFileSize
/1000000.f
) + CI18N::get("uiMb") + ")";
2159 // ****************************************************************************
2160 // ****************************************************************************
2161 // ****************************************************************************
2163 // ****************************************************************************
2164 // ****************************************************************************
2165 // ****************************************************************************
2167 // ****************************************************************************
2168 CCheckThread::CCheckThread(bool includeBackgroundPatch
)
2172 TotalFileToCheck
= 0;
2173 CurrentFileChecked
= 0;
2174 IncludeBackgroundPatch
= includeBackgroundPatch
;
2178 // ****************************************************************************
2179 void CCheckThread::run ()
2181 nlwarning("CCheckThread::start");
2183 CPatchManager
*pPM
= CPatchManager::getInstance();
2184 pPM
->MustLaunchBatFile
= false;
2187 nlwarning("CCheckThread : get version");
2189 // Check if the client version is the same as the server version
2190 string sClientVersion
= pPM
->getClientVersion();
2191 string sServerVersion
= pPM
->getServerVersion();
2192 string sTranslate
= CI18N::get("uiClientVersion") + " (" + sClientVersion
+ ") ";
2193 sTranslate
+= CI18N::get("uiServerVersion") + " (" + sServerVersion
+ ")";
2194 pPM
->setState(true, sTranslate
);
2196 if (sServerVersion
.empty())
2204 sint32 nServerVersion
, nClientVersion
;
2205 fromString(sServerVersion
, nServerVersion
);
2206 fromString(sClientVersion
, nClientVersion
);
2208 if (nClientVersion
!= nServerVersion
)
2210 // first, try in the version subdirectory
2213 pPM
->DescFilename
= toString("%05d/ryzom_%05d.idx", nServerVersion
, nServerVersion
);
2214 // The client version is different from the server version : download new description file
2215 pPM
->getServerFile(pPM
->DescFilename
, false); // For the moment description file is not zipped
2219 // fallback to patch root directory
2220 pPM
->DescFilename
= toString("ryzom_%05d.idx", nServerVersion
);
2221 // The client version is different from the server version : download new description file
2222 pPM
->getServerFile(pPM
->DescFilename
, false); // For the moment description file is not zipped
2226 nlwarning("CCheckThread : read description files");
2228 // Read the description file
2229 pPM
->readDescFile(nServerVersion
);
2231 nlwarning("CCheckThread : check files");
2233 // For all bnp in the description file get all patches to apply
2234 // depending on the version of the client bnp files
2235 const CBNPFileSet
&rDescFiles
= pPM
->DescFile
.getFiles();
2236 pPM
->FilesToPatch
.clear();
2237 TotalFileToCheck
= rDescFiles
.fileCount();
2238 for (i
= 0; i
< rDescFiles
.fileCount(); ++i
)
2240 CPatchManager::SFileToPatch ftp
;
2241 sTranslate
= CI18N::get("uiCheckingFile") + " " + rDescFiles
.getFile(i
).getFileName();
2242 pPM
->setState(true, sTranslate
);
2243 // get list of patch to apply to this file. don't to a full checksum test if possible
2244 nlwarning(rDescFiles
.getFile(i
).getFileName().c_str());
2245 pPM
->getPatchFromDesc(ftp
, rDescFiles
.getFile(i
), false);
2246 // add the file if there are some patches to apply, or if an already patched version was found in the unpack directory
2247 if (!ftp
.Patches
.empty() || (IncludeBackgroundPatch
&& !ftp
.SrcFileName
.empty()))
2249 pPM
->FilesToPatch
.push_back(ftp
);
2250 sTranslate
= CI18N::get("uiNeededPatches") + " " + toString (ftp
.Patches
.size());
2251 pPM
->setState(true, sTranslate
);
2253 CurrentFileChecked
= i
;
2256 // Here we got all the files to patch in FilesToPatch and all the versions that must be obtained
2257 // Now we have to get the optional categories
2258 const CBNPCategorySet
&rDescCats
= pPM
->DescFile
.getCategories();
2259 pPM
->OptionalCat
.clear();
2260 for (i
= 0; i
< rDescCats
.categoryCount(); ++i
)
2262 // For all optional categories check if there is a 'file to patch' in it
2263 const CBNPCategory
&rCat
= rDescCats
.getCategory(i
);
2264 if (rCat
.isOptional())
2265 for (j
= 0; j
< rCat
.fileCount(); ++j
)
2267 const string
&rFilename
= rCat
.getFile(j
);
2268 bool bAdded
= false;
2269 for (k
= 0; k
< pPM
->FilesToPatch
.size(); ++k
)
2271 if (stricmp(pPM
->FilesToPatch
[k
].FileName
.c_str(), rFilename
.c_str()) == 0)
2273 pPM
->OptionalCat
.push_back(rCat
.getName());
2283 // For all categories that required an optional category if the cat required is present the category that
2284 // reference it must be present
2285 for (i
= 0; i
< rDescCats
.categoryCount(); ++i
)
2287 // For all optional categories check if there is a 'file to patch' in it
2288 const CBNPCategory
&rCat
= rDescCats
.getCategory(i
);
2289 if (rCat
.isOptional() && !rCat
.getCatRequired().empty())
2291 // Does the rCat is already present ?
2292 bool bFound
= false;
2293 for (j
= 0; j
< pPM
->OptionalCat
.size(); ++j
)
2295 if (rCat
.getName() == pPM
->OptionalCat
[j
])
2304 // rCat is not present but perhaps its required cat is present
2305 const string
&sCatReq
= rCat
.getCatRequired();
2306 for (j
= 0; j
< pPM
->OptionalCat
.size(); ++j
)
2308 if (sCatReq
== pPM
->OptionalCat
[j
])
2310 // Required Cat present -> Add the category rCat
2311 pPM
->OptionalCat
.push_back(rCat
.getName());
2320 // Delete categories optional cat that are hidden
2321 for (i
= 0; i
< rDescCats
.categoryCount(); ++i
)
2323 // For all optional categories check if there is a 'file to patch' in it
2324 const CBNPCategory
&rCat
= rDescCats
.getCategory(i
);
2325 if (rCat
.isOptional() && rCat
.isHidden())
2327 // Does the rCat is present ?
2328 for (j
= 0; j
< pPM
->OptionalCat
.size(); ++j
)
2330 if (rCat
.getName() == pPM
->OptionalCat
[j
])
2333 /*#ifdef RY_BG_DOWNLOADER
2334 // fixme : strange stlport link error for the background downloader when using erase ...
2335 std::copy(pPM->OptionalCat.begin() + j + 1, pPM->OptionalCat.end(), pPM->OptionalCat.begin()+j);
2336 pPM->OptionalCat.resize(pPM->OptionalCat.size() - 1);
2338 pPM
->OptionalCat
.erase(pPM
->OptionalCat
.begin()+j
);
2346 // Get all extract to category and check files inside the bnp with real files
2347 for (i
= 0; i
< rDescCats
.categoryCount(); ++i
)
2349 // For all optional categories check if there is a 'file to patch' in it
2350 const CBNPCategory
&rCat
= rDescCats
.getCategory(i
);
2351 if (!rCat
.getUnpackTo().empty())
2353 for (j
= 0; j
< rCat
.fileCount(); ++j
)
2355 string sBNPFilename
= pPM
->ClientPatchPath
+ rCat
.getFile(j
);
2357 sTranslate
= CI18N::get("uiCheckInBNP") + " " + rCat
.getFile(j
);
2358 pPM
->setState(true, sTranslate
);
2359 CBigFile::BNP bnpFile
;
2360 bnpFile
.BigFileName
= sBNPFilename
;
2362 if (bnpFile
.readHeader())
2364 // read the file inside the bnp and calculate the sha1
2365 FILE *bnp
= nlfopen (sBNPFilename
, "rb");
2368 for (uint32 k
= 0; k
< bnpFile
.SFiles
.size(); ++k
)
2370 const CBigFile::SBNPFile
&rBNPFile
= bnpFile
.SFiles
[k
];
2371 // Is the real file exists ?
2372 string sRealFilename
= rCat
.getUnpackTo() + rBNPFile
.Name
;
2373 if (NLMISC::CFile::fileExists(sRealFilename
))
2375 // Yes compare the sha1 with the sha1 of the BNP File
2376 CHashKey sha1BNPFile
;
2377 nlfseek64 (bnp
, rBNPFile
.Pos
, SEEK_SET
);
2378 uint8
*pPtr
= new uint8
[rBNPFile
.Size
];
2379 if (fread (pPtr
, rBNPFile
.Size
, 1, bnp
) != 1)
2385 sha1BNPFile
= getSHA1(pPtr
, rBNPFile
.Size
);
2387 CHashKey sha1RealFile
= getSHA1(sRealFilename
, true);
2388 if ( ! (sha1RealFile
== sha1BNPFile
))
2390 sTranslate
= CI18N::get("uiSHA1Diff") + " " + rBNPFile
.Name
;
2391 pPM
->setState(true, sTranslate
);
2392 pPM
->MustLaunchBatFile
= true;
2397 // File dest do not exist
2398 sTranslate
= CI18N::get("uiSHA1Diff") + " " + rBNPFile
.Name
;
2399 pPM
->setState(true, sTranslate
);
2400 pPM
->MustLaunchBatFile
= true;
2416 sTranslate
= CI18N::get("uiCheckEndNoErr");
2417 pPM
->setState(true, sTranslate
);
2421 catch (const NLMISC::EDiskFullError
&)
2423 // more explicit message for this common error case
2424 nlwarning("EXCEPTION CATCH: disk full");
2425 pPM
->setState(true, CI18N::get("uiCheckEndWithErr"));
2426 pPM
->setErrorMessage(CI18N::get("uiPatchDiskFull"));
2430 catch (const Exception
&e
)
2432 nlwarning("EXCEPTION CATCH: CCheckThread::run() failed");
2433 string sTranslate
= CI18N::get("uiCheckEndWithErr") + " " + e
.what();
2434 pPM
->setState(true, CI18N::get("uiCheckEndWithErr"));
2435 pPM
->setErrorMessage(sTranslate
);
2441 // ****************************************************************************
2442 // ****************************************************************************
2443 // ****************************************************************************
2445 // ****************************************************************************
2446 // ****************************************************************************
2447 // ****************************************************************************
2449 // ****************************************************************************
2450 CPatchThread::CPatchThread(bool commitPatch
)
2454 CurrentFilePatched
= 0;
2455 PatchSizeProgress
= 0;
2456 _CommitPatch
= commitPatch
;
2460 // ****************************************************************************
2461 void CPatchThread::clear()
2463 AllFilesToPatch
.clear();
2466 // ****************************************************************************
2467 // trap : optimize this if needed
2468 void CPatchThread::add(const CPatchManager::SFileToPatch
&ftp
)
2470 for (uint32 i
= 0; i
< AllFilesToPatch
.size(); ++i
)
2472 if (AllFilesToPatch
[i
].FileName
== ftp
.FileName
)
2475 AllFilesToPatch
.push_back(ftp
);
2479 // ****************************************************************************
2480 void CPatchThread::run()
2483 CPatchManager
*pPM
= CPatchManager::getInstance();
2487 string sServerVersion
= pPM
->getServerVersion();
2488 PatchSizeProgress
= 0;
2490 if (sServerVersion
.empty())
2498 // Patch all the files
2499 // If at least one file has been patched relaunch the client
2501 CurrentFilePatched
= 0.f
;
2506 // First do all ref files
2507 // ----------------------
2509 for (i
= 0; i
< AllFilesToPatch
.size(); ++i
)
2511 CPatchManager::SFileToPatch
&rFTP
= AllFilesToPatch
[i
];
2512 string ext
= NLMISC::CFile::getExtension(rFTP
.FileName
);
2515 float oldCurrentFilePatched
= CurrentFilePatched
;
2517 pPM
->MustLaunchBatFile
= true;
2518 CurrentFilePatched
= oldCurrentFilePatched
+ 1.f
;
2527 // Second do all bnp files
2528 // -----------------------
2530 for (i
= 0; i
< AllFilesToPatch
.size(); ++i
)
2532 CPatchManager::SFileToPatch
&rFTP
= AllFilesToPatch
[i
];
2534 string ext
= NLMISC::CFile::getExtension(rFTP
.FileName
);
2535 if (ext
== "bnp" || ext
== "snp")
2537 float oldCurrentFilePatched
= CurrentFilePatched
;
2539 pPM
->MustLaunchBatFile
= true;
2540 CurrentFilePatched
= oldCurrentFilePatched
+ 1.f
;
2550 catch (const NLMISC::EDiskFullError
&)
2552 // more explicit message for this common error case
2553 nlwarning("EXCEPTION CATCH: CPatchThread::run() Disk Full");
2554 pPM
->setState(true, CI18N::get("uiPatchEndWithErr"));
2555 sTranslate
= CI18N::get("uiPatchDiskFull");
2558 catch(const Exception
&e
)
2560 nlwarning("EXCEPTION CATCH: CPatchThread::run() failed");
2561 pPM
->setState(true, string(e
.what()));
2562 sTranslate
= CI18N::get("uiPatchEndWithErr");
2567 // Unpack all files with the UnpackTo flag
2568 // ---------------------------------------
2569 // To do that we create a batch file that will copy all files (unpacked to patch directory)
2570 // to the directory we want (the UnpackTo string)
2572 // Recreate batch file
2573 if (!pPM
->MustLaunchBatFile
&& !pPM
->getAsyncDownloader())
2575 pPM
->deleteFile(pPM
->UpdateBatchFilename
, false, false);
2580 sTranslate
= CI18N::get("uiPatchEndNoErr");
2584 // Set a more explicit error message
2585 pPM
->setErrorMessage(sTranslate
);
2593 class CPatchThreadDownloadProgress
: public NLMISC::IProgressCallback
2596 CPatchThread
*PatchThread
;
2599 uint CurrentFilePatched
;
2600 CPatchThreadDownloadProgress() : PatchThread(NULL
),
2603 CurrentFilePatched(0)
2606 virtual void progress (float progressValue
)
2608 clamp(progressValue
, 0.f
, 1.f
);
2609 nlassert(PatchThread
);
2610 //PatchThread->CurrentFilePatched = std::max(PatchThread->CurrentFilePatched, (float) this->CurrentFilePatched + Bias + Scale * progressValue);
2611 PatchThread
->CurrentFilePatched
= this->CurrentFilePatched
+ Bias
+ Scale
* progressValue
;
2612 CPatchManager::getInstance()->touchState();
2616 // ****************************************************************************
2617 void CPatchThread::processFile (CPatchManager::SFileToPatch
&rFTP
)
2619 CPatchManager
*pPM
= CPatchManager::getInstance();
2621 // Source File Name (in writable or readable directory)
2624 // Destination File Name (in writable directory)
2625 string DestinationName
;
2627 if (rFTP
.ExtractPath
.empty())
2629 DestinationName
= pPM
->WritableClientDataPath
+ rFTP
.FileName
;
2631 if (rFTP
.LocalFileExists
)
2633 // following lines added by Sadge to ensure that the correct file gets patched
2636 if (NLMISC::CFile::fileExists(pPM
->WritableClientDataPath
+ rFTP
.FileName
))
2638 SourceName
= pPM
->WritableClientDataPath
+ rFTP
.FileName
;
2640 else if (NLMISC::CFile::fileExists(pPM
->ReadableClientDataPath
+ rFTP
.FileName
))
2642 SourceName
= pPM
->ReadableClientDataPath
+ rFTP
.FileName
;
2645 // version from previous download
2646 if (SourceName
.empty()) throw Exception (std::string("ERROR: Failed to find file: ")+rFTP
.FileName
);
2648 // following lines removed by Sadge to ensure that the correct file gets patched
2649 // SourceName = CPath::lookup(rFTP.FileName); // exception if file do not exists
2653 // note : if file was background downloaded, we have :
2654 // rFTP.LocalFileExists = false
2655 // rFTP.SrcFileName = "unpack/filename.bnp.tmp"
2656 SourceName
= DestinationName
;
2661 SourceName
= pPM
->ClientPatchPath
+ rFTP
.FileName
;
2662 DestinationName
= SourceName
;
2665 if (rFTP
.LocalFileToDelete
)
2667 // corrupted or invalid file ? ....
2668 NLMISC::CFile::deleteFile(SourceName
);
2669 rFTP
.LocalFileExists
= false;
2673 sTranslate
= CI18N::get("uiProcessing") + " " + rFTP
.FileName
;
2674 pPM
->setState(true, sTranslate
);
2676 if (pPM
->getAsyncDownloader())
2678 if (!rFTP
.ExtractPath
.empty())
2680 std::string info
= rFTP
.FileName
;
2682 string PatchName
= toString("%05d/%s%s", rFTP
.Patches
[rFTP
.Patches
.size() - 1], rFTP
.FileName
.c_str(), ".lzma");
2683 pPM
->getAsyncDownloader()->addToDownloadList(PatchName
, SourceName
, rFTP
.LastFileDate
, rFTP
.ExtractPath
, rFTP
.SZFileSize
, rFTP
.FinalFileSize
);
2688 std::string tmpSourceName
= rFTP
.SrcFileName
.empty() ? SourceName
: rFTP
.SrcFileName
; // source is same than destination, or possibly
2689 // a patch from a previous background downloader session
2690 if (rFTP
.LocalFileToDelete
)
2692 // corrupted or invalid file ? ....
2693 rFTP
.LocalFileExists
= false;
2694 NLMISC::CFile::deleteFile(tmpSourceName
);
2698 bool usePatchFile
= true;
2699 bool haveAllreadyTryiedDownloadingOfFile
= false;
2701 // compute the total size of patch to download
2702 uint32 totalPatchSize
= 0;
2703 if (rFTP
.Incremental
)
2705 for (uint i
=0; i
<rFTP
.PatcheSizes
.size(); ++i
)
2706 totalPatchSize
+= rFTP
.PatcheSizes
[i
];
2708 else if (!rFTP
.PatcheSizes
.empty())
2710 totalPatchSize
= rFTP
.PatcheSizes
.back();
2714 CPatchThreadDownloadProgress progress
;
2715 progress
.PatchThread
= this;
2716 progress
.CurrentFilePatched
= (uint
) floorf(CurrentFilePatched
);
2718 // look for the source file, if not present or invalid (not matching
2719 // a known version) or if total patch size greater than half the
2720 // uncompressed final size or if total patch size is greater
2721 // than lzma compressed file, then load the lzma file
2722 if ((rFTP
.Incremental
&& !rFTP
.LocalFileExists
) // incremental and no base file, we need a complete file
2723 || totalPatchSize
> rFTP
.FinalFileSize
/ 2 // patch is too big regarding the final file, patch applying time will be slow
2724 || rFTP
.SZFileSize
< totalPatchSize
) // lzma is smaller than patch !
2728 usePatchFile
= false;
2729 // compute the seven zip filename
2730 string lzmaFile
= rFTP
.FileName
+".lzma";
2732 // download the 7zip file
2735 // first, try in the file version subfolfer
2738 progress
.Scale
= 1.f
;
2739 progress
.Bias
= 0.f
;
2740 if (!rFTP
.Patches
.empty())
2742 pPM
->getServerFile(toString("%05u/", rFTP
.Patches
.back())+lzmaFile
, false, "", &progress
);
2744 // else -> file comes from a previous download (with .tmp extension, and is up to date)
2745 // the remaining code will just rename it with good name and exit
2747 catch (const NLMISC::EWriteError
&)
2749 // this is a local error, rethrow ...
2754 // failed with version subfolder, try in the root patch directory
2755 pPM
->getServerFile(lzmaFile
, false, "", &progress
);
2758 catch (const NLMISC::EWriteError
&)
2760 // this is a local error, rethrow ...
2765 // can not load the 7zip file, use normal patching
2766 usePatchFile
= true;
2767 haveAllreadyTryiedDownloadingOfFile
= true;
2771 OutFilename
= pPM
->ClientPatchPath
+ NLMISC::CFile::getFilename(rFTP
.FileName
);
2772 // try to unpack the file
2775 if (!unpackLZMA(pPM
->ClientPatchPath
+lzmaFile
, OutFilename
+".tmp"))
2777 // fallback to standard patch method
2778 usePatchFile
= true;
2779 haveAllreadyTryiedDownloadingOfFile
= true;
2783 catch (const NLMISC::EWriteError
&)
2789 nlwarning("Failed to unpack lzma file %s", (pPM
->ClientPatchPath
+lzmaFile
).c_str());
2790 // fallback to standard patch method
2791 usePatchFile
= true;
2792 haveAllreadyTryiedDownloadingOfFile
= true;
2796 if (rFTP
.LocalFileExists
)
2797 pPM
->deleteFile(SourceName
);
2799 pPM
->deleteFile(pPM
->ClientPatchPath
+lzmaFile
); // delete the archive file
2800 pPM
->deleteFile(SourceName
, false, false); // File can exists if bad BNP loading
2803 pPM
->renameFile(OutFilename
+".tmp", DestinationName
);
2809 uint32 currentPatchedSize
= 0;
2810 for (uint32 j
= 0; j
< rFTP
.Patches
.size(); ++j
)
2813 // first, try in the patch subdirectory
2817 PatchName
= toString("%05d/", rFTP
.Patches
[j
]) + rFTP
.FileName
.substr(0, rFTP
.FileName
.size()-4) + toString("_%05d", rFTP
.Patches
[j
]) + ".patch";
2818 sTranslate
= CI18N::get("uiLoginGetFile") + " " + PatchName
;
2819 pPM
->setState(true, sTranslate
);
2820 progress
.Scale
= 1.f
;
2821 progress
.Bias
= totalPatchSize
!= 0 ? (float) currentPatchedSize
/ totalPatchSize
: 0.f
;
2822 progress
.Scale
= totalPatchSize
!= 0 ? (float) rFTP
.PatcheSizes
[j
] / totalPatchSize
: 1.f
;
2823 pPM
->getServerFile(PatchName
, false, "", &progress
);
2824 // remove the subfolder name
2825 PatchName
= NLMISC::CFile::getFilename(PatchName
);
2827 catch (const NLMISC::EWriteError
&)
2833 // fallback to patch root directory
2834 PatchName
= rFTP
.FileName
.substr(0, rFTP
.FileName
.size()-4);
2835 PatchName
+= toString("_%05d", rFTP
.Patches
[j
]) + ".patch";
2836 sTranslate
= CI18N::get("uiLoginGetFile") + " " + PatchName
;
2837 pPM
->setState(true, sTranslate
);
2838 pPM
->getServerFile(PatchName
, false, "", &progress
);
2842 // If the patches are not to be applied on the last version
2843 string SourceNameXD
= tmpSourceName
;
2844 if (!rFTP
.Incremental
)
2846 // Some note about non incremental patches :
2847 // Usually, files such as '.exe' or '.dll' do not work well with incremental patch, so it is better
2848 // to apply a single patch from a refrence version to them. (so here we necessarily got
2849 // 'rFTP.Patches.size() == 1' !)
2850 // The reference version itself is incrementally patched, however, and may be rebuilt from time to time
2851 // when the delta to the reference has become too big (but is most of the time < to the sum of equivallent delta patchs)
2852 // The non-incremental final patch (from ref file to final bnp), is guaranteed to be done last
2853 // after the reference file has been updated
2855 // Find the reference file to apply the patch
2856 SourceNameXD
= rFTP
.FileName
;
2857 SourceNameXD
= SourceNameXD
.substr(0, SourceNameXD
.rfind('.'));
2858 SourceNameXD
+= "_.ref";
2863 std::string tmpRefFile
= SourceNameXD
+ ".tmp";
2864 if (!NLMISC::CFile::fileExists(pPM
->ClientPatchPath
+ tmpRefFile
))
2866 // Not found in the patch directory -> version in data directory should be good, or would have been
2867 // detected by the check thread else.
2871 SourceNameXD
= tmpRefFile
; // good ref file download by bg downloader
2875 // following lines added by Sadge to ensure that the correct file gets patched
2876 // string SourceNameXDFull;
2877 // if (NLMISC::CFile::fileExists(pPM->ClientDataPath + SourceNameXD)) SourceNameXDFull = pPM->ClientDataPath + SourceNameXD;
2879 // following lines removed by Sadge to ensure that the correct file gets patched
2880 // string SourceNameXDFull = CPath::lookup(SourceNameXD, false, false);
2881 // if (SourceNameXDFull.empty())
2882 // SourceNameXDFull = pPM->ClientDataPath + SourceNameXD;
2883 // SourceNameXD = SourceNameXDFull;
2884 if (CFile::fileExists(pPM
->WritableClientDataPath
+ SourceNameXD
))
2886 SourceNameXD
= pPM
->WritableClientDataPath
+ SourceNameXD
;
2888 else if (CFile::fileExists(pPM
->ReadableClientDataPath
+ SourceNameXD
))
2890 SourceNameXD
= pPM
->ReadableClientDataPath
+ SourceNameXD
;
2894 PatchName
= pPM
->ClientPatchPath
+ PatchName
;
2896 string OutFilename
= pPM
->ClientPatchPath
+ rFTP
.FileName
+ ".tmp__" + toString(j
);
2898 sTranslate
= CI18N::get("uiApplyingDelta") + " " + CFile::getFilename(PatchName
);
2899 pPM
->setState(true, sTranslate
);
2901 bool deltaPatchResult
= xDeltaPatch(PatchName
, SourceNameXD
, OutFilename
);
2903 if (!deltaPatchResult
&& !haveAllreadyTryiedDownloadingOfFile
) // Patch failed, try to download and apply lzma
2907 // compute the seven zip filename
2908 string lzmaFile
= rFTP
.FileName
+".lzma";
2910 // download the 7zip file
2913 // first, try in the file version subfolfer
2916 progress
.Scale
= 1.f
;
2917 progress
.Bias
= 0.f
;
2918 if (!rFTP
.Patches
.empty())
2920 pPM
->getServerFile(toString("%05u/", rFTP
.Patches
.back())+lzmaFile
, false, "", &progress
);
2922 // else -> file comes from a previous download (with .tmp extension, and is up to date)
2923 // the remaining code will just rename it with good name and exit
2925 catch (const NLMISC::EWriteError
&)
2927 // this is a local error, rethrow ...
2932 // failed with version subfolder, try in the root patch directory
2933 pPM
->getServerFile(lzmaFile
, false, "", &progress
);
2936 catch (const NLMISC::EWriteError
&)
2938 // this is a local error, rethrow ...
2946 OutFilename
= pPM
->ClientPatchPath
+ NLMISC::CFile::getFilename(rFTP
.FileName
);
2947 // try to unpack the file
2950 if (!unpackLZMA(pPM
->ClientPatchPath
+lzmaFile
, OutFilename
+".tmp"))
2955 catch (const NLMISC::EWriteError
&)
2961 nlwarning("Failed to unpack lzma file %s", (pPM
->ClientPatchPath
+lzmaFile
).c_str());
2965 if (rFTP
.LocalFileExists
)
2966 pPM
->deleteFile(SourceName
);
2968 pPM
->deleteFile(pPM
->ClientPatchPath
+lzmaFile
); // delete the archive file
2969 pPM
->deleteFile(SourceName
, false, false); // File can exists if bad BNP loading
2972 pPM
->renameFile(OutFilename
+".tmp", DestinationName
);
2978 if (rFTP
.LocalFileExists
)
2979 pPM
->deleteFile(SourceName
);
2980 pPM
->deleteFile(PatchName
);
2984 pPM
->deleteFile(SourceNameXD
, false, false); // File can exists if bad BNP loading
2986 tmpSourceName
= OutFilename
;
2987 PatchSizeProgress
+= rFTP
.PatcheSizes
[j
];
2988 currentPatchedSize
+= rFTP
.PatcheSizes
[j
];
2991 if (tmpSourceName
!= DestinationName
)
2993 pPM
->deleteFile(SourceName
, false, false); // File can exists if bad BNP loading
2996 // let the patch in the unpack directory
2997 pPM
->renameFile(tmpSourceName
, pPM
->ClientPatchPath
+ rFTP
.FileName
+ ".tmp");
3001 pPM
->renameFile(tmpSourceName
, DestinationName
);
3007 PatchSizeProgress
+= totalPatchSize
;
3010 // If all patches applied with success so file size should be ok
3011 // We just have to change file date to match the last patch applied
3012 pPM
->applyDate(DestinationName
, rFTP
.LastFileDate
);
3013 //progress.progress(1.f);
3016 // ****************************************************************************
3017 bool CPatchThread::xDeltaPatch(const string
&patch
, const string
&src
, const string
&out
)
3021 CPatchManager::MyPatchingCB patchingCB
;
3023 patchingCB
.patchFilename
= patch
;
3024 patchingCB
.srcFilename
= src
;
3026 CPatchManager
*pPM
= CPatchManager::getInstance();
3027 pPM
->deleteFile(out
, false, false);
3029 std::string errorMsg
;
3030 CXDeltaPatch::TApplyResult ar
= CXDeltaPatch::apply(patch
, src
, out
, errorMsg
, &patchingCB
);
3031 if (ar
!= CXDeltaPatch::ApplyResult_Ok
)
3035 case CXDeltaPatch::ApplyResult_WriteError
:
3036 throw NLMISC::EWriteError(out
);
3038 case CXDeltaPatch::ApplyResult_DiskFull
:
3039 throw NLMISC::EDiskFullError(out
);
3043 nlinfo("Error applying %s to %s giving %s", patch
.c_str(), src
.c_str(), out
.c_str());
3054 // Start the child process.
3055 string strCmdLine = "xdelta patch " + patch + " " + src + " " + out;
3060 // ****************************************************************************
3061 // ****************************************************************************
3062 // ****************************************************************************
3064 // ****************************************************************************
3065 // ****************************************************************************
3066 // ****************************************************************************
3068 // ****************************************************************************
3069 CScanDataThread::CScanDataThread()
3071 AskForCancel
= false;
3074 TotalFileToScan
= 1;
3075 CurrentFileScanned
= 1;
3078 // ****************************************************************************
3079 void CScanDataThread::run ()
3081 CPatchManager
*pPM
= CPatchManager::getInstance();
3085 // Check if the client version is the same as the server version
3086 string sClientVersion
= pPM
->getClientVersion();
3087 string sTranslate
= CI18N::get("uiClientVersion") + " (" + sClientVersion
+ ") ";
3088 pPM
->setState(true, sTranslate
);
3090 // For all bnp in the description file get all patches to apply
3091 // depending on the version of the client bnp files
3092 const CBNPFileSet
&rDescFiles
= pPM
->DescFile
.getFiles();
3093 TotalFileToScan
= rDescFiles
.fileCount();
3094 for (i
= 0; i
< rDescFiles
.fileCount(); ++i
)
3096 sTranslate
= CI18N::get("uiCheckingFile") + " " + rDescFiles
.getFile(i
).getFileName();
3097 pPM
->setState(true, sTranslate
);
3099 // get list of file to apply to this patch, performing a full checksum test (slow...)
3100 CPatchManager::SFileToPatch ftp
;
3101 pPM
->getPatchFromDesc(ftp
, rDescFiles
.getFile(i
), false);
3102 // if the file has been found but don't correspond to any local version (SHA1)
3103 // or if the file has not been found (or the .bnp is very very buggy so that addSearchFile() failed)
3104 if( (ftp
.LocalFileExists
&& ftp
.LocalFileToDelete
) ||
3105 (!ftp
.LocalFileExists
&& !ftp
.LocalFileToDelete
) )
3107 pPM
->addDataScanLogCorruptedFile(ftp
);
3108 CPatchManager::getCorruptedFileInfo(ftp
, sTranslate
);
3109 pPM
->setState(true, sTranslate
);
3111 CurrentFileScanned
= i
;
3113 // if the user ask to cancel the thread, stop now
3118 sTranslate
= CI18N::get("uiCheckEndNoErr");
3119 pPM
->setState(true, sTranslate
);
3123 catch (const Exception
&e
)
3125 nlwarning("EXCEPTION CATCH: CScanDataThread::run() failed");
3126 string sTranslate
= CI18N::get("uiCheckEndWithErr") + " " + e
.what();
3127 pPM
->setState(true, sTranslate
);
3134 // ****************************************************************************
3135 uint32
CPatchManager::SPatchInfo::getAvailablePatchsBitfield() const
3137 // About the test (until a patch enum is added, we use the 'optional' flag)
3138 // Non optional -> must patch it (will be for RoS)
3139 // Optional -> Will be for Mainland
3140 // Required : stands for 'bnp' required by the Optional bnps !! so ignore only RoS is wanted
3143 if (!NonOptCat
.empty())
3145 result
|= (1 << BGDownloader::DownloadID_RoS
);
3147 if (!OptCat
.empty() || !ReqCat
.empty())
3149 result
|= (1 << BGDownloader::DownloadID_MainLand
);
3154 // ***************************************************************************
3155 void CPatchManager::setAsyncDownloader(IAsyncDownloader
* asyncDownloader
)
3157 _AsyncDownloader
= asyncDownloader
;
3159 // ***************************************************************************
3160 IAsyncDownloader
* CPatchManager::getAsyncDownloader() const
3162 return _AsyncDownloader
;
3164 // **************************************************************************
3165 // ****************************************************************************
3166 void CPatchManager::startInstallThread(const std::vector
<CInstallThreadEntry
>& entries
)
3168 InstallThread
= new CInstallThread(entries
);
3169 Thread
= IThread::create (InstallThread
);
3170 nlassert (Thread
!= NULL
);
3174 void CPatchManager::startDownloadThread(const std::vector
<CInstallThreadEntry
>& entries
)
3176 DownloadThread
= new CDownloadThread(entries
);
3177 Thread
= IThread::create (DownloadThread
);
3178 nlassert (Thread
!= NULL
);
3183 void CPatchManager::onFileInstallFinished()
3185 if (_AsyncDownloader
)
3187 _AsyncDownloader
->onFileInstallFinished();
3192 void CPatchManager::onFileDownloadFinished()
3194 if (_AsyncDownloader
)
3196 _AsyncDownloader
->onFileDownloadFinished();
3201 void CPatchManager::onFileDownloading(const std::string
& sourceName
, uint32 rate
, uint32 fileIndex
, uint32 fileCount
, uint64 fileSize
, uint64 fullSize
)
3203 if (_AsyncDownloader
)
3205 _AsyncDownloader
->onFileDownloading(sourceName
, rate
, fileIndex
, fileCount
, fileSize
, fullSize
);
3209 void CPatchManager::onFileInstalling(const std::string
& sourceName
, uint32 rate
, uint32 fileIndex
, uint32 fileCount
, uint64 fileSize
, uint64 fullSize
)
3211 if (_AsyncDownloader
)
3213 _AsyncDownloader
->onFileInstalling(sourceName
, rate
, fileIndex
, fileCount
, fileSize
, fullSize
);
3217 bool CPatchManager::download(const std::string
& patchFullname
, const std::string
& sourceFullname
,
3218 const std::string
& tmpDirectory
, uint32 timestamp
)
3220 CPatchManager
*pPM
= CPatchManager::getInstance();
3222 static std::string zsStr
= ".lzma";
3223 static std::string::size_type zsStrLength
= zsStr
.size();
3225 // we delete file if present and diferent timestamp
3226 // if the file is the same then indicates its not necessary to download it a second time
3227 if (NLMISC::CFile::fileExists(sourceFullname
))
3229 uint32 t
= NLMISC::CFile::getFileModificationDate(sourceFullname
);
3231 { //we didn't downl oad
3234 pPM
->deleteFile(sourceFullname
);
3236 // file will be save to a .tmp file
3237 std::string extension
;
3238 if (patchFullname
.size() >= zsStrLength
&& patchFullname
.substr(patchFullname
.size() - zsStrLength
) == zsStr
)
3242 // remove tmp file if exist
3243 std::string patchName
= tmpDirectory
+ NLMISC::CFile::getFilename(sourceFullname
) + extension
+ std::string(".tmp");
3244 if (NLMISC::CFile::fileExists(patchName
))
3246 pPM
->deleteFile(patchName
);
3248 // creates directory tree if necessary
3249 NLMISC::CFile::createDirectoryTree( NLMISC::CFile::getPath(patchName
) );
3250 NLMISC::CFile::createDirectoryTree( NLMISC::CFile::getPath(sourceFullname
) );
3255 pPM
->getServerFile(patchFullname
, false, patchName
);
3257 catch ( const std::exception
& e
)
3259 nlwarning("%s", e
.what());
3260 pPM
->setState(true, string(e
.what()) );
3265 if (!NLMISC::CFile::fileExists(patchName
))
3269 // if file is compressed unpack then commit (rename)
3270 if (patchName
.size() >= zsStrLength
3271 && patchName
.substr(patchName
.size() - zsStrLength
) == zsStr
)
3273 std::string outFilename
= patchName
.substr(0, patchName
.size() - zsStrLength
);
3274 unpack7Zip(patchName
, outFilename
);
3275 pPM
->deleteFile(patchName
);
3276 pPM
->renameFile(outFilename
, sourceFullname
);
3280 // file is not compressed so rename the .tmp file to final name
3281 pPM
->renameFile(patchName
, sourceFullname
);
3283 // apply modification date
3284 pPM
->applyDate(sourceFullname
, timestamp
);
3290 bool CPatchManager::extract(const std::string
& patchPath
,
3291 const std::vector
<std::string
>& sourceFilename
,
3292 const std::vector
<std::string
>& extractPath
,
3293 const std::string
& updateBatchFilename
,
3294 const std::string
& execName
,
3297 nlassert(sourceFilename
.size() == extractPath
.size());
3298 CPatchManager
*pPM
= CPatchManager::getInstance();
3301 for (uint32 j
= 0; j
< extractPath
.size() && !ok
; ++j
)
3303 if (!extractPath
[j
].empty())
3311 // nothing to extract
3317 pPM
->deleteFile(updateBatchFilename
, false, false);
3319 FILE *fp
= nlfopen (updateBatchFilename
, "wt");
3323 string err
= toString("Can't open file '%s' for writing: code=%d %s (error code 29)", updateBatchFilename
.c_str(), errno
, strerror(errno
));
3324 throw Exception (err
);
3327 #ifdef NL_OS_WINDOWS
3328 fprintf(fp
, "@echo off\n");
3329 fprintf(fp
, "ping 127.0.0.1 -n 7 -w 1000 > nul\n"); // wait
3331 fprintf(fp
, "#!/bin/sh\n");
3332 fprintf(fp
, "sleep 7\n"); // wait
3335 // Unpack files with category ExtractPath non empty
3336 for (uint32 j
= 0; j
< sourceFilename
.size(); ++j
)
3338 if (!extractPath
[j
].empty())
3340 string rFilename
= sourceFilename
[j
];
3342 vector
<string
> vFilenames
;
3343 if (!pPM
->bnpUnpack(rFilename
, patchPath
, vFilenames
))
3346 string err = toString("Error unpacking %s", rFilename.c_str());
3347 throw Exception (err);
3352 std::string sourcePath
= NLMISC::CFile::getPath(sourceFilename
[j
]);
3353 for (uint32 fff
= 0; fff
< vFilenames
.size (); fff
++)
3355 string SrcPath
= CPath::standardizeDosPath(sourcePath
);
3356 string SrcName
= SrcPath
+ vFilenames
[fff
];
3357 string DstPath
= CPath::standardizeDosPath(extractPath
[j
]);
3358 string DstName
= DstPath
+ vFilenames
[fff
];
3359 NLMISC::CFile::createDirectoryTree(extractPath
[j
]);
3361 // this file must be moved
3362 #ifdef NL_OS_WINDOWS
3363 fprintf(fp
, ":loop%u\n", nblab
);
3364 fprintf(fp
, "attrib -r -a -s -h %s\n", DstName
.c_str());
3365 fprintf(fp
, "del %s\n", DstName
.c_str());
3366 fprintf(fp
, "if exist %s goto loop%u\n", DstName
.c_str(), nblab
);
3367 fprintf(fp
, "move %s %s\n", SrcName
.c_str(), DstPath
.c_str());
3369 // TODO: for Linux and OS X
3380 #ifdef NL_OS_WINDOWS
3381 fprintf(fp
, "start %s %%1 %%2 %%3\n", execName
.c_str());
3383 // TODO: for Linux and OS X
3393 if (!launchProgram(updateBatchFilename
, "", false))
3395 // error occurs during the launch
3396 string str
= toString("Can't execute '%s': code=%d %s (error code 30)", updateBatchFilename
.c_str(), errno
, strerror(errno
));
3397 throw Exception (str
);
3404 void CPatchManager::setStateListener(IPatchManagerStateListener
* stateListener
)
3406 _StateListener
= stateListener
;
3410 void CPatchManager::fatalError(const std::string
& errorId
, const std::string
& param1
, const std::string
& param2
)
3412 if (_AsyncDownloader
)
3414 _AsyncDownloader
->fatalError(errorId
, param1
, param2
);
3419 // ****************************************************************************
3420 void CDownloadThread::run()
3422 CPatchManager
*pPM
= CPatchManager::getInstance();
3424 std::string patchPath
= CPath::standardizePath (ClientRootPath
+TheTmpInstallDirectory
)+"patch/";
3427 static bool _FirstTime
= true;
3428 static uint64 _FullSize
= 0;
3429 static uint64 _CurrentSize
= 0;
3430 static uint32 _Start
;
3432 // At first launch calculat the amount of data need to download
3435 for (uint first
= 0,last
= (uint
)_Entries
.size() ; first
!= last
; ++first
)
3437 _FullSize
+= _Entries
[first
].SZipFileSize
;
3439 _Start
= NLMISC::CTime::getSecondsSince1970();
3444 for (uint first
= 0,last
= (uint
)_Entries
.size() ; first
!= last
; ++first
)
3446 std::string patchName
= CPath::standardizePath (_Entries
[first
].PatchName
, false);
3447 std::string sourceName
= CPath::standardizePath (_Entries
[first
].SourceName
, false);
3449 _CurrentSize
+= _Entries
[first
].SZipFileSize
;
3452 // Calculate the time since last update
3453 uint32 dt
= NLMISC::CTime::getSecondsSince1970() - _Start
;
3456 rate
= uint32(_CurrentSize
/ dt
);
3459 pPM
->onFileDownloading(sourceName
, rate
, first
+1, last
, _CurrentSize
, _FullSize
);
3461 std::string finalFile
=patchPath
+ patchName
;
3462 std::string tmpFile
= finalFile
+ std::string(".part");
3465 bool toDownload
= true;
3466 static volatile bool simulateDownload
= false;
3467 if (simulateDownload
)
3473 // Do not download if file are identicaly (check size and modification date)
3474 if (NLMISC::CFile::fileExists(finalFile
))
3476 uint64 fz
= NLMISC::CFile::getFileSize(finalFile
);
3477 uint32 timestamp
= _Entries
[first
].Timestamp
;
3478 uint32 timestamp2
= NLMISC::CFile::getFileModificationDate(finalFile
);
3479 if ( fz
== _Entries
[first
].SZipFileSize
&& timestamp
== timestamp2
)
3485 NLMISC::CFile::deleteFile(finalFile
);
3489 // file need to be download
3492 // delete tmp file if exist
3493 if (NLMISC::CFile::fileExists(tmpFile
))
3495 NLMISC::CFile::deleteFile(tmpFile
);
3497 std::string path
= NLMISC::CFile::getPath(tmpFile
);
3498 // create directory tree
3499 NLMISC::CFile::createDirectoryTree(path
);
3500 // Try to download, rename, applyDate and send error msg to gui in case of error
3503 pPM
->getServerFile(patchName
, false, tmpFile
);
3504 NLMISC::CFile::moveFile(finalFile
, tmpFile
);
3506 pPM
->applyDate(finalFile
, _Entries
[first
].Timestamp
);
3508 catch ( const std::exception
& e
)
3510 nlwarning("%s", e
.what());
3511 pPM
->setState(true, string(e
.what()) );
3512 pPM
->fatalError("uiCanNotDownload", patchName
.c_str(), "");
3516 pPM
->fatalError("uiCanNotDownload", patchName
.c_str(), "");
3522 // message to gui to indicates that all file are downloaded
3523 pPM
->onFileDownloadFinished();
3527 void CInstallThread::run()
3529 std::string patchPath
= CPath::standardizePath (ClientRootPath
+TheTmpInstallDirectory
)+"patch/";
3530 CPatchManager
*pPM
= CPatchManager::getInstance();
3532 std::set
<std::string
> allowed
;
3536 for (first
= 0,last
= (uint
)_Entries
.size() ; first
!= last
; ++first
)
3538 std::string correct
= CPath::standardizePath (patchPath
+ _Entries
[first
].PatchName
, false);
3539 allowed
.insert(correct
);
3541 // Delete file from tmp directory that are not "allowed" (torrent protocol can download partial file that are not asked)
3542 std::vector
<std::string
> vFiles
;
3543 CPath::getPathContent(patchPath
, true, false, true, vFiles
);
3544 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
3546 std::string sName2
= CPath::standardizePath (vFiles
[i
], false);
3547 if (allowed
.find(sName2
) == allowed
.end() )
3549 pPM
->deleteFile(sName2
, false, false);
3553 static bool _FirstTime
= true;
3554 static uint64 _FullSize
= 0;
3555 static uint64 _CurrentSize
= 0;
3556 static uint32 _Start
;
3558 // calculate size of data to download in order to know the install speed
3561 for (uint first
= 0,last
= (uint
)_Entries
.size() ; first
!= last
; ++first
)
3563 _FullSize
+= _Entries
[first
].Size
;
3565 _Start
= NLMISC::CTime::getSecondsSince1970();
3570 for (first
= 0,last
= (uint
)_Entries
.size() ; first
!= last
; ++first
)
3572 std::string patchName
= CPath::standardizePath (patchPath
+_Entries
[first
].PatchName
, false);
3573 std::string sourceName
= CPath::standardizePath (_Entries
[first
].SourceName
, false);
3574 uint32 lastFileDate
= _Entries
[first
].Timestamp
;
3577 _CurrentSize
+= _Entries
[first
].Size
;
3579 // calcule the install speed
3580 uint32 dt
= NLMISC::CTime::getSecondsSince1970() - _Start
;
3583 uint64 size
= _CurrentSize
/ dt
;
3584 rate
= uint32 (size
);
3587 // File can exists if bad BNP loading
3588 if (NLMISC::CFile::fileExists(sourceName
))
3590 pPM
->deleteFile(sourceName
, false, false);
3593 if (NLMISC::CFile::fileExists(patchName
))
3595 static std::string zsStr
= ".lzma";
3596 static std::string::size_type zsStrLength
= zsStr
.size();
3599 // if file is compressed unpack the file (decompress, rename tempory file, apply modification date)
3600 if (patchName
.size() >= zsStrLength
3601 && patchName
.substr(patchName
.size() - zsStrLength
) == zsStr
)
3603 std::string outFilename
= patchName
.substr(0, patchName
.size() - zsStrLength
);
3604 std::string localOutFilename
= CPath::standardizeDosPath(outFilename
);
3606 if ( unpackLZMA(patchName
, localOutFilename
) )
3608 pPM
->deleteFile(patchName
);
3609 pPM
->renameFile(outFilename
, sourceName
);
3610 pPM
->applyDate(sourceName
, lastFileDate
);
3614 throw NLMISC::Exception("Can not unpack");
3620 // if file is uncompressed rename tempory file and apply modification date)
3621 pPM
->renameFile(patchName
, sourceName
);
3622 pPM
->applyDate(sourceName
, lastFileDate
);
3625 catch ( const std::exception
& e
)
3627 nlwarning("%s", e
.what());
3628 pPM
->setState(true, string(e
.what()) );
3629 pPM
->fatalError("uiCanNotInstall", patchName
.c_str(), "");
3635 pPM
->fatalError("uiCanNotInstall", patchName
.c_str(), "");
3640 pPM
->onFileInstalling(sourceName
, rate
, first
+1, last
, _CurrentSize
, _FullSize
);
3646 // remove date from tmp directory (because install is finished)
3647 std::string install
= CPath::standardizePath (ClientRootPath
+TheTmpInstallDirectory
);
3649 std::vector
<std::string
> vFiles
;
3650 // Delete all classic file from tmp directory
3651 CPath::getPathContent(install
, true, false, true, vFiles
);
3652 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
3654 NLMISC::CFile::deleteFile(vFiles
[i
]);
3656 // Delete all directory from tmp directory
3660 CPath::getPathContent(install
, true, true, false, vFiles
);
3661 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
3663 NLMISC::CFile::deleteDirectory(vFiles
[i
]);
3666 while ( !vFiles
.empty() );
3667 // delete tmp directory
3668 NLMISC::CFile::deleteDirectory(install
);
3669 // delete libtorrent_logs directory if exist (not activate)
3670 if (NLMISC::CFile::fileExists("libtorrent_logs"))
3675 CPath::getPathContent("libtorrent_logs", true, true, false, vFiles
);
3676 for (uint32 i
= 0; i
< vFiles
.size(); ++i
)
3678 NLMISC::CFile::deleteDirectory(vFiles
[i
]);
3681 while ( !vFiles
.empty() );
3682 NLMISC::CFile::deleteDirectory("libtorrent_logs");
3687 pPM
->reboot(); // do not reboot just run the extract .bat