swift
application.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "core/application.h"
5 
6 #include <cstdio>
7 #include <cstdlib>
8 
9 #include <QCoreApplication>
10 #include <QDateTime>
11 #include <QEventLoop>
12 #include <QFileInfo>
13 #include <QHttpMultiPart>
14 #include <QNetworkReply>
15 #include <QNetworkRequest>
16 #include <QProcess>
17 #include <QRegularExpression>
18 #include <QSslSocket>
19 #include <QStandardPaths>
20 #include <QStringBuilder>
21 #include <QStringList>
22 #include <QSysInfo>
23 #include <QTemporaryDir>
24 #include <QThread>
25 #include <QTimer>
26 #include <QWriteLocker>
27 #include <Qt>
28 #include <QtGlobal>
29 
30 #include "config/buildconfig.h"
35 #include "core/cookiemanager.h"
36 #include "core/corefacade.h"
37 #include "core/inputmanager.h"
38 #include "core/registermetadata.h"
39 #include "core/setupreader.h"
40 #include "core/webdataservices.h"
41 #include "misc/applicationinfo.h"
42 #include "misc/crashhandler.h"
43 #include "misc/datacache.h"
44 #include "misc/dbusserver.h"
45 #include "misc/eventloop.h"
46 #include "misc/filelogger.h"
47 #include "misc/loghandler.h"
48 #include "misc/logmessage.h"
49 #include "misc/logpattern.h"
51 #include "misc/registermetadata.h"
52 #include "misc/settingscache.h"
53 #include "misc/stringutils.h"
54 #include "misc/swiftdirectories.h"
55 #include "misc/threadutils.h"
56 #include "misc/verify.h"
57 
58 using namespace swift::config;
59 using namespace swift::misc;
60 using namespace swift::misc::db;
61 using namespace swift::misc::network;
62 using namespace swift::misc::aviation;
63 using namespace swift::misc::simulation;
64 using namespace swift::misc::weather;
65 using namespace swift::core;
66 using namespace swift::core::context;
67 using namespace swift::core::vatsim;
68 using namespace swift::core::data;
69 using namespace swift::core::db;
70 
71 swift::core::CApplication *sApp = nullptr; // set by constructor
72 
74 static const QString &swiftDataRoot()
75 {
76  static const QString path =
78  return path;
79 }
80 
81 namespace swift::core
82 {
83  CApplication::CApplication(CApplicationInfo::Application application, bool init)
84  : CApplication(executable(), application, init)
85  {}
86 
87  CApplication::CApplication(const QString &applicationName, CApplicationInfo::Application application, bool init)
88  : CIdentifiable(this), m_accessManager(new QNetworkAccessManager(this)), m_applicationInfo(application),
89  m_applicationName(applicationName), m_coreFacadeConfig(CCoreFacadeConfig::NotUsed)
90  {
91  Q_ASSERT_X(!sApp, Q_FUNC_INFO, "already initialized");
92  Q_ASSERT_X(QCoreApplication::instance(), Q_FUNC_INFO, "no application object");
93 
95  QCoreApplication::setApplicationName(m_applicationName);
96  QCoreApplication::setApplicationVersion(CBuildConfig::getVersionString());
97  this->setObjectName(m_applicationName);
98  this->thread()->setObjectName(
99  m_applicationName); // normally no effect as thread already runs, but does not harm either
100 
101  // init skipped when called from CGuiApplication
102  if (init) { this->init(true); }
103  }
104 
105  void CApplication::init(bool withMetadata)
106  {
107  if (!sApp)
108  {
109  // notify when app goes down
112 
113  // metadata
114  if (withMetadata) { CApplication::registerMetadata(); }
115 
116  // unit test
117  if (this->getApplicationInfo().getApplication() == CApplicationInfo::UnitTest)
118  {
121  }
123  CApplication::getApplicationInfo().getApplication());
124  this->initParser();
125  this->initLogging();
126  this->tagApplicationDataDirectory();
127 
128  //
129  // cmd line arguments not yet parsed here
130  //
131 
132  // main app
133  sApp = this;
134 
135  this->initNetwork();
136 
137  // global setup
138  m_setupReader.reset(new CSetupReader(this));
139 
140  // check for updates
141  m_gitHubPackagesReader.reset(new CGitHubPackagesReader(this));
142  connect(m_gitHubPackagesReader.data(), &CGitHubPackagesReader::updateInfoAvailable, this,
145 
146  // startup done
151 
152  if (!this->getApplicationInfo().isUnitTest())
153  {
154  m_inputManager = new CInputManager(this);
155  m_inputManager->createDevices();
156  }
157 
158  connect(this, &QObject::destroyed, [cat = CLogCategoryList(this)] {
159  for (CWorkerBase *worker : CWorkerBase::allWorkers())
160  {
161  CLogMessage(cat).debug(u"Worker named '%1' still exists after application destroyed")
162  << worker->objectName();
163  }
164  });
165  }
166  }
167 
169  {
173  if (!apps.contains(myself)) { apps.push_back(myself); }
174  const bool ok = CFileUtils::writeStringToLockedFile(apps.toJsonString(),
175  CFileUtils::appendFilePaths(swiftDataRoot(), "apps.json"));
176  if (!ok)
177  {
178  CLogMessage(static_cast<CApplication *>(nullptr)).error(u"Failed to write to application list file");
179  }
180  return ok;
181  }
182 
184  {
188  if (!apps.contains(myself)) { return true; }
189  apps.remove(myself);
190  const bool ok = CFileUtils::writeStringToLockedFile(apps.toJsonString(),
191  CFileUtils::appendFilePaths(swiftDataRoot(), "apps.json"));
192  if (!ok)
193  {
194  CLogMessage(static_cast<CApplication *>(nullptr)).error(u"Failed to write to application list file");
195  }
196  return ok;
197  }
198 
200  {
201  Q_ASSERT_X(instance(), Q_FUNC_INFO, "missing application");
202  emit this->startUpCompleted(true);
204  return QCoreApplication::exec();
205  }
206 
207  void CApplication::restartApplication(const QStringList &newArguments, const QStringList &removeArguments)
208  {
211  const QStringList args = this->argumentsJoined(newArguments, removeArguments);
212  this->gracefulShutdown();
213  QProcess::startDetached(prg, args);
215  }
216 
218 
220  {
222  apps.convertFromJsonNoThrow(CFileUtils::readLockedFileToString(swiftDataRoot() + "apps.json"), {}, {});
223  apps.removeIf([](const CApplicationInfo &info) { return !info.getProcessInfo().exists(); });
224  return apps;
225  }
226 
228  {
230  return running.containsApplication(application);
231  }
232 
234  {
235  return getRunningApplications().containsBy([this](const CApplicationInfo &info) {
236  return info.getApplication() == getApplicationInfo().getApplication();
237  });
238  }
239 
241 
242  bool CApplication::isIncognito() const { return m_incognito; }
243 
244  void CApplication::setIncognito(bool incognito) { m_incognito = incognito; }
245 
247 
249  {
250  static const QString s(m_applicationName % u" " % CBuildConfig::getVersionString());
251  return s;
252  }
253 
255  {
256  static const QString s(m_applicationName % u" " % this->versionStringDetailed());
257  return s;
258  }
259 
261  {
262  if (m_shutdown) { return {}; }
263  const CSetupReader *r = m_setupReader.data();
264  if (!r) { return {}; }
265  return r->getSetup();
266  }
267 
269  {
270  if (m_shutdown) { return {}; }
271  if (!m_gitHubPackagesReader) { return {}; }
272  return m_gitHubPackagesReader->getUpdateInfo();
273  }
274 
276  {
277  if (m_shutdown) { return; }
278  if (!m_gitHubPackagesReader) { return; }
279  m_gitHubPackagesReader->readUpdateInfo();
280  }
281 
283  {
284  if (CBuildConfig::isLocalDeveloperDebugBuild()) { return CDistribution::localDeveloperBuild(); }
285  const CUpdateInfo u = this->getUpdateInfo();
286  return u.anticipateOwnDistribution();
287  }
288 
290  {
291  m_started = false; // reset
292 
293  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
294 
295  // parsing itself is done
296  CStatusMessageList msgs;
297  do {
298  // clear cache?
299  if (this->isSet(m_cmdClearCache))
300  {
301  const QStringList files(CApplication::clearCaches());
302  msgs.push_back(CLogMessage(this).debug() << "Cleared cache, " << files.size() << " files");
303  }
304 
305  // crashpad dump
306  if (this->isSet(m_cmdTestCrashpad))
307  {
308  msgs.push_back(CLogMessage(this).info(u"About to simulate crash"));
309  QTimer::singleShot(10 * 1000, [=] {
310  if (!sApp || sApp->isShuttingDown()) { return; }
311  this->simulateCrash();
312  });
313  }
314 
315  Q_ASSERT_X(m_setupReader && m_setupReader->isSetupAvailable(), Q_FUNC_INFO, "Setup not available");
316 
317  // start hookin
318  msgs.push_back(this->startHookIn());
319  if (msgs.isFailure()) { break; }
320 
321  // Settings if not already initialized
322  msgs.push_back(this->initLocalSettings());
323  if (msgs.isFailure()) { break; }
324  }
325  while (false);
326 
327  // terminate with failures, otherwise log messages
328  if (msgs.isFailure())
329  {
330  this->cmdLineErrorMessage(msgs);
331  return false;
332  }
333  else if (!msgs.isEmpty()) { CLogMessage::preformatted(msgs); }
334 
335  m_started = true;
336  return m_started;
337  }
338 
340  {
341  if (m_shutdown || !m_setupReader) { return false; }
342  return m_setupReader->isSetupAvailable();
343  }
344 
346  {
347  return (this->getGlobalSetup().isSwiftVersionMinimumMappingVersion());
348  }
349 
351  {
352  if (this->isShuttingDown()) { return false; } // service will not survive for long
353  return !m_webDataServices.isNull();
354  }
355 
357  {
358  // use hasWebDataServices() to test if services are available
359  // getting the assert means web services are accessed before the are initialized
360 
361  Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO,
362  "Missing web data services, use hasWebDataServices to test if existing");
363  return m_webDataServices.data();
364  }
365 
367  {
368  if (this->isDeveloperFlagSet() && CBuildConfig::isLocalDeveloperDebugBuild())
369  {
370  static const QString s(CBuildConfig::getVersionStringPlatform() % u" [dev,DEVDBG]");
371  return s;
372  }
373  if (isDeveloperFlagSet())
374  {
375  static const QString s(CBuildConfig::getVersionStringPlatform() % u" [dev]");
376  return s;
377  }
378  if (CBuildConfig::isLocalDeveloperDebugBuild())
379  {
380  static const QString s(CBuildConfig::getVersionStringPlatform() % u" [DEVDBG]");
381  return s;
382  }
383  return CBuildConfig::getVersionStringPlatform();
384  }
385 
387  {
388  static const QString s(QStringLiteral("swift %1").arg(versionStringDetailed()));
389  return s;
390  }
391 
393  {
394  static const QByteArray a(swiftVersionString().toUtf8());
395  return a.constData();
396  }
397 
398  bool CApplication::initIsRunningInDeveloperEnvironment() const
399  {
400  //
401  // assumption: restricted distributions are development versions
402  //
403 
404  if (this->getApplicationInfo().isSampleOrUnitTest()) { return true; }
405  if (CBuildConfig::isLocalDeveloperDebugBuild()) { return true; }
406 
407  const CDistribution d(this->getOwnDistribution());
408  return d.isRestricted() && this->isSet(m_cmdDevelopment);
409  }
410 
411  CStatusMessage CApplication::initLocalSettings()
412  {
413  if (m_localSettingsLoaded) { return {}; }
414  m_localSettingsLoaded = true;
415 
416  // trigger loading and saving of settings in appropriate scenarios
417  if (m_coreFacadeConfig.getMode() != CCoreFacadeConfig::Remote)
418  {
419  // facade running here locally
421  if (msg.isFailure()) { return msg; }
422 
423  // Settings are distributed via DBus. So only one application is responsible for saving. `enableLocalSave()`
424  // means "this is the application responsible for saving". If swiftgui requests a setting to be saved, it is
425  // sent to swiftcore and saved by swiftcore.
427  }
428  return {};
429  }
430 
432 
433  void CApplication::saveSettingsOnShutdown(bool saveSettings) { m_saveSettingsOnShutdown = saveSettings; }
434 
436  {
439  }
440 
442  {
443  if (keys.isEmpty()) { return {}; }
444  return this->supportsContexts() ? this->getIContextApplication()->saveSettingsByKey(keys) :
446  }
447 
449  {
450  static const QTemporaryDir tempDir;
451  if (tempDir.isValid()) { return tempDir.path(); }
452  return QDir::tempPath();
453  }
454 
456  {
457  QString str = CBuildConfig::getVersionString() % u" " %
458  (CBuildConfig::isReleaseBuild() ? u"Release build" : u"Debug build") % separator %
459  u"Local dev.dbg.: " % boolToYesNo(CBuildConfig::isLocalDeveloperDebugBuild()) % separator %
460  u"dev.env.: " % boolToYesNo(this->isDeveloperFlagSet()) % separator % u"distribution: " %
461  this->getOwnDistribution().toQString(true) % separator % u"Windows NT: " %
462  boolToYesNo(CBuildConfig::isRunningOnWindowsNtPlatform()) % separator % u"Linux: " %
463  boolToYesNo(CBuildConfig::isRunningOnLinuxPlatform()) % " Unix: " %
464  boolToYesNo(CBuildConfig::isRunningOnUnixPlatform()) % separator % u"MacOS: " %
465  boolToYesNo(CBuildConfig::isRunningOnMacOSPlatform()) % separator % "Build Abi: " %
466  QSysInfo::buildAbi() % separator % u"Build CPU: " % QSysInfo::buildCpuArchitecture() % separator %
467  CBuildConfig::compiledWithInfo();
468 
469  if (this->supportsContexts()) { str += (separator % u"Supporting contexts"); }
470 
471  return str;
472  }
473 
475  int maxRedirects)
476  {
477  const CApplication::ProgressSlot progress;
478  return this->getFromNetwork(url, callback, progress, maxRedirects);
479  }
480 
482  const CApplication::ProgressSlot &progress, int maxRedirects)
483  {
484  return this->getFromNetwork(url.toNetworkRequest(), NoLogRequestId, callback, progress, maxRedirects);
485  }
486 
488  const CApplication::ProgressSlot &progress, int maxRedirects)
489  {
490  return this->getFromNetwork(url.toNetworkRequest(), logId, callback, progress, maxRedirects);
491  }
492 
494  const CApplication::CallbackSlot &callback, int maxRedirects)
495  {
496  const CApplication::ProgressSlot progress;
497  return this->getFromNetwork(request, callback, progress, maxRedirects);
498  }
499 
501  const CApplication::CallbackSlot &callback,
502  const CApplication::ProgressSlot &progress, int maxRedirects)
503  {
504  return this->getFromNetwork(request, NoLogRequestId, callback, progress, maxRedirects);
505  }
506 
508  const CApplication::CallbackSlot &callback,
509  const CApplication::ProgressSlot &progress, int maxRedirects)
510  {
511  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects,
512  [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
513  QNetworkReply *nr = qam.get(request);
514  return nr;
515  });
516  }
517 
519  const CApplication::CallbackSlot &callback, int maxRedirects)
520  {
521  const CApplication::ProgressSlot progress;
522  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects,
523  [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
524  QNetworkReply *nr = qam.deleteResource(request);
525  return nr;
526  });
527  }
528 
529  QNetworkReply *CApplication::postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data,
530  const CSlot<void(QNetworkReply *)> &callback)
531  {
532  return this->httpRequestImpl(request, logId, callback, NoRedirects,
533  [data](QNetworkAccessManager &qam, const QNetworkRequest &request) {
534  QNetworkReply *nr = qam.post(request, data);
535  return nr;
536  });
537  }
538 
540  const CSlot<void(QNetworkReply *)> &callback)
541  {
542  if (multiPart->thread() != m_accessManager->thread()) { multiPart->moveToThread(m_accessManager->thread()); }
543 
544  QPointer<CApplication> myself(this);
545  return httpRequestImpl(request, logId, callback, NoRedirects,
546  [=](QNetworkAccessManager &qam, const QNetworkRequest &request) {
547  QNetworkReply *nr = nullptr;
548  if (!myself) { return nr; }
549  if (!multiPart) { return nr; }
550  nr = qam.post(request, multiPart);
551  multiPart->setParent(nr);
552  return nr;
553  });
554  }
555 
556  QNetworkReply *CApplication::headerFromNetwork(const CUrl &url, const CallbackSlot &callback, int maxRedirects)
557  {
558  return headerFromNetwork(url.toNetworkRequest(), callback, maxRedirects);
559  }
560 
562  int maxRedirects)
563  {
564  return httpRequestImpl(
565  request, NoLogRequestId, callback, maxRedirects,
566  [](QNetworkAccessManager &qam, const QNetworkRequest &request) { return qam.head(request); });
567  }
568 
569  QNetworkReply *CApplication::downloadFromNetwork(const CUrl &url, const QString &saveAsFileName,
570  const CSlot<void(const CStatusMessage &)> &callback,
571  int maxRedirects)
572  {
573  // upfront checks
574  if (url.isEmpty()) { return nullptr; }
575  if (saveAsFileName.isEmpty()) { return nullptr; }
576  const QFileInfo fi(saveAsFileName);
577  if (!fi.dir().exists()) { return nullptr; }
578 
579  // function called with reply when done
580  CallbackSlot callbackSlot(this, [=](QNetworkReply *reply) {
582  CStatusMessage msg;
583  if (reply->error() != QNetworkReply::NoError)
584  {
585  msg = CStatusMessage(this, CStatusMessage::SeverityError, u"Download for '%1' failed: '%2'")
586  << url.getFullUrl() << nwReply->errorString();
587  }
588  else
589  {
590  const bool ok = CFileUtils::writeByteArrayToFile(reply->readAll(), saveAsFileName);
591  msg = ok ? CStatusMessage(this, CStatusMessage::SeverityInfo, u"Saved file '%1' downloaded from '%2'")
592  << saveAsFileName << url.getFullUrl() :
594  u"Saving file '%1' downloaded from '%2' failed")
595  << saveAsFileName << url.getFullUrl();
596  }
597  nwReply->close();
598  QTimer::singleShot(0, callback.object(), [=] {
599  if (!sApp || sApp->isShuttingDown()) { return; }
600  callback(msg);
601  });
602  });
603 
604  ProgressSlot progressSlot(this, [=](int, qint64, qint64, const QUrl &) {
605  // so far not implemented
606  });
607 
608  QNetworkReply *reply = this->getFromNetwork(url, callbackSlot, progressSlot, maxRedirects);
609  return reply;
610  }
611 
612  void CApplication::deleteAllCookies() { m_cookieManager->deleteAllCookies(); }
613 
614  void CApplication::exit(int retcode)
615  {
616  if (sApp) { instance()->gracefulShutdown(); }
617 
618  // when the event loop is not running, this does nothing
619  QCoreApplication::exit(retcode);
620  }
621 
622  QStringList CApplication::arguments() { return QCoreApplication::arguments(); }
623 
624  int CApplication::indexOfCommandLineOption(const QCommandLineOption &option, const QStringList &args)
625  {
626  const QStringList names = option.names();
627  if (names.isEmpty() || args.isEmpty()) { return -1; }
628  int i = -1;
629  for (const QString &arg : args)
630  {
631  i++;
632  QString a;
633  if (arg.startsWith("--")) { a = arg.mid(2); }
634  else if (arg.startsWith("-")) { a = arg.mid(1); }
635  else { continue; }
636 
637  if (names.contains(a, Qt::CaseInsensitive)) { return i; }
638  }
639  return -1;
640  }
641 
642  void CApplication::argumentsWithoutOption(const QCommandLineOption &option, QStringList &args)
643  {
644  const int index = indexOfCommandLineOption(option, args);
645  if (index < 0) { return; }
646 
647  // remove argument and its value
648  args.removeAt(index);
649  if (!option.valueName().isEmpty() && args.size() > index) { args.removeAt(index); }
650  }
651 
652  void CApplication::processEventsFor(int milliseconds)
653  {
654  // sApp check allows to use it in test cases without sApp
655  if (sApp && sApp->isShuttingDown()) { return; }
656  QEventLoop eventLoop;
657  QTimer::singleShot(milliseconds, &eventLoop, &QEventLoop::quit);
659  eventLoop.exec();
660  }
661 
662  CStatusMessageList CApplication::initContextsAndStartCoreFacade(const CCoreFacadeConfig &coreConfig)
663  {
664  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
665 
666  m_coreFacadeConfig = coreConfig;
667  const CStatusMessage msg = this->initLocalSettings();
668  if (msg.isFailure()) { return msg; }
669 
670  // now we can use settings
671  // if not yet initialized, init web data services
672  if (!m_webDataServices)
673  {
674  const CStatusMessageList msgs = this->initAndStartWebDataServices(
675  CWebReaderFlags::AllReaders, CDatabaseReaderConfigList::forPilotClient());
676  if (msgs.hasErrorMessages()) { return msgs; }
677  }
678  return this->startCoreFacade(); // will do nothing if setup is not yet loaded
679  }
680 
681  CStatusMessageList CApplication::startCoreFacadeWithoutContexts()
682  {
683  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
684 
685  m_coreFacadeConfig = CCoreFacadeConfig(CCoreFacadeConfig::NotUsed);
686  const CStatusMessage msg = this->initLocalSettings();
687  if (msg.isFailure()) { return msg; }
688 
689  return this->startCoreFacade(); // will do nothing if setup is not yet loaded
690  }
691 
692  CStatusMessageList CApplication::initAndStartWebDataServices(CWebReaderFlags::WebReader webReader,
693  const db::CDatabaseReaderConfigList &dbReaderConfig)
694  {
695  Q_ASSERT_X(m_webDataServices.isNull(), Q_FUNC_INFO, "Services already started");
696  SWIFT_VERIFY_X(QSslSocket::supportsSsl(), Q_FUNC_INFO, "No SSL");
697  if (!QSslSocket::supportsSsl()) { return CStatusMessage(this).error(u"No SSL supported, can`t be used"); }
698 
699  return this->startWebDataServices(webReader, dbReaderConfig);
700  }
701 
702  bool CApplication::isLocalContext() const
703  {
704  return this->getIContextApplication() && this->getIContextApplication()->isUsingImplementingObject();
705  }
706 
707  bool CApplication::isDBusContext() const
708  {
709  return this->getIContextApplication() && !this->getIContextApplication()->isUsingImplementingObject() &&
710  !this->getIContextApplication()->isEmptyObject();
711  }
712 
713  CStatusMessageList CApplication::startCoreFacade()
714  {
715  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
716 
717  if (!m_setupReader || !m_setupReader->isSetupAvailable())
718  {
719  return CStatusMessage(this).error(u"No setup reader or setup available");
720  }
721 
722  Q_ASSERT_X(m_coreFacade.isNull(), Q_FUNC_INFO, "Cannot alter facade");
723  Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No facade without setup possible");
724  Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Need running web data services");
725 
726  const CStatusMessageList msgs(CStatusMessage(this).info(u"Will start core facade now"));
727  m_coreFacade.reset(new CCoreFacade(m_coreFacadeConfig));
728  emit this->coreFacadeStarted();
729  return msgs;
730  }
731 
732  CStatusMessageList CApplication::startWebDataServices(CWebReaderFlags::WebReader webReader,
733  const db::CDatabaseReaderConfigList &dbReaderConfig)
734  {
735  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
736 
737  if (!m_setupReader || !m_setupReader->isSetupAvailable())
738  {
739  return CStatusMessage(this).error(u"No setup reader or setup available");
740  }
741 
742  Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No web data services without setup possible");
743  CStatusMessageList msgs;
744  if (!m_webDataServices)
745  {
746  msgs.push_back(CStatusMessage(this).info(u"Will start web data services now"));
747  m_webDataServices.reset(new CWebDataServices(webReader, dbReaderConfig, this));
748  Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Missing web services");
749 
750  emit this->webDataServicesStarted(true);
751  }
752  else { msgs.push_back(CStatusMessage(this).info(u"Web data services already running")); }
753 
754  return msgs;
755  }
756 
757  void CApplication::initLogging()
758  {
759  CLogHandler::instance()->install(); // make sure we have a log handler!
760 
761  // File logger
762  m_fileLogger.reset(new CFileLogger(this));
763  connect(CLogHandler::instance(), &CLogHandler::localMessageLogged, m_fileLogger.data(),
764  &CFileLogger::writeStatusMessageToFile);
765  connect(CLogHandler::instance(), &CLogHandler::remoteMessageLogged, m_fileLogger.data(),
766  &CFileLogger::writeStatusMessageToFile);
767  m_fileLogger->changeLogPattern(CLogPattern().withSeverityAtOrAbove(CStatusMessage::SeverityDebug));
768  }
769 
770  void CApplication::initParser()
771  {
772  m_parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
773  m_parser.setApplicationDescription(m_applicationName);
774  m_cmdHelp = m_parser.addHelpOption();
775  m_cmdVersion = m_parser.addVersionOption();
776  m_allOptions.append(m_cmdHelp);
777  m_allOptions.append(m_cmdVersion);
778 
779  // dev. system
780  m_cmdDevelopment = QCommandLineOption({ "dev", "development" },
781  QCoreApplication::translate("application", "Dev. system features?"));
782  this->addParserOption(m_cmdDevelopment);
783 
784  // Skip single application check
785  m_cmdSkipSingleApp = QCommandLineOption(
786  { "skipsa", "skipsingleapp" }, QCoreApplication::translate("application", "Skip the single app.test."));
787  this->addParserOption(m_cmdSkipSingleApp);
788 
789  // reset caches upfront
790  m_cmdClearCache = QCommandLineOption({ "ccache", "clearcache" },
791  QCoreApplication::translate("application", "Clear (reset) the caches."));
792  this->addParserOption(m_cmdClearCache);
793 
794  // test crashpad upload
795  m_cmdTestCrashpad = QCommandLineOption(
796  { "testcp", "testcrashpad" }, QCoreApplication::translate("application", "Trigger crashpad situation."));
797  this->addParserOption(m_cmdTestCrashpad);
798  }
799 
800  bool CApplication::isSet(const QCommandLineOption &option) const { return (m_parser.isSet(option)); }
801 
802  void CApplication::registerMetadata()
803  {
806  }
807 
808  QStringList CApplication::clearCaches()
809  {
810  const QStringList files(CDataCache::instance()->enumerateStore());
811  CDataCache::instance()->clearAllValues();
812  return files;
813  }
814 
815  void CApplication::gracefulShutdown()
816  {
817  if (m_shutdown) { return; }
818  if (m_shutdownInProgress) { return; }
819  m_shutdownInProgress = true;
820 
821  CLogMessage(this).info(u"Graceful shutdown of CApplication started");
822 
823  // info that we will shutdown
824  emit this->aboutToShutdown();
825 
826  // Release all input devices to not cause any accidental hotkey triggers anymore.
827  // This is also necessary to properly free platform specific instances at a defined point in time.
828  if (m_inputManager)
829  {
830  CLogMessage(this).info(u"Graceful shutdown of CApplication, released devices");
831  m_inputManager->releaseDevices();
832  }
833 
834  // save settings (but only when application was really alive)
835  if (m_parsed && m_saveSettingsOnShutdown)
836  {
837  const CStatusMessage m = this->supportsContexts() ? this->getIContextApplication()->saveSettings() :
838  CSettingsCache::instance()->saveToStore();
839  CLogMessage::preformatted(m);
840  }
841 
842  // from here on we really rip apart the application object
843  // and it should no longer be used
844 
845  if (this->supportsContexts(true))
846  {
847  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of contexts");
848 
849  // clean up facade
850  m_coreFacade->gracefulShutdown();
851  m_coreFacade.reset();
852  }
853 
854  if (m_webDataServices)
855  {
856  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of web services");
857 
858  m_webDataServices->gracefulShutdown();
859  m_webDataServices.reset();
860  }
861 
862  if (m_gitHubPackagesReader) { m_gitHubPackagesReader.reset(); }
863 
864  if (m_setupReader) { m_setupReader.reset(); }
865 
866  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of logger");
867  m_fileLogger->close();
868 
869  // clean up all in "deferred delete state"
870  qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
871  processEventsFor(500);
872 
873  // completed
874  m_shutdown = true;
875  sApp = nullptr;
876 
877  disconnect(this);
878  }
879 
880  void CApplication::onStartUpCompleted()
881  {
882  // void
883  }
884 
885  void CApplication::initNetwork()
886  {
887  if (!m_accessManager) { m_accessManager = new QNetworkAccessManager(this); }
888 
889  m_cookieManager = new CCookieManager(this);
890  m_cookieManager->setParent(m_accessManager);
891  m_accessManager->setCookieJar(m_cookieManager);
892 
893  // Init network
894  Q_ASSERT_X(m_accessManager, Q_FUNC_INFO, "Need QAM");
895  }
896 
898 
899  const QString &CApplication::executable()
900  {
901  static const QString e(QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName());
902  return e;
903  }
904 
905  const QStringList &CApplication::getLogCategories()
906  {
907  static const QStringList l({ "swift.application", "swift." % executable() });
908  return l;
909  }
910 
911  // ---------------------------------------------------------------------------------
912  // Parsing
913  // ---------------------------------------------------------------------------------
914 
915  bool CApplication::addParserOption(const QCommandLineOption &option)
916  {
917  m_allOptions.append(option);
918  return m_parser.addOption(option);
919  }
920 
921  bool CApplication::addParserOptions(const QList<QCommandLineOption> &options)
922  {
923  m_allOptions.append(options);
924  return m_parser.addOptions(options);
925  }
926 
927  void CApplication::addDBusAddressOption()
928  {
929  m_cmdDBusAddress = QCommandLineOption(
930  { "dbus", "dbusaddress" },
931  QCoreApplication::translate("application", "DBus address (session, system, P2P IP e.g. 192.168.23.5)"),
932  "dbusaddress");
933  this->addParserOption(m_cmdDBusAddress);
934  }
935 
936  void CApplication::addNetworkOptions() { this->addParserOptions(IContextNetwork::getCmdLineOptions()); }
937 
938  void CApplication::addAudioOptions() { this->addParserOptions(CContextAudioBase::getCmdLineOptions()); }
939 
940  QString CApplication::getCmdDBusAddressValue() const
941  {
942  if (!this->isParserOptionSet(m_cmdDBusAddress)) { return {}; }
943  const QString v(this->getParserValue(m_cmdDBusAddress));
944  const QString dBusAddress(CDBusServer::normalizeAddress(v));
945  return dBusAddress;
946  }
947 
948  bool CApplication::isParserOptionSet(const QString &option) const { return m_parser.isSet(option); }
949 
950  bool CApplication::skipSingleApplicationCheck() const { return this->isParserOptionSet(m_cmdSkipSingleApp); }
951 
952  bool CApplication::isParserOptionSet(const QCommandLineOption &option) const { return m_parser.isSet(option); }
953 
954  QString CApplication::getParserValue(const QString &option) const { return m_parser.value(option).trimmed(); }
955 
956  QString CApplication::getParserValue(const QCommandLineOption &option) const
957  {
958  return m_parser.value(option).trimmed();
959  }
960 
961  bool CApplication::parseCommandLineArgsAndLoadSetup()
962  {
963  if (!this->startupCheck()) return false;
964  if (!this->parseCommandLineArguments()) return false;
965  if (!this->loadSetupAndHandleErrors()) return false;
966  return true;
967  }
968 
969  bool CApplication::parseCommandLineArguments()
970  {
971  if (m_parsed) { return m_parsed; } // already done
972 
973  // we call parse because we also want to display a GUI error message when applicable
975  if (!m_parser.parse(args))
976  {
977  this->cmdLineErrorMessage("Parser error:", m_parser.errorText());
978  return false;
979  }
980 
981  if (m_alreadyRunning && !this->skipSingleApplicationCheck())
982  {
983  this->cmdLineErrorMessage("Program must only run once",
984  "You cannot run two or more instances side-by-side.");
985  return false;
986  }
987 
988  // help/version
989  if (m_parser.isSet(m_cmdHelp))
990  {
991  // Important: parser help will already stop application
992  this->cmdLineHelpMessage();
993  return false;
994  }
995  if (m_parser.isSet(m_cmdVersion))
996  {
997  // Important: version will already stop application
998  this->cmdLineVersionMessage();
999  return false;
1000  }
1001 
1002  // dev.
1003  m_devFlag = this->initIsRunningInDeveloperEnvironment();
1004 
1005  // Hookin, other parsing
1006  if (!this->parsingHookIn()) { return false; }
1007 
1008  // setup reader
1009  m_parsed = true;
1010  return true;
1011  }
1012 
1013  bool CApplication::startupCheck() const
1014  {
1015  const QStringList verifyErrors = CSwiftDirectories::verifyRuntimeDirectoriesAndFiles();
1016  if (!verifyErrors.isEmpty() && !m_applicationInfo.isUnitTest())
1017  {
1018  cmdLineErrorMessage("Missing runtime directories/files:", verifyErrors.join(", "));
1019  return false;
1020  }
1021  return true;
1022  }
1023 
1024  bool CApplication::loadSetupAndHandleErrors()
1025  {
1026  const CStatusMessageList msgs = loadSetup();
1027 
1028  if (msgs.isFailure()) { displaySetupLoadFailure(msgs); }
1029  return msgs.isSuccess();
1030  }
1031 
1032  void CApplication::displaySetupLoadFailure(swift::misc::CStatusMessageList)
1033  {
1034  // Ignore for CLI application
1035  // Already logged to console
1036  }
1037 
1038  void CApplication::cmdLineErrorMessage(const QString &text, const QString &informativeText) const
1039  {
1040  fputs(qPrintable(text + informativeText), stderr);
1041  }
1042 
1043  void CApplication::cmdLineErrorMessage(const CStatusMessageList &msgs) const
1044  {
1045  if (msgs.isEmpty()) { return; }
1046  if (!msgs.hasErrorMessages()) { return; }
1047  CApplication::cmdLineErrorMessage(msgs.toQString(true), "");
1048  }
1049 
1050  QPointer<ISimulator> CApplication::getISimulator() const
1051  {
1052  if (!this->hasSimulator()) { return nullptr; }
1053  return this->getCoreFacade()->getCContextSimulator()->simulator();
1054  }
1055 
1056  bool CApplication::hasSimulator() const
1057  {
1058  if (!this->getCoreFacade()) { return false; }
1059  if (!this->getCoreFacade()->getIContextSimulator()->isUsingImplementingObject()) { return false; }
1060  return (this->getCoreFacade()->getCContextSimulator()); // should always be true
1061  }
1062 
1063  void CApplication::cmdLineHelpMessage()
1064  {
1065  m_parser.showHelp(); // terminates
1066  Q_UNREACHABLE();
1067  }
1068 
1069  void CApplication::cmdLineVersionMessage()
1070  {
1071  m_parser.showVersion(); // terminates
1072  Q_UNREACHABLE();
1073  }
1074 
1075  QStringList CApplication::argumentsJoined(const QStringList &newArguments, const QStringList &removeArguments) const
1076  {
1077  QStringList joinedArguments = CApplication::arguments();
1078  QStringList newArgumentsChecked = newArguments;
1079 
1080  // remove the executable argument if it exists at position 0
1081  if (!joinedArguments.isEmpty() && !joinedArguments.at(0).startsWith("-"))
1082  {
1083  joinedArguments.removeFirst();
1084  } // was cmd line argument
1085  if (!newArgumentsChecked.isEmpty() && !newArgumentsChecked.at(0).startsWith("-"))
1086  {
1087  newArgumentsChecked.removeFirst();
1088  } // was cmd line argument
1089 
1090  // remove all values before checking options
1091  static const QRegularExpression regExp("^-");
1092  QStringList toBeRemoved(newArgumentsChecked.filter(regExp));
1093  toBeRemoved.append(removeArguments.filter(regExp));
1094  toBeRemoved.append("--installer");
1095  toBeRemoved.removeDuplicates();
1096 
1097  if (!joinedArguments.isEmpty() && !toBeRemoved.isEmpty())
1098  {
1099  // remove all options from removeArguments
1100  // consider alias names, that is why we check on option
1101  for (const QCommandLineOption &option : m_allOptions)
1102  {
1103  const int n = indexOfCommandLineOption(option, toBeRemoved);
1104  if (n >= 0) { argumentsWithoutOption(option, joinedArguments); }
1105  }
1106  }
1107 
1108  joinedArguments.append(newArgumentsChecked);
1109  return joinedArguments;
1110  }
1111 
1112  // ---------------------------------------------------------------------------------
1113  // Contexts
1114  // ---------------------------------------------------------------------------------
1115 
1116  shared_state::CDataLinkDBus *CApplication::getDataLinkDBus() { return getCoreFacade()->getDataLinkDBus(); }
1117 
1118  bool CApplication::supportsContexts(bool ignoreShutdownTest) const
1119  {
1120  if (!ignoreShutdownTest && m_shutdown) { return false; }
1121  if (m_coreFacade.isNull()) { return false; }
1122  if (!m_coreFacade->getIContextApplication()) { return false; }
1123  return (!m_coreFacade->getIContextApplication()->isEmptyObject());
1124  }
1125 
1126  const IContextNetwork *CApplication::getIContextNetwork() const
1127  {
1128  if (!supportsContexts()) { return nullptr; }
1129  return m_coreFacade->getIContextNetwork();
1130  }
1131 
1132  const IContextAudio *CApplication::getIContextAudio() const
1133  {
1134  if (!supportsContexts()) { return nullptr; }
1135  return m_coreFacade->getIContextAudio();
1136  }
1137 
1138  const CContextAudioBase *CApplication::getCContextAudioBase() const
1139  {
1140  if (!supportsContexts()) { return nullptr; }
1141  return m_coreFacade->getCContextAudioBase();
1142  }
1143 
1144  const IContextApplication *CApplication::getIContextApplication() const
1145  {
1146  if (!supportsContexts()) { return nullptr; }
1147  return m_coreFacade->getIContextApplication();
1148  }
1149 
1150  const IContextOwnAircraft *CApplication::getIContextOwnAircraft() const
1151  {
1152  if (!supportsContexts()) { return nullptr; }
1153  return m_coreFacade->getIContextOwnAircraft();
1154  }
1155 
1156  const IContextSimulator *CApplication::getIContextSimulator() const
1157  {
1158  if (!supportsContexts()) { return nullptr; }
1159  return m_coreFacade->getIContextSimulator();
1160  }
1161 
1162  IContextNetwork *CApplication::getIContextNetwork()
1163  {
1164  if (!supportsContexts()) { return nullptr; }
1165  return m_coreFacade->getIContextNetwork();
1166  }
1167 
1168  IContextAudio *CApplication::getIContextAudio()
1169  {
1170  if (!supportsContexts()) { return nullptr; }
1171  return m_coreFacade->getIContextAudio();
1172  }
1173 
1174  CContextAudioBase *CApplication::getCContextAudioBase()
1175  {
1176  if (!supportsContexts()) { return nullptr; }
1177  return m_coreFacade->getCContextAudioBase();
1178  }
1179 
1180  IContextApplication *CApplication::getIContextApplication()
1181  {
1182  if (!supportsContexts()) { return nullptr; }
1183  return m_coreFacade->getIContextApplication();
1184  }
1185 
1186  IContextOwnAircraft *CApplication::getIContextOwnAircraft()
1187  {
1188  if (!supportsContexts()) { return nullptr; }
1189  return m_coreFacade->getIContextOwnAircraft();
1190  }
1191 
1192  IContextSimulator *CApplication::getIContextSimulator()
1193  {
1194  if (!supportsContexts()) { return nullptr; }
1195  return m_coreFacade->getIContextSimulator();
1196  }
1197 
1198  void CApplication::onCoreFacadeStarted()
1199  {
1200  // void
1201  }
1202 
1203  // ---------------------------------------------------------------------------------
1204  // Setup
1205  // ---------------------------------------------------------------------------------
1206 
1207  bool CApplication::hasSetupReader() const { return !m_setupReader.isNull(); }
1208 
1209  CSetupReader *CApplication::getSetupReader() const { return m_setupReader.data(); }
1210 
1211  CStatusMessageList CApplication::loadSetup()
1212  {
1213  if (m_shutdown) { return CStatusMessage(this).warning(u"Shutting down, not reading"); }
1214  if (!m_setupReader) { return CStatusMessage(this).error(u"No reader for setup/version"); }
1215  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Not yet parsed");
1216  const CStatusMessageList requestMsgs = m_setupReader->loadSetup();
1217  return requestMsgs;
1218  }
1219 
1220  CUrl CApplication::getVatsimMetarUrl() const
1221  {
1222  if (m_shutdown) { return {}; }
1223  if (m_webDataServices)
1224  {
1225  const CUrl url(m_webDataServices->getVatsimMetarUrl());
1226  if (!url.isEmpty()) { return url; }
1227  }
1228  if (m_setupReader) { return m_setupReader->getSetup().getVatsimMetarsUrl(); }
1229  return {};
1230  }
1231 
1232  CUrl CApplication::getVatsimDataFileUrl() const
1233  {
1234  if (m_shutdown) { return {}; }
1235  if (m_webDataServices)
1236  {
1237  const CUrl url(m_webDataServices->getVatsimDataFileUrl());
1238  if (!url.isEmpty()) { return url; }
1239  }
1240  if (m_setupReader) { return m_setupReader->getSetup().getVatsimDataFileUrl(); }
1241  return {};
1242  }
1243 
1244  CUrl CApplication::getVatsimServerFileUrl() const
1245  {
1246  if (m_shutdown || !m_setupReader) { return {}; }
1247 
1248  return m_setupReader->getSetup().getVatsimServerFileUrl();
1249  }
1250 
1251  CUrl CApplication::getVatsimFsdHttpUrl() const
1252  {
1253  if (m_shutdown || !m_setupReader) { return {}; }
1254 
1255  return m_setupReader->getSetup().getVatsimFsdHttpUrl();
1256  }
1257 
1258  void CApplication::onCrashDumpUploadEnabledChanged()
1259  {
1260  const bool enabled = CBuildConfig::isReleaseBuild() && m_crashDumpUploadEnabled.getThreadLocal();
1261  this->enableCrashDumpUpload(enabled);
1262  }
1263 
1264  void CApplication::simulateCrash() { CCrashHandler::instance()->simulateCrash(); }
1265 
1266  void CApplication::simulateAssert() { CCrashHandler::instance()->simulateAssert(); }
1267 
1268  void CApplication::enableCrashDumpUpload(bool enable) { CCrashHandler::instance()->setUploadsEnabled(enable); }
1269 
1270  bool CApplication::isSupportingCrashpad() const
1271  {
1272 #ifdef SWIFT_USE_CRASHPAD
1273  return true;
1274 #else
1275  return false;
1276 #endif
1277  }
1278 
1279  void CApplication::httpRequestImplInQAMThread(const QNetworkRequest &request, int logId,
1280  const CallbackSlot &callback, const ProgressSlot &progress,
1281  int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
1282  {
1283  // run in QAM thread
1284  if (this->isShuttingDown()) { return; }
1285  QTimer::singleShot(0, m_accessManager, [=] {
1286  // should be now in QAM thread
1287  if (!sApp || sApp->isShuttingDown()) { return; }
1288  Q_ASSERT_X(CThreadUtils::isInThisThread(sApp->m_accessManager), Q_FUNC_INFO,
1289  "Wrong thread, must be QAM thread");
1290  this->httpRequestImpl(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
1291  });
1292  }
1293 
1294  QNetworkReply *CApplication::httpRequestImpl(const QNetworkRequest &request, int logId,
1295  const CApplication::CallbackSlot &callback, int maxRedirects,
1296  NetworkRequestOrPostFunction requestOrPostMethod)
1297  {
1298  ProgressSlot progress;
1299  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects, requestOrPostMethod);
1300  }
1301 
1302  QNetworkReply *CApplication::httpRequestImpl(const QNetworkRequest &request, int logId,
1303  const CallbackSlot &callback, const ProgressSlot &progress,
1304  int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
1305  {
1306  if (this->isShuttingDown()) { return nullptr; }
1307 
1308  QWriteLocker locker(&m_accessManagerLock);
1309  Q_ASSERT_X(m_accessManager->thread() == qApp->thread(), Q_FUNC_INFO,
1310  "Network manager supposed to be in main thread");
1311  if (!CThreadUtils::isInThisThread(m_accessManager))
1312  {
1313  this->httpRequestImplInQAMThread(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
1314  return nullptr; // not yet started, will be called again in QAM thread
1315  }
1316 
1317  Q_ASSERT_X(CThreadUtils::isInThisThread(m_accessManager), Q_FUNC_INFO, "Network manager thread mismatch");
1318  QNetworkRequest copiedRequest =
1319  CNetworkUtils::getSwiftNetworkRequest(request, this->getApplicationNameAndVersion());
1320 
1321  QNetworkReply *reply = getPostOrDeleteRequest(*m_accessManager, copiedRequest);
1323  reply->setProperty(CUrlLog::propertyNameId(), QVariant(logId));
1324  const QUrl url(reply->url());
1325  QString urlStr = url.toString();
1326 
1327  if (progress)
1328  {
1329  connect(reply, &QNetworkReply::downloadProgress, progress.object(),
1330  [=](qint64 current, qint64 max) { progress(logId, current, max, url); });
1331  }
1332 
1333  if (callback)
1334  {
1335  Q_ASSERT_X(callback.object(), Q_FUNC_INFO, "Need callback object (to determine thread)");
1336  connect(
1337  reply, &QNetworkReply::finished, callback.object(),
1338  [=] {
1339  // Called when finished!
1340  // QNetworkRequest::FollowRedirectsAttribute would allow auto redirect, but we use our approach as
1341  // it gives us better control \fixme: Check again on Qt 5.9: Added redirects policy to
1342  // QNetworkAccessManager (ManualRedirectsPolicy, NoLessSafeRedirectsPolicy,
1343  // SameOriginRedirectsPolicy, UserVerifiedRedirectsPolicy)
1344  const bool isRedirect = CNetworkUtils::isHttpStatusRedirect(reply);
1345  if (isRedirect && maxRedirects > 0)
1346  {
1347  const QUrl redirectUrl = CNetworkUtils::getHttpRedirectUrl(reply);
1348  if (!redirectUrl.isEmpty())
1349  {
1350  QNetworkRequest redirectRequest(redirectUrl);
1351  const int redirectsLeft = maxRedirects - 1;
1352  CLogMessage(sApp).info(u"Redirecting '%1' to '%2'") << urlStr << redirectUrl.toString();
1353  this->httpRequestImplInQAMThread(redirectRequest, logId, callback, progress, redirectsLeft,
1354  getPostOrDeleteRequest);
1355  return;
1356  }
1357  }
1358  // called when there are no more callbacks
1359  callback(reply);
1360  },
1361  Qt::QueuedConnection); // called in callback thread
1362  }
1363  return reply;
1364  }
1365 
1366  void CApplication::tagApplicationDataDirectory()
1367  {
1369  const QDir dir(d);
1370  if (!dir.exists() || !dir.isReadable()) { return; }
1371  const QString aiStr(this->getApplicationInfo().toJsonString());
1372  const QString filePath(CFileUtils::appendFilePaths(
1373  dir.path(), CApplicationInfo::fileName())); // will be overridden by next swift app
1374  CFileUtils::writeStringToFile(aiStr, filePath);
1375  }
1376 } // namespace swift::core
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
QStringList argumentsJoined(const QStringList &newArguments={}, const QStringList &removeArguments={}) const
Current parameters replaced by new arguments without the cmd line argument.
const QString & versionStringDetailed() const
String with beta, dev. and version.
QNetworkReply * getFromNetwork(const swift::misc::network::CUrl &url, const CallbackSlot &callback, int maxRedirects=DefaultMaxRedirects)
Request to get network reply.
QNetworkReply * downloadFromNetwork(const swift::misc::network::CUrl &url, const QString &saveAsFileName, const swift::misc::CSlot< void(const swift::misc::CStatusMessage &)> &callback, int maxRedirects=DefaultMaxRedirects)
Download file from network and store it as passed.
std::atomic_bool m_shutdown
Is being shutdown?
Definition: application.h:579
void restartApplication(const QStringList &newArguments={}, const QStringList &removeArguments={})
Stop and restart application.
bool m_parsed
Parsing accomplished?
Definition: application.h:576
data::CGlobalSetup getGlobalSetup() const
Global setup.
static constexpr int NoLogRequestId
network request without logging
Definition: application.h:413
bool isDeveloperFlagSet() const
Running with dev.flag?
Definition: application.h:173
swift::misc::db::CDistribution getOwnDistribution() const
Own distribution.
bool m_alreadyRunning
Application already running.
Definition: application.h:578
const char * swiftVersionChar()
swift info string
std::atomic_bool m_incognito
Incognito mode?
Definition: application.h:580
QCommandLineOption m_cmdClearCache
Clear cache.
Definition: application.h:573
const context::IContextApplication * getIContextApplication() const
Direct access to contexts if a CCoreFacade has been initialized.
virtual void onStartUpCompleted()
Startup completed.
static bool unregisterAsRunning()
Unregister from running.
bool isIncognito() const
Is incognito mode?
bool hasUnsavedSettings() const
Unsaved settings.
QNetworkReply * postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data, const CallbackSlot &callback)
Post to network.
virtual void onCoreFacadeStarted()
Called when facade/contexts have been started.
const QString & swiftVersionString() const
swift info string
void coreFacadeStarted()
Facade started.
void setIncognito(bool incognito)
Set incognito mode.
static QString getTemporaryDirectory()
Directory for temporary files.
bool hasWebDataServices() const
Web data services available?
virtual void cmdLineErrorMessage(const QString &text, const QString &informativeText) const
Display error message.
void init(bool withMetadata)
Init class, allows to init from swift::gui::CGuiApplication as well (pseudo virtual)
void updateInfoAvailable(bool success)
Update info available (cache, web load)
swift::misc::CStatusMessage saveSettingsByKey(const QStringList &keys)
Save all settings.
CApplication(swift::misc::CApplicationInfo::Application application, bool init=true)
Constructor.
Definition: application.cpp:83
static void exit(int retcode=EXIT_SUCCESS)
Exit application, perform graceful shutdown and exit.
QString getInfoString(const QString &separator) const
Comprehensive info.
bool isShuttingDown() const
Is application shutting down?
QNetworkReply * deleteResourceFromNetwork(const QNetworkRequest &request, int logId, const CallbackSlot &callback, int maxRedirects=DefaultMaxRedirects)
Request to delete a network resource from network, supporting swift::misc::network::CUrlLog.
static constexpr int NoRedirects
network request not allowing redirects
Definition: application.h:412
QCommandLineOption m_cmdDevelopment
Development flag.
Definition: application.h:572
bool isSet(const QCommandLineOption &option) const
Flag set or explicitly set to true.
std::atomic_bool m_shutdownInProgress
shutdown in progress?
Definition: application.h:581
static swift::misc::CApplicationInfoList getRunningApplications()
Information about all running apps (including this one only if exec() has already been called)
QStringList getUnsavedSettingsKeys() const
All unsaved settings.
const QString & getApplicationNameVersionDetailed() const
Version, name beta and dev info.
void toggleIncognito()
Toggle incognito mode.
virtual swift::misc::CStatusMessageList startHookIn()
Can be used to start special services.
Definition: application.h:546
static bool isApplicationRunning(swift::misc::CApplicationInfo::Application application)
Is application running?
bool m_started
Started with success?
Definition: application.h:577
swift::misc::db::CUpdateInfo getUpdateInfo() const
Update info.
bool isSetupAvailable() const
Setup already synchronized.
static QStringList clearCaches()
Clear the caches.
virtual bool start()
Start services, if not yet parsed call CApplication::parse.
bool supportsContexts(bool ignoreShutdownTest=false) const
Supports contexts.
void reloadUpdateInfo()
Reload update info.
CWebDataServices * getWebDataServices() const
Get the web data services.
const swift::misc::CApplicationInfo & getApplicationInfo() const
swift application running
Definition: application.h:138
int exec()
Finishes initialization and executes the event loop.
const QString & getApplicationNameAndVersion() const
Application name and version.
virtual void gracefulShutdown()
Graceful shutdown.
void saveSettingsOnShutdown(bool saveSettings)
Save settings on shutdown.
QCommandLineOption m_cmdTestCrashpad
Test a crasphpad upload.
Definition: application.h:574
static void registerMetadata()
Register metadata.
bool isAlreadyRunning() const
True if this swift application is already running (including different versions)
virtual bool hasMinimumMappingVersion() const
Minimum mapping version check.
void startUpCompleted(bool success)
Startup has been completed Will be triggered shortly before starting the event loop.
QNetworkReply * headerFromNetwork(const swift::misc::network::CUrl &url, const CallbackSlot &callback, int maxRedirects=NoRedirects)
Request to get network repy using HTTP's HEADER method.
static CApplication * instance()
Similar to.
static bool registerAsRunning()
Register as running.
Configuration object for the contexts.
ContextMode getMode() const
Mode.
@ Remote
context runs in a different process.
The class providing facades (the contexts) for all DBus relevant operations.
Definition: corefacade.h:57
Read available updates from GitHub Packages REST API.
void updateInfoAvailable(bool available)
Updates have been received from GitHub Packages.
Input manager handling hotkey function calls.
Definition: inputmanager.h:32
void createDevices()
Creates low level input devices. Once completed, hotkeys start to be processed.
Read the central URLs / locations of our data, setup and versions.
Definition: setupreader.h:36
data::CGlobalSetup getSetup() const
Current setup (reader URLs, DB location, crash server)
Definition: setupreader.cpp:79
Encapsulates reading data from web sources.
virtual misc::CStatusMessage saveSettingsByKey(const QStringList &keys)=0
Save core settings to disk.
virtual QStringList getUnsavedSettingsKeys() const =0
Get keys of all unsaved settings currently in core settings cache.
Audio context interface.
Definition: contextaudio.h:61
const IContextSimulator * getIContextSimulator() const
Context for simulator.
Definition: context.cpp:69
IContextNetwork * getIContextNetwork()
Context for network.
Definition: context.cpp:29
const IContextApplication * getIContextApplication() const
Context for application.
Definition: context.cpp:39
IContextAudio * getIContextAudio()
Context for network.
Definition: context.cpp:33
IContextOwnAircraft * getIContextOwnAircraft()
Context for own aircraft.
Definition: context.cpp:44
Global settings for readers, debug flags, etc.
Definition: globalsetup.h:31
Value object encapsulating a list of reader configs.
Description of a swift application.
Application getApplication() const
Get application.
static const QString & fileName()
File name of the application info file.
Application
Enumeration of application roles.
const CProcessInfo & getProcessInfo() const
Get process info.
void setApplicationDataDirectory(const QString &appDataDir)
Set application data dir.
List of swift application descriptions.
bool containsApplication(CApplicationInfo::Application application) const
List containing entry for CApplicationInfo::Application ?
QString toJsonString(QJsonDocument::JsonFormat format=QJsonDocument::Indented) const
Convenience function JSON as string.
CStatusMessage convertFromJsonNoThrow(const QJsonObject &json, const CLogCategoryList &categories, const QString &prefix)
Call convertFromJson, catch any CJsonException that is thrown and return it as CStatusMessage.
Class to write log messages to file.
Definition: filelogger.h:22
static QString readLockedFileToString(const QString &fileNameAndPath)
Read file into string, with a lock so two applications can't access at the same time.
Definition: fileutils.cpp:78
static bool writeStringToFile(const QString &content, const QString &fileNameAndPath)
Write string to text file.
Definition: fileutils.cpp:40
static bool writeByteArrayToFile(const QByteArray &data, const QString &fileNameAndPath)
Write byte array to file.
Definition: fileutils.cpp:51
static bool writeStringToLockedFile(const QString &content, const QString &fileNameAndPath)
Write string to file, with a lock so two applications can't access at the same time.
Definition: fileutils.cpp:61
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
Definition: fileutils.cpp:95
Base class with a member CIdentifier to be inherited by a class which has an identity in the environm...
Definition: identifiable.h:24
A sequence of log categories.
Class for emitting a log message.
Definition: logmessage.h:27
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
Value class for matching log messages based on their categories.
Definition: logpattern.h:49
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
bool containsBy(Predicate p) const
Return true if there is an element for which a given predicate returns true.
Definition: range.h:101
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
int removeIf(Predicate p)
Remove elements for which a given predicate returns true.
Definition: sequence.h:446
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
int remove(const T &object)
Remove all elements equal to the given object, if it is contained.
Definition: sequence.h:435
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
CStatusMessage loadFromStore()
Load settings from disk.
void enableLocalSave()
Connects signal CValueCache::valuesSaveRequested to a private slot that saves the values....
static CSettingsCache * instance()
Return the singleton instance.
CStatusMessage saveToStore(const QString &keyPrefix={})
Save settings to disk.
Callable wrapper for a member function with function signature F.
Definition: slot.h:62
Streamable status message, e.g.
constexpr static auto SeverityError
Status severities.
constexpr static auto SeverityInfo
Status severities.
bool isFailure() const
Operation considered unsuccessful.
Status messages, e.g. from Core -> GUI.
bool isFailure() const
Any message is marked as failure.
bool hasErrorMessages() const
Error messages.
bool isSuccess() const
All messages are marked as success.
static const QString & normalizedApplicationDataDirectory()
swift application data directory for one specific installation (a version)
QStringList getAllUnsavedKeys(const QString &keyPrefix={}) const
Return keys of all values which have been changed but not saved.
Base class for CWorker and CContinuousWorker.
Definition: worker.h:72
static const QSet< CWorkerBase * > & allWorkers()
All workers currently existing.
Definition: worker.h:163
Distributions for channel.
Definition: distribution.h:27
Update info, i.e. artifacts and distributions.
Definition: updateinfo.h:24
CDistribution anticipateOwnDistribution() const
Own distribution.
Definition: updateinfo.cpp:63
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:74
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
bool isEmpty() const
Empty.
Definition: url.cpp:54
QNetworkRequest toNetworkRequest() const
To request.
Definition: url.cpp:113
QString getFullUrl(bool withQuery=true) const
Qualified name.
Definition: url.cpp:84
Core data traits (aka cached values) and classes.
Classes interacting with the swift database (aka "datastore").
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
void registerMetadata()
Register all relevant metadata in swift::core.
Free functions in swift::misc.
SWIFT_MISC_EXPORT void setMockCacheRootDirectory(const QString &path)
Overwrite the default root directory for cache and settings, for testing purposes.
void registerMetadata()
Register all relevant metadata in Misc.
QString applicationName()
Get application name.
Definition: filelogger.cpp:24
const char * constData() const const
QStringList names() const const
QString valueName() const const
QString applicationFilePath()
void setApplicationName(const QString &application)
void setApplicationVersion(const QString &version)
QStringList arguments()
void exit(int returnCode)
QCoreApplication * instance()
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
qint64 currentMSecsSinceEpoch()
bool exists() const const
QString tempPath()
int exec(QEventLoop::ProcessEventsFlags flags)
void quit()
QDir dir() const const
QByteArray readAll()
void append(QList< T > &&value)
QList< T >::const_reference at(qsizetype i) const const
bool isEmpty() const const
void removeAt(qsizetype i)
void removeFirst()
qsizetype size() const const
bool startsWith(QList< T >::parameter_type value) const const
QNetworkReply * deleteResource(const QNetworkRequest &request)
QNetworkReply * get(const QNetworkRequest &request)
QNetworkReply * head(const QNetworkRequest &request)
QNetworkReply * post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
QNetworkReply::NetworkError error() const const
QUrl url() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
bool moveToThread(QThread *targetThread)
void setObjectName(QAnyStringView name)
void setParent(QObject *parent)
bool setProperty(const char *name, QVariant &&value)
QThread * thread() const const
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
bool supportsSsl()
QString writableLocation(QStandardPaths::StandardLocation type)
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) &&
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringList filter(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
qsizetype removeDuplicates()
QString buildAbi()
QString buildCpuArchitecture()
CaseInsensitive
QueuedConnection
bool isValid() const const
QString path() const const
QFuture< QtFuture::ArgsType< Signal >> connect(Sender *sender, Signal signal)
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26