Fix
[ryzomcore.git] / nelns / login_system / nel_launcher_windows_ext2 / patch.cpp
blobb55c8cdff0e53d42c4f4a6a9b1ec63b3ce5bd962
1 // NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // Includes
21 #include "std_afx.h"
23 //#define USE_CURL
25 #ifdef USE_CURL
26 #include <curl/curl.h>
27 #endif
29 #include "nel/misc/debug.h"
30 #include "nel/misc/common.h"
31 #include "nel/misc/path.h"
32 #include "nel/misc/thread.h"
34 #include "patch.h"
35 #include "nel_launcher_dlg.h"
40 // Namespaces
43 using namespace std;
44 using namespace NLMISC;
48 // Variables
51 HINTERNET RootInternet = NULL;
53 static const string DirFilename = "dir.ngz";
54 static const string NelLauncherFilename = "nel_launcher.exe";
55 static const string NelLauncherConfigFilename = "nel_launcher.cfg";
57 // The file is auto generated by the nel launcher (and deleted) when the .cfg or .exe has been changed in the root directory
58 static const string UpdateNelLauncherBatchFilename = "updt_nl.bat";
60 // This file must be in the patch server, will be execute after killing nel_launcher in the root directory
61 static const string RelaunchNelLauncherBatchFilename = "rlnch_nl.bat";
63 // This file must be in the patch server, will be execute after the patching and waiting the end of the .bat to continue to execute the launcher in the root directory
64 static const string FinalizeNelLauncherBatchFilename = "fnlz_ptc.bat";
70 struct CEntry
72 string Filename;
73 uint32 Size;
74 uint32 Date;
75 CEntry(const string &fn, uint32 s, uint32 d) : Filename(fn), Size(s), Date(d) { }
80 // Functions
83 void setRWAccess (const string &filename)
85 if(VerboseLog) nlinfo("setRWAccess to '%s'", filename.c_str());
87 if (!NLMISC::CFile::setRWAccess(filename))
89 nlwarning ("Can't have read/write access to '%s' file : code=%d %s", filename.c_str(), errno, strerror(errno));
90 throw Exception ("Can't have read/write access to '%s' file : code=%d %s (error code 18)", filename.c_str(), errno, strerror(errno));
94 string deleteFile (const string &filename, bool throwException=true)
96 if(VerboseLog) nlinfo("delete file '%s'", filename.c_str());
98 if (!NLMISC::CFile::deleteFile(filename))
100 string str = toString("Can't delete '%s' file : code=%d %s (error code 19)", filename.c_str(), errno, strerror(errno));
101 nlwarning (str.c_str());
102 if(throwException)
103 throw Exception (str);
104 return str;
106 return "";
109 void setVersion(const std::string &version)
111 if(VerboseLog) nlinfo("setVersion to '%s'", version.c_str());
113 string fn = "VERSION";
115 setRWAccess(fn);
116 FILE *fp = fopen (fn.c_str(), "wb");
117 if (fp == NULL)
119 throw Exception ("Can't open file '%s' : code=%d %s (error code 20)", fn.c_str (), errno, strerror(errno));
122 if (fputs (version.c_str (), fp) == EOF)
124 throw Exception ("Can't write file '%s' : code=%d %s (error code 21)", fn.c_str (), errno, strerror(errno));
126 fclose (fp);
129 string getVersion()
131 if(VerboseLog) nlinfo("getVersion");
133 string fn = "VERSION";
134 FILE *fp = fopen (fn.c_str (), "rb");
135 if (fp != NULL)
137 char ver[1000];
138 if (fgets (ver, 998, fp) != NULL)
140 return ver;
142 else
144 throw Exception ("Can't read file '%s' : code=%d %s (error code 22)", fn.c_str (), errno, strerror(errno));
146 fclose (fp);
148 else
150 nlwarning ("Can't open file '%s' : code=%d %s", fn.c_str (), errno, strerror(errno));
152 return "";
155 static string currentFile;
156 int myProgressFunc(void *foo, double t, double d, double ultotal, double ulnow);
158 class CPatchThread : public IRunnable
160 public:
162 CPatchThread(const string &sp, const string &sv, const std::string &urlOk, const std::string &urlFailed, const std::string &logSeparator) :
163 ServerPath (sp), ServerVersion(sv), UrlOk(urlOk), UrlFailed(urlFailed), Ended(false), StateChanged(true), LogSeparator(logSeparator)
167 bool Ended; // true if the thread have ended the patch
168 bool PatchOk; // true if the patch was good
169 string Url; // url to display after the patch
171 string State;
172 string StateLog;
173 bool StateChanged;
175 private:
177 string ClientRootPath; // the root client path (c:\ryzom)
178 string ClientPatchPath; // the patch path (c:\ryzom\patch)
179 string ServerRootPath; // the root server path (http://www.toto.com)
180 string DisplayedServerRootPath; // contains the serverpath without login and password
182 // get a file and decompress it in the patch directory
183 void getFile (const CEntry &e)
185 string path = ClientPatchPath + e.Filename;
186 nlinfo ("Get the file from '%s' to '%s'", string(DisplayedServerRootPath+e.Filename).c_str(), path.c_str());
187 // get the new file
188 downloadFile (ServerRootPath+e.Filename+".ngz", path+".ngz");
189 // decompress it
190 decompressFile (path+".ngz", e.Date);
194 void run ()
196 nlinfo("CPathThread::run called");
200 CurrentFilesToGet = 0;
201 CurrentBytesToGet = 0;
202 TotalFilesToGet = 0;
203 TotalBytesToGet = 0;
205 bool executeFinalizeBat = false;
207 ClientRootPath = "./";
208 ClientPatchPath = "./patch/";
209 ServerRootPath = CPath::standardizePath (ServerPath)+ServerVersion+"/";
210 DisplayedServerRootPath; // contains the serverpath without login and password
212 uint pos = ServerRootPath.find ("@");
213 if (pos != string::npos)
215 DisplayedServerRootPath = "http://"+ServerRootPath.substr (pos+1);
217 else
219 DisplayedServerRootPath = ServerRootPath;
222 setState(true, true, "Patching from '%s'", DisplayedServerRootPath.c_str());
224 // create the patch directory if not exists
225 if (!NLMISC::CFile::isExists ("patch"))
227 setState(true, true, "Creating patch directory");
228 if (_mkdir ("patch") == -1)
230 throw Exception ("Can't create patch directory : code=%d %s (error code 23)", errno, strerror(errno));
234 // first, get the file that contains all files (dir.ngz)
235 deleteFile (DirFilename.c_str(), false);
236 downloadFile (ServerRootPath+DirFilename, DirFilename);
238 // now parse the file
239 gzFile gz = gzopen (DirFilename.c_str (), "rb");
240 if (gz == NULL)
242 int gzerrno;
243 const char *gzerr = gzerror (gz, &gzerrno);
244 throw Exception ("Can't open file '%s': code=%d %s (error code 24)", DirFilename.c_str(), gzerrno, gzerr);
247 vector<CEntry> filesList;
248 vector<CEntry> needToGetFilesList;
250 setState(true, true, "Parsing %s...", DirFilename.c_str());
252 char buffer[2000];
253 if (gzgets (gz, buffer, 2000) == NULL)
255 int gzerrno;
256 const char *gzerr = gzerror (gz, &gzerrno);
257 throw Exception ("Can't read header of'%s' : code=%d %s (error code 25)", DirFilename.c_str(), gzerrno, gzerr);
260 if (string(buffer) != "FILESLIST\n")
262 throw Exception ("%s has not a valid content '%s' : code=8888 (error code 26)", DirFilename.c_str(), buffer);
265 while (!gzeof(gz))
267 if (gzgets (gz, buffer, 2000) == NULL)
269 int gzerrno;
270 const char *gzerr = gzerror (gz, &gzerrno);
271 throw Exception ("Can't read '%s' : code=%d %s (error code 27)", DirFilename.c_str(), gzerrno, gzerr);
274 string b = buffer;
275 uint pos1 = b.find ("/");
276 uint pos2 = b.find ("/", pos1+1);
278 if (pos1 != string::npos || pos2 != string::npos)
280 string filename = b.substr (0, pos1);
281 uint32 size = atoi(b.substr (pos1+1, pos2-pos1).c_str());
282 uint32 date = atoi(b.substr (pos2+1).c_str());
284 string path = ClientRootPath+filename;
285 if (!NLMISC::CFile::fileExists (path))
287 path = ClientPatchPath + filename;
290 // we have to check around 2 seconds because some windows file system store
291 // their time is less bits
292 uint32 moddate = NLMISC::CFile::getFileModificationDate(path);
293 uint32 delta = (moddate>date) ? (moddate-date) : (date-moddate);
295 nlinfo("'%s' local %u %uB server %u %uB delta %u", filename.c_str(), moddate, NLMISC::CFile::getFileSize (path), date, size, delta);
297 if (delta > 2 || NLMISC::CFile::getFileSize (path) != size)
299 nlinfo("file '%s' is not the same date/size than on server, need to get it", filename.c_str());
300 needToGetFilesList.push_back (CEntry(filename, size, date));
301 TotalFilesToGet++;
302 TotalBytesToGet += size;
304 filesList.push_back (CEntry(filename, size, date));
307 gzclose (gz);
309 // if we need to update nel_launcher.exe don't patch other file now, get
310 // nel_launcher.exe and relaunch it now
312 bool patchExe = false, patchCfg = false, patchBat = false;
314 uint i;
316 for (i = 0; i < needToGetFilesList.size(); i++)
318 string fn = needToGetFilesList[i].Filename;
320 if (fn == NelLauncherFilename)
322 getFile (needToGetFilesList[i]);
323 patchExe = true;
325 else if (fn == NelLauncherConfigFilename)
327 getFile (needToGetFilesList[i]);
328 patchCfg = true;
330 else if (fn == RelaunchNelLauncherBatchFilename)
332 getFile (needToGetFilesList[i]);
333 patchBat = true;
337 if (patchBat)
339 setState (true, true, "Launching %s", RelaunchNelLauncherBatchFilename.c_str());
341 //if (_execlp ("update_nel_launcher.bat", "update_nel_launcher.bat", NULL) == -1)
342 STARTUPINFO si;
343 PROCESS_INFORMATION pi;
345 ZeroMemory( &si, sizeof(si) );
346 // Flag permettant de prendre en compte wShowWindow
347 si.dwFlags = STARTF_USESHOWWINDOW;
348 si.wShowWindow = SW_HIDE;
350 si.cb = sizeof(si);
352 ZeroMemory( &pi, sizeof(pi) );
354 // Start the child process.
355 if( !CreateProcess( NULL, // No module name (use command line).
356 (char*)RelaunchNelLauncherBatchFilename.c_str(), // Command line.
357 NULL, // Process handle not inheritable.
358 NULL, // Thread handle not inheritable.
359 FALSE, // Set handle inheritance to FALSE.
360 0, // No creation flags.
361 NULL, // Use parent's environment block.
362 NULL, // Use parent's starting directory.
363 &si, // Pointer to STARTUPINFO structure.
364 &pi ) // Pointer to PROCESS_INFORMATION structure.
367 // error occurs during the launch
368 string str = toString("Can't execute '%s': code=%d %s (error code 28)", RelaunchNelLauncherBatchFilename.c_str(), errno, strerror(errno));
369 throw Exception (str);
372 // Close process and thread handles.
373 CloseHandle( pi.hProcess );
374 CloseHandle( pi.hThread );
376 exit(0);
378 else if (patchExe || patchCfg)
380 FILE *fp = fopen (UpdateNelLauncherBatchFilename.c_str(), "wt");
381 if (fp == NULL)
383 string err = toString("Can't open file '%s' for writing: code=%d %s (error code 29)", UpdateNelLauncherBatchFilename.c_str(), errno, strerror(errno));
384 throw Exception (err);
387 fprintf(fp, "@echo off\n");
389 if (patchExe)
391 nlinfo ("Need to special patch '%s'",NelLauncherFilename.c_str());
393 fprintf(fp, ":loopexe\n");
394 fprintf(fp, "attrib -r -a -s -h %s\n", NelLauncherFilename.c_str());
395 fprintf(fp, "del %s\n", NelLauncherFilename.c_str());
396 fprintf(fp, "if exist %s goto loopexe\n", NelLauncherFilename.c_str());
397 fprintf(fp, "move patch\\%s .\n", NelLauncherFilename.c_str());
400 if (patchCfg)
402 nlinfo ("Need to special patch '%s'",NelLauncherConfigFilename.c_str());
404 fprintf(fp, ":loopcfg\n");
405 fprintf(fp, "attrib -r -a -s -h %s\n", NelLauncherConfigFilename.c_str());
406 fprintf(fp, "del %s\n", NelLauncherConfigFilename.c_str());
407 fprintf(fp, "if exist %s goto loopcfg\n", NelLauncherConfigFilename.c_str());
408 fprintf(fp, "move patch\\%s .\n", NelLauncherConfigFilename.c_str());
411 fprintf(fp, "start %s\n", NelLauncherFilename.c_str());
413 fclose (fp);
415 // remove the files list file
416 setState (true, true, "Deleting %s", DirFilename.c_str());
417 string err = deleteFile (DirFilename, false);
418 if (!err.empty()) setState(true, true, err.c_str());
420 // launching the .bat
421 setState (true, true, "Launching %s", UpdateNelLauncherBatchFilename.c_str());
423 //if (_execlp ("update_nel_launcher.bat", "update_nel_launcher.bat", NULL) == -1)
424 STARTUPINFO si;
425 PROCESS_INFORMATION pi;
427 ZeroMemory( &si, sizeof(si) );
428 // Flag permettant de prendre en compte wShowWindow
429 si.dwFlags = STARTF_USESHOWWINDOW;
430 si.wShowWindow = SW_HIDE;
432 si.cb = sizeof(si);
434 ZeroMemory( &pi, sizeof(pi) );
436 // Start the child process.
437 if( !CreateProcess( NULL, // No module name (use command line).
438 (char*)UpdateNelLauncherBatchFilename.c_str(), // Command line.
439 NULL, // Process handle not inheritable.
440 NULL, // Thread handle not inheritable.
441 FALSE, // Set handle inheritance to FALSE.
442 0, // No creation flags.
443 NULL, // Use parent's environment block.
444 NULL, // Use parent's starting directory.
445 &si, // Pointer to STARTUPINFO structure.
446 &pi ) // Pointer to PROCESS_INFORMATION structure.
449 // error occurs during the launch
450 string str = toString("Can't execute '%s': code=%d %s (error code 30)", UpdateNelLauncherBatchFilename.c_str(), errno, strerror(errno));
451 throw Exception (str);
454 // Close process and thread handles.
455 CloseHandle( pi.hProcess );
456 CloseHandle( pi.hThread );
458 quit();
461 // get files if necessary
462 for (i = 0; i < needToGetFilesList.size (); i++)
464 // we already get these file, don't get it again (should never happen because already get on the last launch)
465 if (needToGetFilesList[i].Filename == NelLauncherFilename ||
466 needToGetFilesList[i].Filename == NelLauncherConfigFilename ||
467 needToGetFilesList[i].Filename == RelaunchNelLauncherBatchFilename
469 continue;
471 if (needToGetFilesList[i].Filename == FinalizeNelLauncherBatchFilename)
473 executeFinalizeBat = true;
476 // put the file in the ryzom patch directory
477 string path = ClientPatchPath + needToGetFilesList[i].Filename;
479 //nldebug ("path '%s' -> %d %s", path.c_str(), NLMISC::CFile::fileExists (ClientRootPath + needToGetFilesList[i].Filename), strlwr(NLMISC::CFile::getExtension(needToGetFilesList[i].Filename)).c_str());
481 // move dll exe and already existing file in the root directory
482 if (NLMISC::CFile::fileExists (ClientRootPath + needToGetFilesList[i].Filename) ||
483 strlwr(NLMISC::CFile::getExtension(needToGetFilesList[i].Filename)) == "dll" ||
484 strlwr(NLMISC::CFile::getExtension(needToGetFilesList[i].Filename)) == "exe")
486 path = ClientRootPath + needToGetFilesList[i].Filename;
489 //setState (true, true, "Get the file from '%s' to '%s'", string(DisplayedServerRootPath+needToGetFilesList[i].Filename).c_str(), path.c_str());
491 // get the new file
492 downloadFile (ServerRootPath+needToGetFilesList[i].Filename+".ngz", path+".ngz");
493 // decompress it
494 decompressFile (path+".ngz", needToGetFilesList[i].Date);
497 if (RootInternet != NULL)
499 InternetCloseHandle(RootInternet);
500 RootInternet = NULL;
503 // now, we have to delete files that are not in the server list
505 setState(true, true, "Scanning patch directory");
506 vector<string> res;
507 CPath::getPathContent(ClientPatchPath, false, false, true, res);
509 for (i = 0; i < res.size (); i++)
511 string fn = strlwr(NLMISC::CFile::getFilename (res[i]));
512 uint j;
513 for (j = 0; j < filesList.size (); j++)
515 if (fn == strlwr(filesList[j].Filename))
517 break;
520 if (j == filesList.size ())
522 string file = ClientPatchPath+NLMISC::CFile::getFilename (res[i]);
523 setState(true, true, "Deleting %s", file.c_str());
524 string err = deleteFile (file, false);
525 if (!err.empty()) setState(true, true, err.c_str());
529 // remove the files list file
530 setState (true, true, "Deleting %s", DirFilename.c_str());
531 string err = deleteFile (DirFilename, false);
532 if (!err.empty()) setState(true, true, err.c_str());
534 // now that all is ok, we set the new client version
535 setState (true, true, "set client version to %s", ServerVersion.c_str ());
536 setVersion (ServerVersion);
538 if (executeFinalizeBat)
540 // execute the configurator if necessary
541 string fn = "patch\\"+FinalizeNelLauncherBatchFilename;
542 setState (true, true, "Launching %s", fn.c_str());
543 system(fn.c_str());
546 // it s the end of the patch process
547 setState (true, true, "Patching completed");
549 Url = UrlOk;
550 PatchOk = true;
551 Ended = true;
553 catch (Exception &e)
555 //Url = UrlFailed;
556 Url = e.what();
557 PatchOk = false;
558 Ended = true;
562 void decompressFile (const string &filename, uint32 date)
564 setState(true, true, "Decompressing %s...", NLMISC::CFile::getFilename(filename).c_str ());
566 if(VerboseLog) nlinfo("Calling gzopen('%s','rb')", filename.c_str());
567 gzFile gz = gzopen (filename.c_str (), "rb");
568 if (gz == NULL)
570 string err = toString("Can't open compressed file '%s' : ", filename.c_str());
571 if(errno == 0)
573 // gzerror
574 int gzerrno;
575 const char *gzerr = gzerror (gz, &gzerrno);
576 err += toString("code=%d %s", gzerrno, gzerr);
578 else
580 err += toString("code=%d %s", errno, strerror (errno));
582 err += " (error code 31)";
583 deleteFile (filename);
584 throw Exception (err);
587 string dest = filename.substr(0, filename.size ()-4);
588 setRWAccess(dest);
589 if(VerboseLog) nlinfo("Calling fopen('%s','wb')", dest.c_str());
590 FILE *fp = nlfopen (dest, "wb");
591 if (fp == NULL)
593 string err = toString("Can't open file '%s' : code=%d %s, (error code 32)", dest.c_str(), errno, strerror(errno));
595 gzclose(gz);
596 deleteFile (filename);
597 throw Exception (err);
600 if(VerboseLog) nlinfo("Entering the while loop decompression");
602 uint32 currentSize = 0;
603 uint8 buffer[10000];
604 while (!gzeof(gz))
606 if(VerboseLog) nlinfo("Calling gzread");
607 int res = gzread (gz, buffer, 10000);
608 if(VerboseLog) nlinfo("gzread returns %d", res);
609 if (res == -1)
611 int gzerrno;
612 const char *gzerr = gzerror (gz, &gzerrno);
613 gzclose(gz);
614 fclose(fp);
615 //deleteFile (filename);
616 throw Exception ("Can't read compressed file '%s' (after %d bytes) : code=%d %s, (error code 33)", filename.c_str(), currentSize, gzerrno, gzerr);
619 currentSize += res;
621 if(VerboseLog) nlinfo("Calling fwrite for %d bytes", res);
622 int res2 = fwrite (buffer, 1, res, fp);
623 if(VerboseLog) nlinfo("fwrite returns %d", res2);
624 if (res2 != res)
626 string err = toString("Can't write file '%s' : code=%d %s (error code 34)", dest.c_str(), errno, strerror(errno));
628 gzclose(gz);
629 fclose(fp);
630 deleteFile (filename);
631 throw Exception (err);
635 if(VerboseLog) nlinfo("Exiting the while loop decompression");
637 if(VerboseLog) nlinfo("Calling gzclose");
638 gzclose(gz);
639 if(VerboseLog) nlinfo("Calling fclose");
640 fclose(fp);
641 deleteFile(filename);
643 // change the file time for having the same as the server side
645 if(date != 0)
647 _utimbuf utb;
648 utb.actime = utb.modtime = date;
649 setRWAccess(dest);
650 if(VerboseLog) nlinfo("Calling _utime");
651 nlinfo("'%s' changing the mod date from %u into %u", dest.c_str(), NLMISC::CFile::getFileModificationDate (dest), date);
652 if (_utime (dest.c_str (), &utb) == -1)
654 nlwarning ("Can't change file time for '%s' : code=%d %s", dest.c_str (), errno, strerror(errno));
656 nlinfo("'%s' now the date is %u", dest.c_str(), NLMISC::CFile::getFileModificationDate (dest));
658 if(VerboseLog) nlinfo("Exiting the decompressing file");
661 void downloadFileWithCurl (const string &source, const string &dest)
663 #ifdef USE_CURL
665 if(VerboseLog) nlinfo("downloadFileWithCurl file '%s'", dest.c_str());
667 // user agent = nel_launcher
669 CURL *curl;
670 CURLcode res;
672 setState(true, true, "Getting %s", NLMISC::CFile::getFilename (source).c_str ());
673 currentFile = NLMISC::CFile::getFilename (source);
675 curl_global_init(CURL_GLOBAL_ALL);
676 curl = curl_easy_init();
677 if(curl == NULL)
679 // file not found, delete local file
680 throw Exception ("curl init failed");
682 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
683 curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, myProgressFunc);
684 curl_easy_setopt(curl, CURLOPT_URL, source.c_str());
686 // create the local file
687 setRWAccess(dest);
688 FILE *fp = fopen (dest.c_str(), "wb");
689 if (fp == NULL)
691 throw Exception ("Can't open file '%s' for writing: code=%d %s (error code 37)", dest.c_str (), errno, strerror(errno));
693 curl_easy_setopt(curl, CURLOPT_FILE, fp);
695 CurrentFilesToGet++;
697 res = curl_easy_perform(curl);
699 long r;
700 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &r);
702 curl_easy_cleanup(curl);
704 fclose(fp);
705 curl_global_cleanup();
707 currentFile = "";
709 if(CURLE_FTP_COULDNT_RETR_FILE == res)
711 // file not found, delete local file
712 NLMISC::CFile::deleteFile(dest.c_str());
713 throw Exception ("curl download failed: (ec %d %d)", res, r);
716 if(CURLE_OK != res)
718 NLMISC::CFile::deleteFile(dest.c_str());
719 throw Exception ("curl download failed: (ec %d %d)", res, r);
722 if(r == 404)
724 // file not found, delete it
725 NLMISC::CFile::deleteFile(dest.c_str());
726 throw Exception ("curl download failed: (ec %d %d)", res, r);
728 #else
729 throw Exception("USE_CURL is not defined, no curl method");
730 #endif
733 void downloadFile (const string &source, const string &dest)
735 // only use curl to test
738 downloadFileWithCurl(source, dest);
739 // download ok, don't continue;
740 return;
742 catch(Exception &e)
744 nlwarning("downloadFileWithCurl failed '%s', try with windows method", e.what());
747 const uint32 bufferSize = 8000;
748 uint8 buffer[bufferSize];
750 if(VerboseLog) nlinfo("downloadFile '%s'", dest.c_str());
752 if (RootInternet == NULL)
754 RootInternet = InternetOpen("nel_launcher", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
755 if (RootInternet == NULL)
757 // error
758 LPVOID lpMsgBuf;
759 string errorstr;
760 DWORD errcode = GetLastError ();
761 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
762 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
763 (LPTSTR) &lpMsgBuf, 0, NULL) > 0)
765 errorstr = (LPCTSTR)lpMsgBuf;
766 LocalFree(lpMsgBuf);
768 else
770 errorstr = "FormatMessage can't get the message";
773 throw Exception ("InternetOpen() failed: %s (ec %d) (error code 35)", errorstr.c_str(), errcode);
777 HINTERNET hUrlDump = InternetOpenUrl(RootInternet, source.c_str(), NULL, NULL, INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_RAW_DATA, 0);
778 if (hUrlDump == NULL)
780 // error
781 LPVOID lpMsgBuf;
782 string errorstr;
783 DWORD errcode = GetLastError ();
784 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
785 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
786 (LPTSTR) &lpMsgBuf, 0, NULL) > 0)
788 errorstr = (LPCTSTR)lpMsgBuf;
789 LocalFree(lpMsgBuf);
791 else
793 errorstr = "FormatMessage can't get the message";
796 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d) (error code 36)", source.c_str (), errorstr.c_str(), errcode);
799 setRWAccess(dest);
800 FILE *fp = fopen (dest.c_str(), "wb");
801 if (fp == NULL)
803 throw Exception ("Can't open file '%s' for writing: code=%d %s (error code 37)", dest.c_str (), errno, strerror(errno));
806 CurrentFilesToGet++;
808 setState(true, true, "Getting %s", NLMISC::CFile::getFilename (source).c_str ());
812 DWORD realSize;
814 if(!InternetReadFile(hUrlDump,(LPVOID)buffer, bufferSize, &realSize))
816 LPVOID lpMsgBuf;
817 string errorstr;
818 DWORD errcode = GetLastError ();
819 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
820 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
821 (LPTSTR) &lpMsgBuf, 0, NULL) > 0)
823 errorstr = (LPCTSTR)lpMsgBuf;
824 LocalFree(lpMsgBuf);
826 else
828 errorstr = "FormatMessage can't get the message";
831 fclose(fp);
832 deleteFile (dest);
834 throw Exception ("InternetOpenUrl() failed on file '%s': %s (ec %d) (error code 38)", source.c_str (), errorstr.c_str(), errcode);
836 else
838 if (realSize == 0)
840 // download complete successfully
841 break;
844 int res2 = fwrite (buffer, 1, realSize, fp);
845 if ((DWORD)res2 != realSize)
847 string err = toString("Can't write file '%s' : code=%d %s (error code 39)", dest.c_str(), errno, strerror(errno));
849 fclose(fp);
850 deleteFile (dest);
851 throw Exception (err);
854 CurrentBytesToGet += realSize;
856 if (TotalBytesToGet == 0 && TotalFilesToGet == 0)
857 setState(false, false, "Getting %s, %d bytes downloaded", NLMISC::CFile::getFilename (source).c_str (), CurrentBytesToGet);
858 else
859 setState(false, false, "Getting file %d on %d, %d bytes, filename %s", CurrentFilesToGet, TotalFilesToGet, CurrentBytesToGet, NLMISC::CFile::getFilename (source).c_str ());
863 while (true);
865 fclose (fp);
866 if (!InternetCloseHandle(hUrlDump))
868 LPVOID lpMsgBuf;
869 string errorstr;
870 DWORD errcode = GetLastError ();
871 if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
872 errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
873 (LPTSTR) &lpMsgBuf, 0, NULL) > 0)
875 errorstr = (LPCTSTR)lpMsgBuf;
876 LocalFree(lpMsgBuf);
878 else
880 errorstr = "FormatMessage can't get the message";
883 throw Exception ("InternetCloseHandle() failed on file '%s': %s (ec %d) (error code 40)", source.c_str (), errorstr.c_str(), errcode);
888 void setState (bool info, bool log, const char *format, ...)
890 char *str;
891 NLMISC_CONVERT_VARGS (str, format, 256);
892 if (info && VerboseLog)
893 nlinfo (str);
894 State = str;
895 if(log)
897 StateLog += str;
898 StateLog += LogSeparator;
900 StateChanged = true;
903 string LogSeparator;
905 string ServerPath;
906 string ServerVersion;
908 string UrlOk;
909 string UrlFailed;
911 friend int myProgressFunc(void *foo, double t, double d, double ultotal, double ulnow);
913 public:
914 uint TotalFilesToGet;
915 uint TotalBytesToGet;
916 uint CurrentFilesToGet;
917 uint CurrentBytesToGet;
920 CPatchThread *PatchThread = NULL;
921 IThread *thread = NULL;
923 int myProgressFunc(void *foo, double t, double d, double ultotal, double ulnow)
925 double pour1 = t!=0.0?d*100.0/t:0.0;
926 double pour2 = ultotal!=0.0?ulnow*100.0/ultotal:0.0;
927 //changeSplash("Progression : dl %s / %s (%5.02f %%) ul %s / %s (%5.02f %%)\n", NLMISC::bytesToHumanReadable((uint32)d).c_str(), NLMISC::bytesToHumanReadable((uint32)t).c_str(), pour1, NLMISC::bytesToHumanReadable((uint32)ulnow).c_str(), NLMISC::bytesToHumanReadable((uint32)ultotal).c_str(), pour2);
928 if(PatchThread)
929 PatchThread->setState(false, false, "Getting file %s : %s / %s (%5.02f %%)", currentFile.c_str(), NLMISC::bytesToHumanReadable((uint32)d).c_str(), NLMISC::bytesToHumanReadable((uint32)t).c_str(), pour1);
930 return 0;
933 void startPatchThread (const std::string &serverPath, const std::string &serverVersion, const std::string &urlOk, const std::string &urlFailed, const std::string &logSeparator)
935 if (PatchThread != NULL)
937 nlwarning ("patch thread already running");
938 return;
941 PatchThread = new CPatchThread (serverPath, serverVersion, urlOk, urlFailed, logSeparator);
942 nlassert (PatchThread != NULL);
944 thread = IThread::create (PatchThread);
945 nlassert (thread != NULL);
946 thread->start ();
949 bool patchEnded (string &url, bool &ok)
951 nlassert (PatchThread != NULL);
953 bool end = PatchThread->Ended;
954 if (end)
956 url = PatchThread->Url;
957 ok = PatchThread->PatchOk;
959 delete PatchThread;
960 PatchThread = NULL;
963 return end;
966 bool patchState (string &state, std::string &stateLog)
968 if (PatchThread == NULL)
969 return false;
971 bool statechanged = PatchThread->StateChanged;
972 if (statechanged)
974 state = PatchThread->State;
975 stateLog = PatchThread->StateLog;
976 PatchThread->StateChanged = false;
979 return statechanged;
982 int getTotalFilesToGet()
984 if(PatchThread)
985 return PatchThread->TotalFilesToGet;
986 return 0;
989 int getCurrentFilesToGet()
991 if(PatchThread)
992 return PatchThread->CurrentFilesToGet;
993 return 0;
996 int getTotalBytesToGet()
998 if(PatchThread)
999 return PatchThread->TotalBytesToGet;
1000 return 0;
1003 int getCurrentBytesToGet()
1005 if(PatchThread)
1006 return PatchThread->CurrentBytesToGet;
1007 return 0;
1010 void stopPatch()
1012 if(PatchThread && thread)
1014 thread->terminate();
1015 delete thread;
1016 thread = NULL;
1017 delete PatchThread;
1018 PatchThread = NULL;