TDME2  1.9.200
Installer.cpp
Go to the documentation of this file.
2 
3 #include <stdio.h>
4 #include <stdlib.h>
5 
6 #include <algorithm>
7 #include <memory>
8 #include <string>
9 #include <unordered_map>
10 #include <unordered_set>
11 #include <vector>
12 
13 #include <tdme/tdme.h>
15 #include <tdme/engine/Color4.h>
16 #include <tdme/engine/Engine.h>
17 #include <tdme/engine/Version.h>
29 #include <tdme/gui/GUI.h>
30 #include <tdme/gui/GUIParser.h>
41 #include <tdme/utilities/Action.h>
42 #include <tdme/utilities/Console.h>
45 #include <tdme/utilities/Integer.h>
50 #include <tdme/utilities/Time.h>
51 
53 
54 using std::distance;
55 using std::make_unique;
56 using std::reverse;
57 using std::sort;
58 using std::string;
59 using std::to_string;
60 using std::unique;
61 using std::unique_ptr;
62 using std::unordered_map;
63 using std::unordered_set;
64 using std::vector;
65 
82 using tdme::gui::GUI;
104 
105 Installer::Installer(): installThreadMutex("install-thread-mutex")
106 {
107  Application::setLimitFPS(true);
108  Tools::loadSettings(this);
109  this->engine = Engine::getInstance();
110  this->popUps = make_unique<PopUps>();
113  installed = false;
114  remountInstallerArchive = false;
115 }
116 
118  try {
119  vector<string> installedComponents;
120  try {
121  FileSystem::getStandardFileSystem()->getContentAsStringArray(".", "install.components.db", installedComponents);
122  } catch (Exception& exception) {
123  }
124 
125  if (timestamp.empty() == true) {
126  try {
127  timestamp = FileSystem::getStandardFileSystem()->getContentAsString(".", "install.version.db");
128  } catch (Exception& exception) {
129  }
130  }
131 
132  string installFolder;
133  try {
134  vector<string> log;
135  FileSystem::getStandardFileSystem()->getContentAsStringArray(".", "install.files.db", log);
136  if (log.size() > 0) installFolder = log[0];
137  } catch (Exception& exception) {
138  }
139 
140  //
141  popUps->dispose();
142  engine->getGUI()->reset();
143  popUps->initialize();
144 
145  installerProperties.load("resources/installer", "installer.properties");
146  if (installerProperties.get("installer_version", "") != "1.9.176") throw ExceptionBase("Installer is outdated. Please uninstall and update installer");
147  unordered_map<string, string> variables = {
148  {"name", installerProperties.get("name", "TDME2 based application")},
149  {"diskspace", installerProperties.get("diskspace", "Unknown")},
150  {"installfolder", installFolder.empty() == true?homeFolder + "/Applications/" + installerProperties.get("install_path", "TDME2-based-application"):installFolder}
151  };
152  engine->getGUI()->addScreen(
153  "installer_welcome",
154  GUIParser::parse(
155  "resources/installer",
156  "installer_welcome.xml",
157  variables
158  )
159  );
160  engine->getGUI()->addScreen(
161  "installer_license",
162  GUIParser::parse(
163  "resources/installer",
164  "installer_license.xml",
165  variables
166  )
167  );
168  dynamic_cast<GUIStyledTextNode*>(engine->getGUI()->getScreen("installer_license")->getNodeById("licence_text"))->setText(MutableString(FileSystem::getInstance()->getContentAsString(".", "LICENSE")));
169  engine->getGUI()->addScreen(
170  "installer_components",
171  GUIParser::parse(
172  "resources/installer",
173  "installer_components.xml",
174  variables
175  )
176  );
177  string componentsXML = "<space height=\"10\" />\n";
178  for (auto componentIdx = 1; true; componentIdx++) {
179  auto componentName = installerProperties.get("component" + to_string(componentIdx), "");
180  auto componentRequired = StringTools::trim(StringTools::toLowerCase(installerProperties.get("component" + to_string(componentIdx) + "_required", "false"))) == "true";
181  auto componentInstalled = false;
182  for (const auto& installedComponentName: installedComponents) {
183  if (installedComponentName == componentName) {
184  componentInstalled = true;
185  break;
186  }
187  }
188  if (componentName.empty() == true) break;
189  componentsXML+=
190  string("<element id=\"component" + to_string(componentIdx) + "\" width=\"100%\" height=\"25\">\n") +
191  string(" <layout width=\"100%\" alignment=\"horizontal\">\n") +
192  string(" <space width=\"10\" />\n") +
193  string(" <checkbox id=\"checkbox_component" + to_string(componentIdx) + "\" name=\"checkbox_component" + to_string(componentIdx) + "\" value=\"1\" selected=\"" + (componentRequired == true || componentInstalled == true?"true":"false") + "\" disabled=\"" + (componentRequired == true?"true":"false") + "\" />\n") +
194  string(" <space width=\"10\" />\n") +
195  string(" <text width=\"*\" font=\"{$font.default}\" size=\"16\" text=\"" + GUIParser::escape(componentName) + "\" color=\"{$color.font_normal}\" height=\"100%\" vertical-align=\"center\" />\n") +
196  string(" </layout>\n") +
197  string("</element>\n");
198  }
199  dynamic_cast<GUIParentNode*>(engine->getGUI()->getScreen("installer_components")->getNodeById("scrollarea_components_inner"))->replaceSubNodes(
200  componentsXML,
201  true
202  );
203  engine->getGUI()->addScreen(
204  "installer_folder",
205  GUIParser::parse(
206  "resources/installer",
207  "installer_folder.xml",
208  variables
209  )
210  );
211  engine->getGUI()->addScreen(
212  "installer_installing",
213  GUIParser::parse(
214  "resources/installer",
215  "installer_installing.xml",
216  variables
217  )
218  );
219  engine->getGUI()->addScreen(
220  "installer_finished",
221  GUIParser::parse(
222  "resources/installer",
223  "installer_finished.xml",
224  variables
225  )
226  );
227  engine->getGUI()->addScreen(
228  "installer_welcome2",
229  GUIParser::parse(
230  "resources/installer",
231  "installer_welcome2.xml",
232  variables
233  )
234  );
235  engine->getGUI()->addScreen(
236  "installer_uninstalling",
237  GUIParser::parse(
238  "resources/installer",
239  "installer_uninstalling.xml",
240  variables
241  )
242  );
243  engine->getGUI()->getScreen("installer_welcome")->addActionListener(this);
244  engine->getGUI()->getScreen("installer_welcome")->addChangeListener(this);
245  engine->getGUI()->getScreen("installer_license")->addActionListener(this);
246  engine->getGUI()->getScreen("installer_license")->addChangeListener(this);
247  engine->getGUI()->getScreen("installer_components")->addActionListener(this);
248  engine->getGUI()->getScreen("installer_components")->addChangeListener(this);
249  engine->getGUI()->getScreen("installer_folder")->addActionListener(this);
250  engine->getGUI()->getScreen("installer_folder")->addChangeListener(this);
251  engine->getGUI()->getScreen("installer_installing")->addActionListener(this);
252  engine->getGUI()->getScreen("installer_installing")->addChangeListener(this);
253  engine->getGUI()->getScreen("installer_finished")->addActionListener(this);
254  engine->getGUI()->getScreen("installer_finished")->addChangeListener(this);
255  engine->getGUI()->getScreen("installer_welcome2")->addActionListener(this);
256  engine->getGUI()->getScreen("installer_welcome2")->addChangeListener(this);
257  engine->getGUI()->getScreen("installer_uninstalling")->addActionListener(this);
258  engine->getGUI()->getScreen("installer_uninstalling")->addChangeListener(this);
259  engine->getGUI()->addRenderScreen(installed == false?"installer_welcome":"installer_welcome2");
260  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
261  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
262  } catch (Exception& exception) {
264  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
265  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
266  popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
267  }
268 }
269 
272  switch (screen) {
273  case SCREEN_WELCOME:
274  engine->getGUI()->addRenderScreen("installer_welcome");
275  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
276  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
277  break;
278  case SCREEN_LICENSE:
279  engine->getGUI()->addRenderScreen("installer_license");
280  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
281  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
282  break;
284  {
285  engine->getGUI()->addRenderScreen("installer_installing");
286  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
287  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
288  dynamic_cast<GUITextNode*>(engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Checking for updates ..."));
289  dynamic_cast<GUITextNode*>(engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString("..."));
290  dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
291  class CheckForUpdateThread: public Thread {
292  public:
293  CheckForUpdateThread(Installer* installer): Thread("checkforupdate-thread", true), installer(installer) {
294  }
295 
296  private:
297  Installer* installer;
298 
299  void run() {
300  //
301  Console::println("CheckForUpdateThread::run(): init");
302 
303  //
304  auto currentTimestamp = installer->timestamp;
305 
306  //
307  auto repairHaveLocalFile = false;
308  auto completionFileName = Application::getOSName() + "-" + Application::getCPUName() + "-upload-";
309  if (installer->installerMode == INSTALLERMODE_REPAIR) completionFileName += installer->timestamp;
310 
311  // determine newest component file name
312  if (installer->timestamp.empty() == true || installer->installerMode == INSTALLERMODE_REPAIR) {
313  vector<string> files;
314  FileSystem::getStandardFileSystem()->list("installer", files);
315  for (const auto& file: files) {
316  if (StringTools::startsWith(file, completionFileName) == true) {
317  Console::println("CheckForUpdateThread: Have upload completion file: " + file);
318  installer->timestamp = StringTools::substring(file, completionFileName.size());
319  repairHaveLocalFile = true;
320  }
321  }
322  }
323  if (installer->timestamp.empty() == false) {
324  Console::println("CheckForUpdateThread::run(): filesystem: newest timestamp: " + installer->timestamp);
325  }
326 
327  //
328  auto repository = installer->installerProperties.get("repository", "");
329  if (repository.empty() == false && (installer->installerMode != INSTALLERMODE_REPAIR || repairHaveLocalFile == false)) {
330  string timestampWeb;
331 
332  // check repository via apache index file for now
333  if (installer->installerMode == INSTALLERMODE_INSTALL ||
334  installer->installerMode == INSTALLERMODE_UPDATE) {
335  try {
336  HTTPClient httpClient;
337  httpClient.setMethod(HTTPClient::HTTP_METHOD_GET);
338  httpClient.setURL(repository + "?timestamp=" + to_string(Time::getCurrentMillis()));
339  httpClient.setUsername(installer->installerProperties.get("repository_username", ""));
340  httpClient.setPassword(installer->installerProperties.get("repository_password", ""));
341  httpClient.execute();
342  auto response = httpClient.getResponse().str();
343  auto pos = 0;
344  while ((pos = response.find(completionFileName, pos)) != string::npos) {
345  pos+= completionFileName.size();
346  auto timestampWebNew = StringTools::substring(response, pos, pos + 14);
347  pos+= 14;
348  if ((installer->timestamp.empty() == true && (timestampWeb.empty() == true || timestampWebNew > timestampWeb)) ||
349  (installer->timestamp.empty() == false && ((timestampWeb.empty() == true || timestampWebNew > timestampWeb) && timestampWebNew > installer->timestamp))) {
350  timestampWeb = timestampWebNew;
351  }
352  }
353  } catch (Exception& exception) {
354  Console::println(string("CheckForUpdateThread::run(): An error occurred: ") + exception.what());
355  }
356  } else
357  if (installer->installerMode == INSTALLERMODE_REPAIR && repairHaveLocalFile == false) {
358  timestampWeb = installer->timestamp;
359  }
360 
361  // download archives if newer
362  if (timestampWeb.empty() == false) {
363  Console::println("CheckForUpdateThread::run(): repository: newest timestamp: " + timestampWeb);
364 
365  // we use web installer archives
366  installer->timestamp = timestampWeb;
367 
368  // download them
369  HTTPDownloadClient httpDownloadClient;
370  for (auto componentIdx = 0; true; componentIdx++) {
371  auto componentId = componentIdx == 0?"installer":"component" + to_string(componentIdx);
372  auto componentName = installer->installerProperties.get(componentId, "");
373  if (componentName.empty() == true) break;
374  Console::println("CheckForUpdateThread::run(): Having component: " + to_string(componentIdx) + ": " + componentName);
375  auto componentInclude = installer->installerProperties.get(componentId + "_include", "");
376  if (componentInclude.empty() == true) {
377  Console::println("CheckForUpdateThread::run(): component: " + to_string(componentIdx) + ": missing includes. Skipping.");
378  continue;
379  }
380  auto componentFileName = Application::getOSName() + "-" + Application::getCPUName() + "-" + StringTools::replace(StringTools::replace(componentName, " - ", "-"), " ", "-") + "-" + installer->timestamp + ".ta";
381 
382  //
383  Console::println("CheckForUpdateThread::run(): Component: " + to_string(componentIdx) + ": component file name: " + componentFileName + ": Downloading");
384  //
385  installer->installThreadMutex.lock();
386  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Downloading ..."));
387  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString(componentFileName));
388  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
389  installer->installThreadMutex.unlock();
390 
391  // sha256
392  if (componentIdx > 0) {
393  installer->installThreadMutex.lock();
394  installer->downloadedFiles.push_back("installer/" + componentFileName + ".sha256.download");
395  installer->downloadedFiles.push_back("installer/" + componentFileName + ".sha256");
396  installer->installThreadMutex.unlock();
397  }
398  httpDownloadClient.reset();
399  httpDownloadClient.setUsername(installer->installerProperties.get("repository_username", ""));
400  httpDownloadClient.setPassword(installer->installerProperties.get("repository_password", ""));
401  httpDownloadClient.setFile("installer/" + componentFileName + ".sha256");
402  httpDownloadClient.setURL(installer->installerProperties.get("repository", "") + componentFileName + ".sha256");
403  httpDownloadClient.start();
404  while (httpDownloadClient.isFinished() == false) {
405  installer->installThreadMutex.lock();
406  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(httpDownloadClient.getProgress(), 2));
407  installer->installThreadMutex.unlock();
408  Thread::sleep(250LL);
409  }
410  httpDownloadClient.join();
411  if (httpDownloadClient.getStatusCode() != 200) {
412  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", "File not found in repository: " + componentFileName + ".sha256(" + to_string(httpDownloadClient.getStatusCode()) + ")");
413  //
414  Console::println("CheckForUpdateThread::run(): done");
415  //
416  return;
417  }
418 
419  // archive
420  if (componentIdx > 0) {
421  installer->installThreadMutex.lock();
422  installer->downloadedFiles.push_back("installer/" + componentFileName + ".download");
423  installer->downloadedFiles.push_back("installer/" + componentFileName);
424  installer->installThreadMutex.unlock();
425  }
426  httpDownloadClient.reset();
427  httpDownloadClient.setFile("installer/" + componentFileName);
428  httpDownloadClient.setURL(installer->installerProperties.get("repository", "") + componentFileName);
429  httpDownloadClient.start();
430  while (httpDownloadClient.isFinished() == false) {
431  installer->installThreadMutex.lock();
432  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(httpDownloadClient.getProgress(), 2));
433  installer->installThreadMutex.unlock();
434  Thread::sleep(250LL);
435  }
436  httpDownloadClient.join();
437  if (httpDownloadClient.getStatusCode() != 200) {
438  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", "File not found in repository: " + componentFileName + "(" + to_string(httpDownloadClient.getStatusCode()) + ")");
439  //
440  Console::println("CheckForUpdateThread::run(): done");
441  //
442  return;
443  }
444  }
445  }
446  }
447 
448  // no update available, but update was requested
449  if (installer->installerMode == INSTALLERMODE_UPDATE && installer->timestamp <= currentTimestamp) {
450  installer->popUps->getInfoDialogScreenController()->show("Information", "No update available");
451  //
452  Console::println("CheckForUpdateThread::run(): done");
453  //
454  return;
455  }
456 
457  //
458  installer->installThreadMutex.lock();
459  installer->screen = SCREEN_COMPONENTS;
460  installer->remountInstallerArchive = true;
461  installer->installThreadMutex.unlock();
462 
463  //
464  Console::println("CheckForUpdateThread::run(): done");
465  }
466  };
467  //
468  auto checkForUpdateThread = new CheckForUpdateThread(this);
469  checkForUpdateThread->start();
470  //
471  break;
472  }
473  case SCREEN_COMPONENTS:
474  engine->getGUI()->addRenderScreen("installer_components");
475  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
476  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
477  break;
478  case SCREEN_PATH:
479  engine->getGUI()->addRenderScreen("installer_folder");
480  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
481  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
482  break;
483  case SCREEN_INSTALLING:
484  {
485  engine->getGUI()->addRenderScreen("installer_installing");
486  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
487  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
488  class InstallThread: public Thread {
489  public:
490  InstallThread(Installer* installer): Thread("install-thread", true), installer(installer) {
491  }
492  void run() {
493  //
494  Console::println("InstallThread::run(): init");
495 
496  // we can not just overwrite executables on windows specially the installer exe or the libraries of it
497  // we need to install them with .update suffix and remove the suffix after installer is closed
498  #if defined(_WIN32)
499  vector<string> windowsUpdateRenameFiles;
500  #endif
501 
502  //
503  installer->installThreadMutex.lock();
504  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Initializing ..."));
505  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString("..."));
506  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
507  installer->installThreadMutex.unlock();
508 
509  //
510  auto hadException = false;
511  vector<string> log;
512  vector<string> components;
513  auto installFolder = dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString();
514  try {
515  Installer::createPathRecursively(installFolder);
516  } catch (Exception& exception) {
517  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
518  hadException = true;
519  }
520 
521  //
522  log.push_back(installFolder);
523  if (hadException == false) {
524  // copy installer archive and sha256
525  if (installer->installerMode == INSTALLERMODE_INSTALL) {
526  auto file = dynamic_cast<ArchiveFileSystem*>(FileSystem::getInstance())->getArchiveFileName();
527  {
528  vector<uint8_t> content;
529  Console::println("InstallThread::run(): Installer: Copy: " + file);
530  FileSystem::getStandardFileSystem()->getContent(
531  FileSystem::getStandardFileSystem()->getPathName(file),
532  FileSystem::getStandardFileSystem()->getFileName(file),
533  content
534  );
535  auto generatedFileName = installFolder + "/" + file;
537  FileSystem::getStandardFileSystem()->getPathName(generatedFileName)
538  );
539  FileSystem::getStandardFileSystem()->setContent(
540  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
541  FileSystem::getStandardFileSystem()->getFileName(generatedFileName),
542  content
543  );
544  }
545  file+= ".sha256";
546  {
547  vector<uint8_t> content;
548  Console::println("InstallThread::run(): Installer: Copy: " + file);
549  FileSystem::getStandardFileSystem()->getContent(
550  FileSystem::getStandardFileSystem()->getPathName(file),
551  FileSystem::getStandardFileSystem()->getFileName(file),
552  content
553  );
554  auto generatedFileName = installFolder + "/" + file;
556  FileSystem::getStandardFileSystem()->getPathName(generatedFileName)
557  );
558  FileSystem::getStandardFileSystem()->setContent(
559  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
560  FileSystem::getStandardFileSystem()->getFileName(generatedFileName),
561  content
562  );
563  }
564  }
565  // copy components
566  for (auto componentIdx = 1; true; componentIdx++) {
567  //
568  auto componentName = installer->installerProperties.get("component" + to_string(componentIdx), "");
569  if (componentName.empty() == true) break;
570 
571  // component file name
572  auto componentFileName = Application::getOSName() + "-" + Application::getCPUName() + "-" + StringTools::replace(StringTools::replace(componentName, " - ", "-"), " ", "-") + "-" + installer->timestamp + ".ta";
573 
574  // check if marked
575  installer->installThreadMutex.lock();
576  if (dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_components")->getNodeById("checkbox_component" + to_string(componentIdx)))->getController()->getValue().equals("1") == false) {
577  installer->installThreadMutex.unlock();
578  // delete installer component archive archive
580  installer->installThreadMutex.lock();
581  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
582  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Deleting " + componentName));
583  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString());
584  installer->installThreadMutex.unlock();
585  FileSystem::getStandardFileSystem()->removeFile("installer", componentFileName);
586  installer->installThreadMutex.lock();
587  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(50.0f, 2));
588  installer->installThreadMutex.unlock();
589  FileSystem::getStandardFileSystem()->removeFile("installer", componentFileName + ".sha256");
590  installer->installThreadMutex.lock();
591  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(100.0f, 2));
592  installer->installThreadMutex.unlock();
593  }
594  continue;
595  }
596  installer->installThreadMutex.unlock();
597 
598  //
599  components.push_back(componentName);
600 
601  //
602  Console::println("InstallThread::run(): Having component: " + to_string(componentIdx) + ": " + componentName);
603  auto componentInclude = installer->installerProperties.get("component" + to_string(componentIdx) + "_include", "");
604  if (componentInclude.empty() == true) {
605  Console::println("InstallThread::run(): component: " + to_string(componentIdx) + ": missing includes. Skipping.");
606  continue;
607  }
608  //
609  Console::println("InstallThread::run(): Component: " + to_string(componentIdx) + ": component file name: " + componentFileName);
610  //
611  installer->installThreadMutex.lock();
612  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
613  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString());
614  installer->installThreadMutex.unlock();
615 
616  unique_ptr<ArchiveFileSystem> archiveFileSystem;
617  try {
618  installer->installThreadMutex.lock();
619  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Verifying " + componentName));
620  installer->installThreadMutex.unlock();
621  archiveFileSystem = make_unique<ArchiveFileSystem>("installer/" + componentFileName);
622  if (archiveFileSystem->computeSHA256Hash() != FileSystem::getStandardFileSystem()->getContentAsString("installer", componentFileName + ".sha256")) {
623  throw ExceptionBase("Failed to verify: " + componentFileName + ", remove files in ./installer/ and try again");
624  }
625  installer->installThreadMutex.lock();
626  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Installing " + componentName));
627  installer->installThreadMutex.unlock();
628  vector<string> files;
629  Installer::scanArchive(archiveFileSystem.get(), files);
630  uint64_t totalSize = 0LL;
631  uint64_t doneSize = 0LL;
632  for (const auto& file: files) {
633  totalSize+= archiveFileSystem->getFileSize(
634  archiveFileSystem->getPathName(file),
635  archiveFileSystem->getFileName(file)
636  );
637  }
638  for (const auto& file: files) {
639  vector<uint8_t> content;
640  Console::println("InstallThread::run(): Component: " + to_string(componentIdx) + ": " + file);
641  archiveFileSystem->getContent(
642  archiveFileSystem->getPathName(file),
643  archiveFileSystem->getFileName(file),
644  content
645  );
646  auto generatedFileName = installFolder + "/" + file;
648  FileSystem::getStandardFileSystem()->getPathName(generatedFileName)
649  );
650  #if defined(_WIN32)
651  auto windowsGeneratedFile = generatedFileName;
652  if (
653  (installer->installerMode == INSTALLERMODE_REPAIR ||
654  installer->installerMode == INSTALLERMODE_UPDATE) &&
655  (StringTools::endsWith(StringTools::toLowerCase(generatedFileName), ".exe") == true ||
656  StringTools::endsWith(StringTools::toLowerCase(generatedFileName), ".dll") == true)) {
657  windowsUpdateRenameFiles.push_back(FileSystem::getStandardFileSystem()->getFileName(generatedFileName));
658  windowsGeneratedFile+= ".update";
659  }
660  #endif
661 
662  if (archiveFileSystem->isExecutable(archiveFileSystem->getPathName(file), archiveFileSystem->getFileName(file)) == true) {
663  #if defined(__FreeBSD__) || defined(__linux__) || defined(__NetBSD__)
664  FileSystem::getStandardFileSystem()->setContent(
665  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
666  FileSystem::getStandardFileSystem()->getFileName(generatedFileName),
667  content
668  );
669  log.push_back(generatedFileName);
670  FileSystem::getStandardFileSystem()->setExecutable(
671  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
672  FileSystem::getStandardFileSystem()->getFileName(generatedFileName)
673  );
674  auto launcherName = installer->installerProperties.get("launcher_" + StringTools::toLowerCase(FileSystem::getStandardFileSystem()->getFileName(generatedFileName)), "");
675  if (launcherName.empty() == false) {
676  Installer::createPathRecursively(installer->homeFolder + "/" + ".local/share/applications/");
677  FileSystem::getStandardFileSystem()->setContentFromString(
678  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
679  FileSystem::getStandardFileSystem()->getFileName(generatedFileName) + ".sh",
680  string() +
681  "#!/bin/sh\n" +
682  "cd " + installFolder + "\n" +
683  "nohup " +
684  FileSystem::getStandardFileSystem()->getPathName(generatedFileName) + "/" + FileSystem::getStandardFileSystem()->getFileName(generatedFileName) + " " +
685  "</dev/null &>/dev/null &\n"
686  );
687  FileSystem::getStandardFileSystem()->setExecutable(
688  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
689  FileSystem::getStandardFileSystem()->getFileName(generatedFileName) + ".sh"
690  );
691  auto executablePathName = FileSystem::getInstance()->getPathName(generatedFileName);
692  auto executableFileName = FileSystem::getInstance()->getFileName(generatedFileName);
693  auto iconFileName = StringTools::toLowerCase(executableFileName) + "-icon.png";
694  if (archiveFileSystem->exists("resources/platforms/icons/" + iconFileName) == false &&
695  FileSystem::getInstance()->exists(executablePathName + "/resources/platforms/icons/" + iconFileName) == false) iconFileName = "default-icon.png";
696  FileSystem::getStandardFileSystem()->setContentFromString(
697  installer->homeFolder + "/" + ".local/share/applications",
698  launcherName + ".desktop",
699  string() +
700  "[Desktop Entry]\n" +
701  "Name=" + launcherName + "\n" +
702  "Exec=" + FileSystem::getStandardFileSystem()->getPathName(generatedFileName) + "/" + FileSystem::getStandardFileSystem()->getFileName(generatedFileName) + ".sh\n" +
703  "Terminal=false\n" +
704  "Type=Application\n" +
705  "Icon=" + installFolder + "/resources/platforms/icons/" + iconFileName + "\n"
706  );
707  log.push_back(generatedFileName + ".sh");
708  log.push_back(installer->homeFolder + "/" + ".local/share/applications/" + launcherName + ".desktop");
709  }
710  #elif defined(_WIN32)
711  FileSystem::getStandardFileSystem()->setContent(
712  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
713  FileSystem::getStandardFileSystem()->getFileName(windowsGeneratedFile),
714  content
715  );
716  log.push_back(generatedFileName);
717  auto executable = FileSystem::getStandardFileSystem()->getFileName(generatedFileName);
718  auto launcherName = installer->installerProperties.get(
719  "launcher_" +
720  StringTools::substring(
721  StringTools::toLowerCase(executable),
722  0,
723  executable.rfind('.')
724  ),
725  ""
726  );
727  if (launcherName.empty() == false) {
728  FileSystem::getStandardFileSystem()->setContentFromString(
729  installFolder,
730  "windows-create-shortcut.bat",
731  FileSystem::getInstance()->getContentAsString(".", "windows-create-shortcut.bat")
732  );
733  auto startMenuFolder = string(installer->homeFolder) + "/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/" + installer->installerProperties.get("name", "TDME2 based application");
734  Installer::createPathRecursively(startMenuFolder);
735  auto linkFile = startMenuFolder + "/" + launcherName + ".lnk";
736  auto executablePathName = FileSystem::getInstance()->getPathName(generatedFileName);
737  auto executableFileName = FileSystem::getInstance()->getFileName(generatedFileName);
738  auto iconFileName = StringTools::replace(StringTools::toLowerCase(executableFileName), ".exe", "") + "-icon.ico";
739  if (FileSystem::getInstance()->exists("resources/platforms/win32/" + iconFileName) == false &&
740  FileSystem::getInstance()->exists(executablePathName + "/resources/platforms/win32/" + iconFileName) == false) iconFileName = "default-icon.ico";
741  Console::println(
742  StringTools::replace(StringTools::replace(installFolder, "/", "\\"), " ", "^ ") + "\\windows-create-shortcut.bat " +
743  "\"" + StringTools::replace(generatedFileName, "/", "\\") + "\" " +
744  "\"" + StringTools::replace(linkFile, "/", "\\") + "\" " +
745  "\"resources\\platforms\\win32\\" + iconFileName + "\" "
746  );
747  Console::println(
748  Application::execute(
749  StringTools::replace(StringTools::replace(installFolder, "/", "\\"), " ", "^ ") + "\\windows-create-shortcut.bat " +
750  "\"" + StringTools::replace(generatedFileName, "/", "\\") + "\" " +
751  "\"" + StringTools::replace(linkFile, "/", "\\") + "\" " +
752  "\"resources\\platforms\\win32\\" + iconFileName + "\" "
753  )
754  );
755  log.push_back(linkFile);
756  FileSystem::getStandardFileSystem()->removeFile(installFolder, "windows-create-shortcut.bat");
757  }
758  #else
759  FileSystem::getStandardFileSystem()->setContent(
760  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
761  FileSystem::getStandardFileSystem()->getFileName(generatedFileName),
762  content
763  );
764  log.push_back(generatedFileName);
765  FileSystem::getStandardFileSystem()->setExecutable(
766  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
767  FileSystem::getStandardFileSystem()->getFileName(generatedFileName)
768  );
769 
770  #endif
771  } else {
772  FileSystem::getStandardFileSystem()->setContent(
773  FileSystem::getStandardFileSystem()->getPathName(generatedFileName),
774  #if defined(_WIN32)
775  FileSystem::getStandardFileSystem()->getFileName(windowsGeneratedFile),
776  #else
777  FileSystem::getStandardFileSystem()->getFileName(generatedFileName),
778  #endif
779  content
780  );
781  log.push_back(generatedFileName);
782  }
783  doneSize+= content.size();
784  installer->installThreadMutex.lock();
785  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString(file));
786  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(static_cast<float>(doneSize) / static_cast<float>(totalSize), 2));
787  installer->installThreadMutex.unlock();
788  }
789 
790  // delete installer component archive archive
792  installer->installThreadMutex.lock();
793  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
794  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("message"))->setText(MutableString("Deleting " + componentName));
795  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("details"))->setText(MutableString());
796  installer->installThreadMutex.unlock();
797  FileSystem::getStandardFileSystem()->removeFile("installer", componentFileName);
798  installer->installThreadMutex.lock();
799  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(50.0f, 2));
800  installer->installThreadMutex.unlock();
801  FileSystem::getStandardFileSystem()->removeFile("installer", componentFileName + ".sha256");
802  installer->installThreadMutex.lock();
803  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_installing")->getNodeById("progressbar"))->getController()->setValue(MutableString(100.0f, 2));
804  installer->installThreadMutex.unlock();
805  }
806  } catch (Exception& exception) {
807  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
808  hadException = true;
809  break;
810  }
811  }
812  }
813 
814  #if defined(_WIN32)
815  if (installer->installerMode == INSTALLERMODE_REPAIR ||
816  installer->installerMode == INSTALLERMODE_UPDATE) {
817  string updateFinishBatch;
818  updateFinishBatch+= "@ECHO OFF\r\n";
819  updateFinishBatch+= "ECHO FINISHING UPDATE. PLEASE DO NOT CLOSE.\r\n";
820  updateFinishBatch+= "setlocal EnableDelayedExpansion\r\n";
821  auto loopIdx = 0;
822  for (const auto& file: windowsUpdateRenameFiles) {
823  auto updateFile = file + ".update";
824  updateFinishBatch+=
825  ":loop" + to_string(loopIdx) + "\r\n" +
826  "if exist \"" + updateFile + "\" (\r\n" +
827  " if exist \"" + file + "\" (\r\n" +
828  " del \"" + file + "\" > nul 2>&1\r\n" +
829  " if exist \"" + file + "\" goto loop" + to_string(loopIdx) + "\r\n" +
830  " rename \"" + updateFile + "\" \"" + file + "\" > nul 2>&1\r\n" +
831  " ) else (\r\n" +
832  " rename \"" + updateFile + "\" \"" + file + "\" > nul 2>&1\r\n" +
833  " )\r\n" +
834  ")\r\n";
835  loopIdx++;
836  }
837  try {
838  FileSystem::getStandardFileSystem()->setContentFromString(installFolder, "update-finish.bat", updateFinishBatch);
839  } catch (Exception& exception) {
840  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
841  hadException = true;
842  }
843  }
844  #endif
845 
846  try {
847  FileSystem::getStandardFileSystem()->setContentFromStringArray(installFolder, "install.files.db", log);
848  FileSystem::getStandardFileSystem()->setContentFromStringArray(installFolder, "install.components.db", components);
849  FileSystem::getStandardFileSystem()->setContentFromString(installFolder, "install.version.db", installer->timestamp);
850  } catch (Exception& exception) {
851  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
852  hadException = true;
853  }
854 
855  //
856  if (hadException == false) {
857  installer->installThreadMutex.lock();
858  installer->screen = SCREEN_FINISHED;
859  installer->engine->getGUI()->resetRenderScreens();
860  installer->engine->getGUI()->addRenderScreen("installer_finished");
861  installer->engine->getGUI()->addRenderScreen(installer->popUps->getFileDialogScreenController()->getScreenNode()->getId());
862  installer->engine->getGUI()->addRenderScreen(installer->popUps->getInfoDialogScreenController()->getScreenNode()->getId());
863  installer->installThreadMutex.unlock();
864  }
865  // determine set names
866  Console::println("InstallThread::run(): done");
867  }
868  private:
869  Installer* installer;
870  };
871  //
872  auto installThread = new InstallThread(this);
873  installThread->start();
874  //
875  break;
876  }
877  case SCREEN_FINISHED:
878  engine->getGUI()->addRenderScreen("installer_finished");
879  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
880  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
881  break;
882  case SCREEN_WELCOME2:
883  engine->getGUI()->addRenderScreen("installer_welcome2");
884  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
885  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
886  break;
887  case SCREEN_UNINSTALLING:
888  {
889  engine->getGUI()->addRenderScreen("installer_uninstalling");
890  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
891  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
892  class UninstallThread: public Thread {
893  public:
894  UninstallThread(Installer* installer): Thread("install-thread", true), installer(installer) {
895  }
896  void run() {
897  //
898  Console::println("UninstallThread::run(): init");
899 
900  //
901  installer->installThreadMutex.lock();
902  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("message"))->setText(MutableString("Uninstalling ..."));
903  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("details"))->setText(MutableString("..."));
904  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("progressbar"))->getController()->setValue(MutableString(0.0f, 2));
905  installer->installThreadMutex.unlock();
906 
907  // remove files that we installed
908  auto hadException = false;
909  vector<string> log;
910  try {
911  FileSystem::getStandardFileSystem()->getContentAsStringArray(".", "install.files.db", log);
912  } catch (Exception& exception) {
913  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
914  hadException = true;
915  }
916 
917  //
918  auto installFolder = log.size() > 0?log[0] + "/":"";
919 
920  if (hadException == false) {
921  #if defined(_WIN32)
922  auto uninstallFinishBatchLoopIdx = 0;
923  string uninstallFinishBatch;
924  uninstallFinishBatch+= "@ECHO OFF\r\n";
925  uninstallFinishBatch+= "ECHO FINISHING UNINSTALL. PLEASE DO NOT CLOSE.\r\n";
926  uninstallFinishBatch+= "setlocal EnableDelayedExpansion\r\n";
927  #endif
928  for (auto i = 1; i < log.size(); i++) {
929  try {
930  installer->installThreadMutex.lock();
931  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("message"))->setText(MutableString("Uninstalling ..."));
932  dynamic_cast<GUITextNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("details"))->setText(MutableString(log[i]));
933  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_uninstalling")->getNodeById("progressbar"))->getController()->setValue(MutableString(static_cast<float>(i) / static_cast<float>(log.size()), 2));
934  installer->installThreadMutex.unlock();
935  if (FileSystem::getStandardFileSystem()->exists(log[i]) == true) {
936  FileSystem::getStandardFileSystem()->removeFile(
937  FileSystem::getStandardFileSystem()->getPathName(log[i]),
938  FileSystem::getStandardFileSystem()->getFileName(log[i])
939  );
940  }
941  } catch (Exception& innerException) {
942  Console::println(string("UninstallThread::run(): An error occurred: ") + innerException.what());
943  #if defined(_WIN32)
944  if (installer->installerMode == INSTALLERMODE_UNINSTALL &&
945  (StringTools::endsWith(log[i], ".dll") == true ||
946  StringTools::endsWith(log[i], ".exe") == true)) {
947  auto file = FileSystem::getStandardFileSystem()->getFileName(log[i]);
948  uninstallFinishBatch+=
949  ":loop" + to_string(uninstallFinishBatchLoopIdx) + "\r\n" +
950  "if exist \"" + file + "\" (\r\n" +
951  " del \"" + file + "\" > nul 2>&1\r\n" +
952  " if exist \"" + file + "\" goto loop" + to_string(uninstallFinishBatchLoopIdx) + "\r\n" +
953  ")\r\n";
954  uninstallFinishBatchLoopIdx++;
955  }
956  #endif
957  }
958  }
959  #if defined(_WIN32)
960  if (installer->installerMode == INSTALLERMODE_UNINSTALL) {
961  {
962  auto file = "console.log";
963  uninstallFinishBatch+=
964  ":loop" + to_string(uninstallFinishBatchLoopIdx) + "\r\n" +
965  "if exist \"" + file + "\" (\r\n" +
966  " del \"" + file + "\" > nul 2>&1\r\n" +
967  " if exist \"" + file + "\" goto loop" + to_string(uninstallFinishBatchLoopIdx) + "\r\n" +
968  ")\r\n";
969  uninstallFinishBatchLoopIdx++;
970  }
971  try {
972  FileSystem::getStandardFileSystem()->setContentFromString(installFolder, "uninstall-finish.bat", uninstallFinishBatch);
973  } catch (Exception& exception) {
974  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
975  hadException = true;
976  }
977  }
978  #endif
979  }
980 
981  // remove folders that we created non recursive
982  if (hadException == false) {
983  vector<string> folders;
984  for (auto i = 1; i < log.size(); i++) {
985  auto folderCandidate = FileSystem::getStandardFileSystem()->getPathName(log[i]);
986  if (FileSystem::getStandardFileSystem()->isPath(folderCandidate) == true) {
987  while (StringTools::startsWith(folderCandidate, installFolder) == true) {
988  folders.push_back(folderCandidate);
989  folderCandidate = FileSystem::getStandardFileSystem()->getPathName(folderCandidate);
990  }
991  }
992  }
993  sort(folders.begin(), folders.end());
994  reverse(folders.begin(), folders.end());
995  auto newEnd = unique(folders.begin(), folders.end());
996  folders.resize(distance(folders.begin(), newEnd));
997  for (const auto& folder: folders) {
998  try {
999  FileSystem::getStandardFileSystem()->removePath(
1000  folder,
1001  false
1002  );
1003  } catch (Exception& exception) {
1004  Console::println(string("UninstallThread::run(): An error occurred: ") + exception.what());
1005  }
1006  }
1007 
1008  // remove installer folder if not updating
1009  if (installer->installerMode == INSTALLERMODE_UNINSTALL) {
1010  FileSystem::unsetFileSystem();
1011  vector<string> installerFiles;
1012  try {
1013  FileSystem::getStandardFileSystem()->list(
1014  installFolder + "installer",
1015  installerFiles
1016  );
1017  } catch (Exception& exception) {
1018  Console::println(string("UninstallThread::run(): An error occurred: ") + exception.what());
1019  }
1020  for (const auto& installerFile: installerFiles) {
1021  if (StringTools::endsWith(installerFile, ".ta") == true ||
1022  StringTools::endsWith(installerFile, ".ta.sha256") == true) {
1023  try {
1024  FileSystem::getStandardFileSystem()->removeFile(
1025  installFolder + "installer",
1026  installerFile
1027  );
1028  } catch (Exception& exception) {
1029  Console::println(string("UninstallThread::run(): An error occurred: ") + exception.what());
1030  }
1031  }
1032  }
1033  try {
1034  FileSystem::getStandardFileSystem()->removePath(installFolder + "installer", false);
1035  } catch (Exception& exception) {
1036  Console::println(string("UninstallThread::run(): An error occurred: ") + exception.what());
1037  }
1038  }
1039  }
1040 
1041  // remove install databases and console.log
1042  if (hadException == false) {
1043  try {
1044  FileSystem::getStandardFileSystem()->removeFile(".", "install.files.db");
1045  FileSystem::getStandardFileSystem()->removeFile(".", "install.components.db");
1046  FileSystem::getStandardFileSystem()->removeFile(".", "install.version.db");
1047  FileSystem::getStandardFileSystem()->removeFile(".", "console.log");
1048  } catch (Exception& exception) {
1049  installer->popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
1050  hadException = true;
1051  }
1052  }
1053 
1054  // remove install folder
1055  if (hadException == false) {
1056  try {
1057  FileSystem::getStandardFileSystem()->removePath(installFolder, false);
1058  } catch (Exception& exception) {
1059  Console::println(string("UninstallThread::run(): An error occurred: ") + exception.what());
1060  }
1061  }
1062 
1063  //
1064  if (hadException == false) {
1065  if (installer->installerMode == INSTALLERMODE_UPDATE || installer->installerMode == INSTALLERMODE_REPAIR) {
1066  installer->installThreadMutex.lock();
1067  installer->screen = SCREEN_INSTALLING;
1068  installer->performScreenAction();
1069  installer->installThreadMutex.unlock();
1070  } else {
1071  // TODO: Maybe show a finishing screen
1072  #if defined(_WIN32)
1073  string drive;
1074  if (installFolder[1] == ':') drive = StringTools::substring(installFolder, 0, 2) + " && ";
1075  system(
1076  (
1077  string() +
1078  drive +
1079  "cd " +
1080  "\"" + installFolder + "/" + "\"" +
1081  " && start cmd /c \"uninstall-finish.bat && del uninstall-finish.bat && cd .. && rmdir \"\"\"" + StringTools::replace(installFolder, "/", "\\") + "\"\"\" > nul 2>&1\"").c_str()
1082  );
1083  #endif
1084  //
1085  Application::exit(0);
1086  }
1087  }
1088  // determine set names
1089  Console::println("UninstallThread::run(): done");
1090  }
1091  private:
1092  Installer* installer;
1093  };
1094  //
1095  UninstallThread* uninstallThread = new UninstallThread(this);
1096  uninstallThread->start();
1097  //
1098  break;
1099  }
1100  default:
1101  Console::println("Installer::performScreenAction(): Unhandled screen: " + to_string(screen));
1102  break;
1103  }
1104  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
1105  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
1106 }
1107 
1109 {
1110  try {
1111  installed =
1112  FileSystem::getStandardFileSystem()->exists("install.files.db") == true &&
1113  FileSystem::getStandardFileSystem()->exists("install.components.db") == true &&
1114  FileSystem::getStandardFileSystem()->exists("install.version.db") == true;
1115  if (installed == true) screen = SCREEN_WELCOME2;
1116  engine->initialize();
1117  engine->setSceneColor(Color4(125.0f / 255.0f, 125.0f / 255.0f, 125.0f / 255.0f, 1.0f));
1119  popUps->initialize();
1120  #if defined(_WIN32)
1121  homeFolder = StringTools::replace(string(getenv("USERPROFILE")), '\\', '/');
1122  #else
1123  homeFolder = string(getenv("HOME"));
1124  #endif
1125  } catch (Exception& exception) {
1127  engine->getGUI()->addRenderScreen(popUps->getFileDialogScreenController()->getScreenNode()->getId());
1128  engine->getGUI()->addRenderScreen(popUps->getInfoDialogScreenController()->getScreenNode()->getId());
1129  popUps->getInfoDialogScreenController()->show("An error occurred:", exception.what());
1130  }
1132 }
1133 
1135 {
1136  engine->dispose();
1137 }
1138 
1139 void Installer::reshape(int width, int height)
1140 {
1141  engine->reshape(width, height);
1142 }
1143 
1145  if (type == GUIActionListenerType::PERFORMED) {
1146  if (node->getId() == "button_next") {
1149  } else {
1150  screen = static_cast<Screen>(static_cast<int>(screen) + 1 % static_cast<int>(SCREEN_MAX));
1151  }
1153  } else
1154  if (node->getId() == "button_back") {
1156  try {
1157  timestamp = FileSystem::getStandardFileSystem()->getContentAsString(".", "install.version.db");
1158  } catch (Exception& exception) {
1159  Console::println(string("Installer::onAction(): An error occurred: ") + exception.what());
1160  }
1162  } else
1165  } else {
1166  screen = static_cast<Screen>(static_cast<int>(screen) - 1 % static_cast<int>(SCREEN_MAX));
1167  }
1169  } else
1170  if (node->getId() == "button_agree") {
1174  } else
1175  if (node->getId() == "button_install") {
1176  dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->setValue(MutableString(StringTools::replace(dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString(), "\\", "/")));
1179  } else
1180  if (node->getId() == "button_cancel") {
1181  FileSystem::unsetFileSystem();
1182  // delete downloaded files
1183  for (const auto& downloadedFile: downloadedFiles) {
1184  try {
1185  FileSystem::getStandardFileSystem()->removeFile(".", downloadedFile);
1186  } catch (Exception& exception) {
1187  Console::println("Installer::onAction(): Removing downloaded file failed: " + downloadedFile);
1188  }
1189  }
1190  Application::exit(0);
1191  } else
1192  if (node->getId() == "button_finish") {
1193  if (installerProperties.get("launch", "").empty() == false &&
1194  dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_finished")->getNodeById("checkbox_launch"))->getController()->getValue().equals("1") == true) {
1195  #if defined(_WIN32)
1196  auto installFolder = dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString();
1197  string drive;
1198  if (installFolder[1] == ':') drive = StringTools::substring(installFolder, 0, 2) + " && ";
1199  string finishCommand;
1202  finishCommand+= " && start cmd /c \"update-finish.bat && del update-finish.bat && " + installerProperties.get("launch", "") + ".exe" + "\"";
1203  } else {
1204  finishCommand+= " && start cmd /c \"" + installerProperties.get("launch", "") + ".exe" + "\"";
1205  }
1206  system(
1207  (
1208  string() +
1209  drive +
1210  "cd " +
1211  "\"" + installFolder + "/" + "\"" +
1212  finishCommand
1213  ).c_str()
1214  );
1215  #elif defined(__APPLE__)
1216  Application::executeBackground(
1217  "open " +
1218  dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString() + "/" +
1219  installerProperties.get("launch", "") + ".app"
1220  );
1221  #else
1222  Application::executeBackground(
1223  dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString() + "/" +
1224  installerProperties.get("launch", "") + ".sh"
1225  );
1226  #endif
1227  } else {
1228  #if defined(_WIN32)
1229  auto installFolder = dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString();
1230  string drive;
1231  if (installFolder[1] == ':') drive = StringTools::substring(installFolder, 0, 2) + " && ";
1234  system(
1235  (
1236  string() +
1237  drive +
1238  "cd " +
1239  "\"" + installFolder + "/" + "\"" +
1240  " && start cmd /c \"update-finish.bat && del update-finish.bat\"").c_str()
1241  );
1242  }
1243  #endif
1244  }
1245  FileSystem::unsetFileSystem();
1246  Application::exit(0);
1247  } else
1248  if (node->getId() == "button_browse") {
1249  class OnBrowseAction: public virtual Action
1250  {
1251  public:
1252  void performAction() override {
1253  installer->popUps->getFileDialogScreenController()->close();
1254  auto pathToShow = installer->popUps->getFileDialogScreenController()->getPathName() + "/" + installer->installerProperties.get("name", "TDME2 based application");
1255  dynamic_cast<GUIElementNode*>(installer->engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->setValue(MutableString(pathToShow));
1256  }
1257 
1258  /**
1259  * Public constructor
1260  * @param installer installer
1261  */
1262  OnBrowseAction(Installer* installer): installer(installer) {
1263  }
1264 
1265  private:
1266  Installer* installer;
1267  };
1268 
1269  vector<string> extensions;
1270  auto pathToShow = dynamic_cast<GUIElementNode*>(engine->getGUI()->getScreen("installer_folder")->getNodeById("install_folder"))->getController()->getValue().getString();
1271  pathToShow = StringTools::replace(pathToShow, "\\", "/");
1272  while (FileSystem::getStandardFileSystem()->exists(pathToShow) == false || FileSystem::getStandardFileSystem()->isPath(pathToShow) == false) {
1273  pathToShow = FileSystem::getStandardFileSystem()->getPathName(pathToShow);
1274  }
1275  if (StringTools::startsWith(pathToShow, "/") == false) pathToShow = homeFolder;
1276  popUps->getFileDialogScreenController()->show(
1277  pathToShow,
1278  "Install in: ",
1279  extensions,
1280  "",
1281  true,
1282  new OnBrowseAction(this)
1283  );
1284  } else
1285  if (node->getId() == "button_uninstall") {
1289  } else
1290  if (node->getId() == "button_update") {
1294  } else
1295  if (node->getId() == "button_repair") {
1299  } else
1300  if (StringTools::startsWith(node->getId(), "component") == true) {
1301  auto componentIdx = Integer::parse(StringTools::substring(node->getId(), string("component").size()));
1302  dynamic_cast<GUIStyledTextNode*>(engine->getGUI()->getScreen("installer_components")->getNodeById("component_description"))->setText(MutableString(installerProperties.get("component" + to_string(componentIdx) + "_description", "No detail description.")));
1303  }
1304  }
1305 }
1306 
1308  Console::println(node->getName() + ": onChange(): " + node->getController()->getValue().getString());
1309 }
1310 
1312 {
1314  if (remountInstallerArchive == true) {
1318  remountInstallerArchive = false;
1319  }
1320  engine->display();
1321  engine->getGUI()->render();
1322  engine->getGUI()->handleEvents();
1324 }
1325 
1326 void Installer::mountInstallerFileSystem(const string& timestamp, bool remountInstallerArchive) {
1327  Console::println("Installer::mountInstallerFileSystem(): timestamp: " + (timestamp.empty() == false?timestamp:"no timestamp"));
1328  // determine installer tdme archive
1329  try {
1330  auto installerArchiveFileNameStart = Application::getOSName() + "-" + Application::getCPUName() + "-Installer-" + (timestamp.empty() == false?timestamp + ".ta":"");
1331  string installerArchiveFileName;
1332  // determine newest component file name
1333  vector<string> files;
1334  FileSystem::getStandardFileSystem()->list("installer", files);
1335  for (const auto& file: files) {
1336  if (StringTools::startsWith(file, installerArchiveFileNameStart) == true &&
1337  StringTools::endsWith(file, ".ta") == true) {
1338  Console::println("Installer::main(): Have installer tdme archive file: " + file);
1339  installerArchiveFileName = file;
1340  }
1341  }
1342  if (installerArchiveFileName.empty() == true) {
1343  Console::println("Installer::main(): No installer TDME archive found. Exiting.");
1344  Application::exit(1);
1345  }
1346  // file system
1347  auto installerFileSystem = make_unique<ArchiveFileSystem>("installer/" + installerArchiveFileName);
1348  if (installerFileSystem->computeSHA256Hash() != FileSystem::getStandardFileSystem()->getContentAsString("installer", installerArchiveFileName + ".sha256")) {
1349  throw ExceptionBase("Installer::main(): Failed to verify: " + installerArchiveFileName + ", get new installer and try again");
1350  }
1351  Console::println("Installer::mountInstallerFileSystem(): unmounting");
1352  // check if to remove old installer file system
1353  auto lastInstallerArchiveFileName = remountInstallerArchive == true?static_cast<ArchiveFileSystem*>(FileSystem::getInstance())->getArchiveFileName():string();
1354  FileSystem::unsetFileSystem();
1355  // so? Also check if new installer archive file name is same as currently used installer archive file name
1356  if (lastInstallerArchiveFileName.empty() == false && lastInstallerArchiveFileName != "installer/" + installerArchiveFileName) {
1357  // yep
1358  Console::println("Installer::mountInstallerFileSystem(): deleting installer tdme archive file: " + lastInstallerArchiveFileName);
1359  try {
1360  FileSystem::getStandardFileSystem()->removeFile(
1361  FileSystem::getStandardFileSystem()->getPathName(lastInstallerArchiveFileName),
1362  FileSystem::getStandardFileSystem()->getFileName(lastInstallerArchiveFileName)
1363  );
1364  FileSystem::getStandardFileSystem()->removeFile(
1365  FileSystem::getStandardFileSystem()->getPathName(lastInstallerArchiveFileName),
1366  FileSystem::getStandardFileSystem()->getFileName(lastInstallerArchiveFileName) + ".sha256"
1367  );
1368  } catch (Exception& exception) {
1369  Console::println(string("Installer::mountInstallerFileSystem(): An error occurred: ") + exception.what());
1370  }
1371  }
1372  Console::println("Installer::mountInstallerFileSystem(): mounting: " + installerArchiveFileName);
1373  FileSystem::setupFileSystem(installerFileSystem.release());
1374  } catch (Exception& exception) {
1375  Console::println(string("Installer::mountInstallerFileSystem(): ") + exception.what());
1376  Application::exit(1);
1377  }
1378 }
1379 
1380 int Installer::main(int argc, char** argv)
1381 {
1382  Console::println(string("Installer ") + Version::getVersion());
1383  Console::println(Version::getCopyright());
1384  Console::println();
1385  if (argc > 1) {
1386  Console::println("Usage: Installer");
1387  Application::exit(1);
1388  }
1389  #if defined(__APPLE__)
1390  // TODO: We have this duplicated in Application::run() also, but we need it twice for installer special case: move me into Application as static method or something
1391  // change working directory on MacOSX if started from app bundle
1392  auto executablePathName = string(argv[0]);
1393  if (executablePathName.find(".app/Contents/MacOS/") != string::npos) {
1394  auto appBundleName = StringTools::substring(executablePathName, 0, executablePathName.rfind(".app") + string(".app").size());
1395  auto workingPathName = StringTools::substring(appBundleName, 0, appBundleName.rfind('/'));
1396  FileSystem::getStandardFileSystem()->changePath(workingPathName);
1397  }
1398  #endif
1400  auto installer = new Installer();
1401  return installer->run(argc, argv, "Installer", nullptr, Application::WINDOW_HINT_NOTRESIZEABLE);
1402 }
1403 
1404 void Installer::scanArchive(ArchiveFileSystem* archiveFileSystem, vector<string>& totalFiles, const string& pathName) {
1405  vector<string> files;
1406  archiveFileSystem->list(pathName, files);
1407  for (const auto& fileName: files) {
1408  if (archiveFileSystem->isPath(pathName + "/" + fileName) == false) {
1409  totalFiles.push_back((pathName.empty() == true?"":pathName + "/") + fileName);
1410  } else {
1411  scanArchive(archiveFileSystem, totalFiles, pathName + "/" + fileName);
1412  }
1413  }
1414 
1415 }
1416 
1417 void Installer::createPathRecursively(const string& pathName) {
1418  StringTokenizer t;
1419  t.tokenize(pathName, "/");
1420  string pathCreating;
1421  while (t.hasMoreTokens() == true) {
1422  string pathComponent = t.nextToken();
1423  #if defined(_WIN32)
1424  pathCreating+= pathCreating.empty() == true?pathComponent:"/" + pathComponent;
1425  #else
1426  pathCreating+= "/" + pathComponent;
1427  #endif
1428  if (FileSystem::getStandardFileSystem()->isDrive(pathCreating) == false && FileSystem::getStandardFileSystem()->exists(pathCreating) == false) {
1429  Console::println("Creating: " + pathCreating);
1430  FileSystem::getStandardFileSystem()->createPath(pathCreating);
1431  }
1432  }
1433 }
1434 
1436  Application::cancelExit();
1437 }
Application base class, please make sure to allocate application on heap to have correct application ...
Definition: Application.h:41
int run(int argc, char **argv, const string &title, InputEventHandler *inputEventHandler=nullptr, int windowHints=WINDOW_HINT_NONE)
Run this application.
void setInputEventHandler(InputEventHandler *inputEventHandler)
Set input event handler.
Color 4 definition class.
Definition: Color4.h:18
Engine main class.
Definition: Engine.h:131
void reshape(int32_t width, int32_t height)
Reshape.
Definition: Engine.cpp:972
void display()
Renders the scene.
Definition: Engine.cpp:1361
void initialize()
Initialize render engine.
Definition: Engine.cpp:733
void dispose()
Shutdown the engine.
Definition: Engine.cpp:2071
void setSceneColor(const Color4 &sceneColor)
Set scene color.
Definition: Engine.h:1159
GUI parser.
Definition: GUIParser.h:40
GUI module class.
Definition: GUI.h:64
void handleEvents(bool clearEvents=true)
Handle screen events.
Definition: GUI.cpp:507
void addRenderScreen(const string &screenId, int screenIdx=-1)
Add render screen.
Definition: GUI.cpp:155
GUIScreenNode * getScreen(const string &id)
Get screen.
Definition: GUI.h:222
void render()
Render GUIs.
Definition: GUI.cpp:324
void addScreen(const string &id, GUIScreenNode *screen)
Add screen.
Definition: GUI.cpp:98
void reset()
Removes all screens and caches.
Definition: GUI.cpp:124
void resetRenderScreens()
Reset render screens.
Definition: GUI.cpp:136
GUI node controller base class.
virtual const MutableString & getValue()=0
GUINodeController * getController()
Definition: GUINode.h:661
const string & getId()
Definition: GUINode.h:339
GUI parent node base class thats supporting child nodes.
Definition: GUIParentNode.h:42
GUI screen node that represents a screen that can be rendered via GUI system.
Definition: GUIScreenNode.h:72
void addChangeListener(GUIChangeListener *listener)
Add change listener.
void addActionListener(GUIActionListener *listener)
Add action listener.
GUINode * getNodeById(const string &nodeId)
Get GUI node by id.
void setPassword(const string &password)
Set password.
Definition: HTTPClient.h:131
void execute()
Execute HTTP request.
Definition: HTTPClient.cpp:179
void setUsername(const string &username)
Set username.
Definition: HTTPClient.h:115
void setMethod(const string &method)
Set method.
Definition: HTTPClient.h:99
void setURL(const string &url)
Set URL.
Definition: HTTPClient.h:83
void setPassword(const string &password)
Set password.
void start()
Starts the HTTP download to file.
void join()
Wait until underlying thread has finished.
void setUsername(const string &username)
Set username.
void setFile(const string &file)
Set file to download to.
Archive file system implementation.
void list(const string &pathName, vector< string > &files, FileNameFilter *filter=nullptr, bool addDrives=false)
List files for given path and filter by a file name filter if not null.
bool isPath(const string &uri)
Check if file is a path.
File system singleton class.
Definition: FileSystem.h:17
void unlock()
Unlocks this mutex.
Definition: Mutex.h:54
void lock()
Locks the mutex, additionally mutex locks will block until other locks have been unlocked.
Definition: Mutex.h:47
Base class for threads.
Definition: Thread.h:20
Pop ups controller accessor class.
Definition: PopUps.h:29
void dispose() override
Disposes.
Definition: Installer.cpp:1134
vector< string > downloadedFiles
Definition: Installer.h:52
void display() override
Display.
Definition: Installer.cpp:1311
static void mountInstallerFileSystem(const string &timestamp=string(), bool remountInstallerArchive=false)
Mount installer file system.
Definition: Installer.cpp:1326
void initialize() override
Init.
Definition: Installer.cpp:1108
static int main(int argc, char **argv)
Main.
Definition: Installer.cpp:1380
void onChange(GUIElementNode *node) override
On change.
Definition: Installer.cpp:1307
static void createPathRecursively(const string &pathName)
Create a path recursively.
Definition: Installer.cpp:1417
void initializeScreens()
Initialize screens.
Definition: Installer.cpp:117
Installer()
Public constructor.
Definition: Installer.cpp:105
void reshape(int width, int height) override
Resize.
Definition: Installer.cpp:1139
void onAction(GUIActionListenerType type, GUIElementNode *node) override
Definition: Installer.cpp:1144
unique_ptr< PopUps > popUps
Definition: Installer.h:41
void onClose() override
On close.
Definition: Installer.cpp:1435
static void scanArchive(ArchiveFileSystem *archiveFileSystem, vector< string > &totalFiles, const string &pathName=string())
Scan archive file system @oaram archive archive file system.
Definition: Installer.cpp:1404
void performScreenAction()
Perform screen action.
Definition: Installer.cpp:270
Console class.
Definition: Console.h:29
Exception base class.
Definition: ExceptionBase.h:19
Integer class.
Definition: Integer.h:25
Mutable utf8 aware string class.
Definition: MutableString.h:23
bool equals(const string &s2) const
Equals.
const string & getString() const
Properties class, which helps out with storeing or loading key value pairs from/to property files.
Definition: Properties.h:23
const string & get(const string &key, const string &defaultValue) const
Get property value by key.
Definition: Properties.h:46
void load(const string &pathName, const string &fileName, FileSystemInterface *fileSystem=nullptr)
Load property file.
Definition: Properties.cpp:24
String tokenizer class.
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
String tools class.
Definition: StringTools.h:22
Time utility class.
Definition: Time.h:20
std::exception Exception
Exception base class.
Definition: Exception.h:18
GUI action listener interface.
GUI change listener interface.
Action Interface.
Definition: Action.h:11