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 =
77  QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/org.swift-project/";
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
110  connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this,
112 
113  // metadata
114  if (withMetadata) { CApplication::registerMetadata(); }
115 
116  // unit test
117  if (this->getApplicationInfo().getApplication() == CApplicationInfo::UnitTest)
118  {
119  const QString tempPath(CApplication::getTemporaryDirectory());
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,
143  &CApplication::updateInfoAvailable, Qt::QueuedConnection);
145 
146  // startup done
148  Qt::QueuedConnection);
150  Qt::QueuedConnection);
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  {
210  const QString prg = QCoreApplication::applicationFilePath();
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 CGlobalSetup(); }
263  const CSetupReader *r = m_setupReader.data();
264  if (!r) { return CGlobalSetup(); }
265  return r->getSetup();
266  }
267 
269  {
270  if (m_shutdown) { return CUpdateInfo(); }
271  if (!m_gitHubPackagesReader) { return CUpdateInfo(); }
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 
366  const QString &CApplication::versionStringDetailed() const
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 
386  const QString &CApplication::swiftVersionString() const
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  if (d.isRestricted() && this->isSet(m_cmdDevelopment)) { return true; }
409 
410  return false;
411  }
412 
413  CStatusMessage CApplication::initLocalSettings()
414  {
415  if (m_localSettingsLoaded) { return CStatusMessage(); }
416  m_localSettingsLoaded = true;
417 
418  // trigger loading and saving of settings in appropriate scenarios
419  if (m_coreFacadeConfig.getMode() != CCoreFacadeConfig::Remote)
420  {
421  // facade running here locally
423  if (msg.isFailure()) { return msg; }
424 
425  // Settings are distributed via DBus. So only one application is responsible for saving. `enableLocalSave()`
426  // means "this is the application responsible for saving". If swiftgui requests a setting to be saved, it is
427  // sent to swiftcore and saved by swiftcore.
429  }
430  return CStatusMessage();
431  }
432 
433  bool CApplication::hasUnsavedSettings() const { return !this->getUnsavedSettingsKeys().isEmpty(); }
434 
435  void CApplication::saveSettingsOnShutdown(bool saveSettings) { m_saveSettingsOnShutdown = saveSettings; }
436 
438  {
441  }
442 
444  {
445  if (keys.isEmpty()) { return CStatusMessage(); }
446  return this->supportsContexts() ? this->getIContextApplication()->saveSettingsByKey(keys) :
448  }
449 
451  {
452  static const QTemporaryDir tempDir;
453  if (tempDir.isValid()) { return tempDir.path(); }
454  return QDir::tempPath();
455  }
456 
457  QString CApplication::getInfoString(const QString &separator) const
458  {
459  QString str = CBuildConfig::getVersionString() % u" " %
460  (CBuildConfig::isReleaseBuild() ? u"Release build" : u"Debug build") % separator %
461  u"Local dev.dbg.: " % boolToYesNo(CBuildConfig::isLocalDeveloperDebugBuild()) % separator %
462  u"dev.env.: " % boolToYesNo(this->isDeveloperFlagSet()) % separator % u"distribution: " %
463  this->getOwnDistribution().toQString(true) % separator % u"Windows NT: " %
464  boolToYesNo(CBuildConfig::isRunningOnWindowsNtPlatform()) % separator % u"Linux: " %
465  boolToYesNo(CBuildConfig::isRunningOnLinuxPlatform()) % " Unix: " %
466  boolToYesNo(CBuildConfig::isRunningOnUnixPlatform()) % separator % u"MacOS: " %
467  boolToYesNo(CBuildConfig::isRunningOnMacOSPlatform()) % separator % "Build Abi: " %
468  QSysInfo::buildAbi() % separator % u"Build CPU: " % QSysInfo::buildCpuArchitecture() % separator %
469  CBuildConfig::compiledWithInfo();
470 
471  if (this->supportsContexts()) { str += (separator % u"Supporting contexts"); }
472 
473  return str;
474  }
475 
476  QNetworkReply *CApplication::getFromNetwork(const CUrl &url, const CApplication::CallbackSlot &callback,
477  int maxRedirects)
478  {
479  const CApplication::ProgressSlot progress;
480  return this->getFromNetwork(url, callback, progress, maxRedirects);
481  }
482 
483  QNetworkReply *CApplication::getFromNetwork(const CUrl &url, const CApplication::CallbackSlot &callback,
484  const CApplication::ProgressSlot &progress, int maxRedirects)
485  {
486  return this->getFromNetwork(url.toNetworkRequest(), NoLogRequestId, callback, progress, maxRedirects);
487  }
488 
489  QNetworkReply *CApplication::getFromNetwork(const CUrl &url, int logId, const CApplication::CallbackSlot &callback,
490  const CApplication::ProgressSlot &progress, int maxRedirects)
491  {
492  return this->getFromNetwork(url.toNetworkRequest(), logId, callback, progress, maxRedirects);
493  }
494 
495  QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request,
496  const CApplication::CallbackSlot &callback, int maxRedirects)
497  {
498  const CApplication::ProgressSlot progress;
499  return this->getFromNetwork(request, callback, progress, maxRedirects);
500  }
501 
502  QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request,
503  const CApplication::CallbackSlot &callback,
504  const CApplication::ProgressSlot &progress, int maxRedirects)
505  {
506  return this->getFromNetwork(request, NoLogRequestId, callback, progress, maxRedirects);
507  }
508 
509  QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request, int logId,
510  const CApplication::CallbackSlot &callback,
511  const CApplication::ProgressSlot &progress, int maxRedirects)
512  {
513  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects,
514  [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
515  QNetworkReply *nr = qam.get(request);
516  return nr;
517  });
518  }
519 
520  QNetworkReply *CApplication::deleteResourceFromNetwork(const QNetworkRequest &request, int logId,
521  const CApplication::CallbackSlot &callback, int maxRedirects)
522  {
523  const CApplication::ProgressSlot progress;
524  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects,
525  [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
526  QNetworkReply *nr = qam.deleteResource(request);
527  return nr;
528  });
529  }
530 
531  QNetworkReply *CApplication::postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data,
532  const CSlot<void(QNetworkReply *)> &callback)
533  {
534  return this->httpRequestImpl(request, logId, callback, NoRedirects,
535  [data](QNetworkAccessManager &qam, const QNetworkRequest &request) {
536  QNetworkReply *nr = qam.post(request, data);
537  return nr;
538  });
539  }
540 
541  QNetworkReply *CApplication::postToNetwork(const QNetworkRequest &request, int logId, QHttpMultiPart *multiPart,
542  const CSlot<void(QNetworkReply *)> &callback)
543  {
544  if (multiPart->thread() != m_accessManager->thread()) { multiPart->moveToThread(m_accessManager->thread()); }
545 
546  QPointer<CApplication> myself(this);
547  return httpRequestImpl(request, logId, callback, NoRedirects,
548  [=](QNetworkAccessManager &qam, const QNetworkRequest &request) {
549  QNetworkReply *nr = nullptr;
550  if (!myself) { return nr; }
551  if (!multiPart) { return nr; }
552  nr = qam.post(request, multiPart);
553  multiPart->setParent(nr);
554  return nr;
555  });
556  }
557 
558  QNetworkReply *CApplication::headerFromNetwork(const CUrl &url, const CallbackSlot &callback, int maxRedirects)
559  {
560  return headerFromNetwork(url.toNetworkRequest(), callback, maxRedirects);
561  }
562 
563  QNetworkReply *CApplication::headerFromNetwork(const QNetworkRequest &request, const CallbackSlot &callback,
564  int maxRedirects)
565  {
566  return httpRequestImpl(
567  request, NoLogRequestId, callback, maxRedirects,
568  [](QNetworkAccessManager &qam, const QNetworkRequest &request) { return qam.head(request); });
569  }
570 
571  QNetworkReply *CApplication::downloadFromNetwork(const CUrl &url, const QString &saveAsFileName,
572  const CSlot<void(const CStatusMessage &)> &callback,
573  int maxRedirects)
574  {
575  // upfront checks
576  if (url.isEmpty()) { return nullptr; }
577  if (saveAsFileName.isEmpty()) { return nullptr; }
578  const QFileInfo fi(saveAsFileName);
579  if (!fi.dir().exists()) { return nullptr; }
580 
581  // function called with reply when done
582  CallbackSlot callbackSlot(this, [=](QNetworkReply *reply) {
583  QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(reply);
584  CStatusMessage msg;
585  if (reply->error() != QNetworkReply::NoError)
586  {
587  msg = CStatusMessage(this, CStatusMessage::SeverityError, u"Download for '%1' failed: '%2'")
588  << url.getFullUrl() << nwReply->errorString();
589  }
590  else
591  {
592  const bool ok = CFileUtils::writeByteArrayToFile(reply->readAll(), saveAsFileName);
593  msg = ok ? CStatusMessage(this, CStatusMessage::SeverityInfo, u"Saved file '%1' downloaded from '%2'")
594  << saveAsFileName << url.getFullUrl() :
596  u"Saving file '%1' downloaded from '%2' failed")
597  << saveAsFileName << url.getFullUrl();
598  }
599  nwReply->close();
600  QTimer::singleShot(0, callback.object(), [=] {
601  if (!sApp || sApp->isShuttingDown()) { return; }
602  callback(msg);
603  });
604  });
605 
606  ProgressSlot progressSlot(this, [=](int, qint64, qint64, const QUrl &) {
607  // so far not implemented
608  });
609 
610  QNetworkReply *reply = this->getFromNetwork(url, callbackSlot, progressSlot, maxRedirects);
611  return reply;
612  }
613 
614  void CApplication::deleteAllCookies() { m_cookieManager->deleteAllCookies(); }
615 
616  void CApplication::exit(int retcode)
617  {
618  if (sApp) { instance()->gracefulShutdown(); }
619 
620  // when the event loop is not running, this does nothing
621  QCoreApplication::exit(retcode);
622  }
623 
624  QStringList CApplication::arguments() { return QCoreApplication::arguments(); }
625 
626  int CApplication::indexOfCommandLineOption(const QCommandLineOption &option, const QStringList &args)
627  {
628  const QStringList names = option.names();
629  if (names.isEmpty() || args.isEmpty()) { return -1; }
630  int i = -1;
631  for (const QString &arg : args)
632  {
633  i++;
634  QString a;
635  if (arg.startsWith("--")) { a = arg.mid(2); }
636  else if (arg.startsWith("-")) { a = arg.mid(1); }
637  else { continue; }
638 
639  if (names.contains(a, Qt::CaseInsensitive)) { return i; }
640  }
641  return -1;
642  }
643 
644  void CApplication::argumentsWithoutOption(const QCommandLineOption &option, QStringList &args)
645  {
646  const int index = indexOfCommandLineOption(option, args);
647  if (index < 0) { return; }
648 
649  // remove argument and its value
650  args.removeAt(index);
651  if (!option.valueName().isEmpty() && args.size() > index) { args.removeAt(index); }
652  }
653 
654  void CApplication::processEventsFor(int milliseconds)
655  {
656  // sApp check allows to use it in test cases without sApp
657  if (sApp && sApp->isShuttingDown()) { return; }
658  QEventLoop eventLoop;
659  QTimer::singleShot(milliseconds, &eventLoop, &QEventLoop::quit);
660  connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &eventLoop, &QEventLoop::quit);
661  eventLoop.exec();
662  }
663 
664  CStatusMessageList CApplication::initContextsAndStartCoreFacade(const CCoreFacadeConfig &coreConfig)
665  {
666  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
667 
668  m_useContexts = true;
669  m_coreFacadeConfig = coreConfig;
670  const CStatusMessage msg = this->initLocalSettings();
671  if (msg.isFailure()) { return msg; }
672 
673  // now we can use settings
674  // if not yet initialized, init web data services
675  if (!m_webDataServices)
676  {
677  const CStatusMessageList msgs = this->initAndStartWebDataServices(
678  CWebReaderFlags::AllReaders, CDatabaseReaderConfigList::forPilotClient());
679  if (msgs.hasErrorMessages()) { return msgs; }
680  }
681  return this->startCoreFacade(); // will do nothing if setup is not yet loaded
682  }
683 
684  CStatusMessageList CApplication::startCoreFacadeWithoutContexts()
685  {
686  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
687 
688  m_useContexts = true; // otherwise startCoreFacade will early-return
689  m_coreFacadeConfig = CCoreFacadeConfig(CCoreFacadeConfig::NotUsed);
690  const CStatusMessage msg = this->initLocalSettings();
691  if (msg.isFailure()) { return msg; }
692 
693  return this->startCoreFacade(); // will do nothing if setup is not yet loaded
694  }
695 
696  CStatusMessageList CApplication::initAndStartWebDataServices(CWebReaderFlags::WebReader webReader,
697  const db::CDatabaseReaderConfigList &dbReaderConfig)
698  {
699  Q_ASSERT_X(m_webDataServices.isNull(), Q_FUNC_INFO, "Services already started");
700  SWIFT_VERIFY_X(QSslSocket::supportsSsl(), Q_FUNC_INFO, "No SSL");
701  if (!QSslSocket::supportsSsl()) { return CStatusMessage(this).error(u"No SSL supported, can`t be used"); }
702 
703  return this->startWebDataServices(webReader, dbReaderConfig);
704  }
705 
706  bool CApplication::isLocalContext() const
707  {
708  return this->getIContextApplication() && this->getIContextApplication()->isUsingImplementingObject();
709  }
710 
711  bool CApplication::isDBusContext() const
712  {
713  return this->getIContextApplication() && !this->getIContextApplication()->isUsingImplementingObject() &&
714  !this->getIContextApplication()->isEmptyObject();
715  }
716 
717  CStatusMessageList CApplication::startCoreFacade()
718  {
719  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
720 
721  if (!m_useContexts)
722  {
723  return CStatusMessage(this).error(u"No need to start core facade");
724  } // we do not use context, so no need to startup
725  if (!m_setupReader || !m_setupReader->isSetupAvailable())
726  {
727  return CStatusMessage(this).error(u"No setup reader or setup available");
728  }
729 
730  Q_ASSERT_X(m_coreFacade.isNull(), Q_FUNC_INFO, "Cannot alter facade");
731  Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No facade without setup possible");
732  Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Need running web data services");
733 
734  const CStatusMessageList msgs(CStatusMessage(this).info(u"Will start core facade now"));
735  m_coreFacade.reset(new CCoreFacade(m_coreFacadeConfig));
736  emit this->coreFacadeStarted();
737  return msgs;
738  }
739 
740  CStatusMessageList CApplication::startWebDataServices(CWebReaderFlags::WebReader webReader,
741  const db::CDatabaseReaderConfigList &dbReaderConfig)
742  {
743  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
744 
745  if (!m_setupReader || !m_setupReader->isSetupAvailable())
746  {
747  return CStatusMessage(this).error(u"No setup reader or setup available");
748  }
749 
750  Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No web data services without setup possible");
751  CStatusMessageList msgs;
752  if (!m_webDataServices)
753  {
754  msgs.push_back(CStatusMessage(this).info(u"Will start web data services now"));
755  m_webDataServices.reset(new CWebDataServices(webReader, dbReaderConfig, this));
756  Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Missing web services");
757 
758  emit this->webDataServicesStarted(true);
759  }
760  else { msgs.push_back(CStatusMessage(this).info(u"Web data services already running")); }
761 
762  return msgs;
763  }
764 
765  void CApplication::initLogging()
766  {
767  CLogHandler::instance()->install(); // make sure we have a log handler!
768 
769  // File logger
770  m_fileLogger.reset(new CFileLogger(this));
771  connect(CLogHandler::instance(), &CLogHandler::localMessageLogged, m_fileLogger.data(),
772  &CFileLogger::writeStatusMessageToFile);
773  connect(CLogHandler::instance(), &CLogHandler::remoteMessageLogged, m_fileLogger.data(),
774  &CFileLogger::writeStatusMessageToFile);
775  m_fileLogger->changeLogPattern(CLogPattern().withSeverityAtOrAbove(CStatusMessage::SeverityDebug));
776  }
777 
778  void CApplication::initParser()
779  {
780  m_parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
781  m_parser.setApplicationDescription(m_applicationName);
782  m_cmdHelp = m_parser.addHelpOption();
783  m_cmdVersion = m_parser.addVersionOption();
784  m_allOptions.append(m_cmdHelp);
785  m_allOptions.append(m_cmdVersion);
786 
787  // dev. system
788  m_cmdDevelopment = QCommandLineOption({ "dev", "development" },
789  QCoreApplication::translate("application", "Dev. system features?"));
790  this->addParserOption(m_cmdDevelopment);
791 
792  // Skip single application check
793  m_cmdSkipSingleApp = QCommandLineOption(
794  { "skipsa", "skipsingleapp" }, QCoreApplication::translate("application", "Skip the single app.test."));
795  this->addParserOption(m_cmdSkipSingleApp);
796 
797  // reset caches upfront
798  m_cmdClearCache = QCommandLineOption({ "ccache", "clearcache" },
799  QCoreApplication::translate("application", "Clear (reset) the caches."));
800  this->addParserOption(m_cmdClearCache);
801 
802  // test crashpad upload
803  m_cmdTestCrashpad = QCommandLineOption(
804  { "testcp", "testcrashpad" }, QCoreApplication::translate("application", "Trigger crashpad situation."));
805  this->addParserOption(m_cmdTestCrashpad);
806  }
807 
808  bool CApplication::isSet(const QCommandLineOption &option) const { return (m_parser.isSet(option)); }
809 
810  void CApplication::registerMetadata()
811  {
814  }
815 
816  QStringList CApplication::clearCaches()
817  {
818  const QStringList files(CDataCache::instance()->enumerateStore());
819  CDataCache::instance()->clearAllValues();
820  return files;
821  }
822 
823  void CApplication::gracefulShutdown()
824  {
825  if (m_shutdown) { return; }
826  if (m_shutdownInProgress) { return; }
827  m_shutdownInProgress = true;
828 
829  CLogMessage(this).info(u"Graceful shutdown of CApplication started");
830 
831  // info that we will shutdown
832  emit this->aboutToShutdown();
833 
834  // Release all input devices to not cause any accidental hotkey triggers anymore.
835  // This is also necessary to properly free platform specific instances at a defined point in time.
836  if (m_inputManager)
837  {
838  CLogMessage(this).info(u"Graceful shutdown of CApplication, released devices");
839  m_inputManager->releaseDevices();
840  }
841 
842  // save settings (but only when application was really alive)
843  if (m_parsed && m_saveSettingsOnShutdown)
844  {
845  const CStatusMessage m = this->supportsContexts() ? this->getIContextApplication()->saveSettings() :
846  CSettingsCache::instance()->saveToStore();
847  CLogMessage(this).preformatted(m);
848  }
849 
850  // from here on we really rip apart the application object
851  // and it should no longer be used
852 
853  if (this->supportsContexts(true))
854  {
855  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of contexts");
856 
857  // clean up facade
858  m_coreFacade->gracefulShutdown();
859  m_coreFacade.reset();
860  }
861 
862  if (m_webDataServices)
863  {
864  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of web services");
865 
866  m_webDataServices->gracefulShutdown();
867  m_webDataServices.reset();
868  }
869 
870  if (m_gitHubPackagesReader) { m_gitHubPackagesReader.reset(); }
871 
872  if (m_setupReader) { m_setupReader.reset(); }
873 
874  CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of logger");
875  m_fileLogger->close();
876 
877  // clean up all in "deferred delete state"
878  qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
879  sApp->processEventsFor(500);
880 
881  // completed
882  m_shutdown = true;
883  sApp = nullptr;
884 
885  disconnect(this);
886  }
887 
888  void CApplication::onStartUpCompleted()
889  {
890  // void
891  }
892 
893  void CApplication::initNetwork()
894  {
895  if (!m_accessManager) { m_accessManager = new QNetworkAccessManager(this); }
896 
897  m_cookieManager = new CCookieManager(this);
898  m_cookieManager->setParent(m_accessManager);
899  m_accessManager->setCookieJar(m_cookieManager);
900 
901  // Init network
902  Q_ASSERT_X(m_accessManager, Q_FUNC_INFO, "Need QAM");
903  }
904 
906 
907  const QString &CApplication::executable()
908  {
909  static const QString e(QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName());
910  return e;
911  }
912 
913  const QStringList &CApplication::getLogCategories()
914  {
915  static const QStringList l({ "swift.application", "swift." % executable() });
916  return l;
917  }
918 
919  // ---------------------------------------------------------------------------------
920  // Parsing
921  // ---------------------------------------------------------------------------------
922 
923  bool CApplication::addParserOption(const QCommandLineOption &option)
924  {
925  m_allOptions.append(option);
926  return m_parser.addOption(option);
927  }
928 
929  bool CApplication::addParserOptions(const QList<QCommandLineOption> &options)
930  {
931  m_allOptions.append(options);
932  return m_parser.addOptions(options);
933  }
934 
935  void CApplication::addDBusAddressOption()
936  {
937  m_cmdDBusAddress = QCommandLineOption(
938  { "dbus", "dbusaddress" },
939  QCoreApplication::translate("application", "DBus address (session, system, P2P IP e.g. 192.168.23.5)"),
940  "dbusaddress");
941  this->addParserOption(m_cmdDBusAddress);
942  }
943 
944  void CApplication::addNetworkOptions() { this->addParserOptions(IContextNetwork::getCmdLineOptions()); }
945 
946  void CApplication::addAudioOptions() { this->addParserOptions(CContextAudioBase::getCmdLineOptions()); }
947 
948  QString CApplication::getCmdDBusAddressValue() const
949  {
950  if (!this->isParserOptionSet(m_cmdDBusAddress)) { return {}; }
951  const QString v(this->getParserValue(m_cmdDBusAddress));
952  const QString dBusAddress(CDBusServer::normalizeAddress(v));
953  return dBusAddress;
954  }
955 
956  bool CApplication::isParserOptionSet(const QString &option) const { return m_parser.isSet(option); }
957 
958  bool CApplication::skipSingleApplicationCheck() const { return this->isParserOptionSet(m_cmdSkipSingleApp); }
959 
960  bool CApplication::isParserOptionSet(const QCommandLineOption &option) const { return m_parser.isSet(option); }
961 
962  QString CApplication::getParserValue(const QString &option) const { return m_parser.value(option).trimmed(); }
963 
964  QString CApplication::getParserValue(const QCommandLineOption &option) const
965  {
966  return m_parser.value(option).trimmed();
967  }
968 
969  bool CApplication::parseCommandLineArgsAndLoadSetup()
970  {
971  if (!this->startupCheck()) return false;
972  if (!this->parseCommandLineArguments()) return false;
973  if (!this->loadSetupAndHandleErrors()) return false;
974  return true;
975  }
976 
977  bool CApplication::parseCommandLineArguments()
978  {
979  if (m_parsed) { return m_parsed; } // already done
980 
981  // we call parse because we also want to display a GUI error message when applicable
982  const QStringList args(QCoreApplication::arguments());
983  if (!m_parser.parse(args))
984  {
985  this->cmdLineErrorMessage("Parser error:", m_parser.errorText());
986  return false;
987  }
988 
989  if (m_alreadyRunning && !this->skipSingleApplicationCheck())
990  {
991  this->cmdLineErrorMessage("Program must only run once",
992  "You cannot run two or more instances side-by-side.");
993  return false;
994  }
995 
996  // help/version
997  if (m_parser.isSet(m_cmdHelp))
998  {
999  // Important: parser help will already stop application
1000  this->cmdLineHelpMessage();
1001  return false;
1002  }
1003  if (m_parser.isSet(m_cmdVersion))
1004  {
1005  // Important: version will already stop application
1006  this->cmdLineVersionMessage();
1007  return false;
1008  }
1009 
1010  // dev.
1011  m_devFlag = this->initIsRunningInDeveloperEnvironment();
1012 
1013  // Hookin, other parsing
1014  if (!this->parsingHookIn()) { return false; }
1015 
1016  // setup reader
1017  m_parsed = true;
1018  return true;
1019  }
1020 
1021  bool CApplication::startupCheck() const
1022  {
1023  const QStringList verifyErrors = CSwiftDirectories::verifyRuntimeDirectoriesAndFiles();
1024  if (!verifyErrors.isEmpty() && !m_applicationInfo.isUnitTest())
1025  {
1026  cmdLineErrorMessage("Missing runtime directories/files:", verifyErrors.join(", "));
1027  return false;
1028  }
1029  return true;
1030  }
1031 
1032  bool CApplication::loadSetupAndHandleErrors()
1033  {
1034  const CStatusMessageList msgs = loadSetup();
1035 
1036  if (msgs.isFailure()) { displaySetupLoadFailure(msgs); }
1037  return msgs.isSuccess();
1038  }
1039 
1040  void CApplication::displaySetupLoadFailure(swift::misc::CStatusMessageList)
1041  {
1042  // Ignore for CLI application
1043  // Already logged to console
1044  }
1045 
1046  void CApplication::cmdLineErrorMessage(const QString &text, const QString &informativeText) const
1047  {
1048  fputs(qPrintable(text + informativeText), stderr);
1049  }
1050 
1051  void CApplication::cmdLineErrorMessage(const CStatusMessageList &msgs) const
1052  {
1053  if (msgs.isEmpty()) { return; }
1054  if (!msgs.hasErrorMessages()) { return; }
1055  CApplication::cmdLineErrorMessage(msgs.toQString(true), "");
1056  }
1057 
1058  QPointer<ISimulator> CApplication::getISimulator() const
1059  {
1060  if (!this->hasSimulator()) { return nullptr; }
1061  return this->getCoreFacade()->getCContextSimulator()->simulator();
1062  }
1063 
1064  bool CApplication::hasSimulator() const
1065  {
1066  if (!this->getCoreFacade()) { return false; }
1067  if (!this->getCoreFacade()->getIContextSimulator()->isUsingImplementingObject()) { return false; }
1068  return (this->getCoreFacade()->getCContextSimulator()); // should always be true
1069  }
1070 
1071  void CApplication::cmdLineHelpMessage()
1072  {
1073  m_parser.showHelp(); // terminates
1074  Q_UNREACHABLE();
1075  }
1076 
1077  void CApplication::cmdLineVersionMessage()
1078  {
1079  m_parser.showVersion(); // terminates
1080  Q_UNREACHABLE();
1081  }
1082 
1083  QStringList CApplication::argumentsJoined(const QStringList &newArguments, const QStringList &removeArguments) const
1084  {
1085  QStringList joinedArguments = CApplication::arguments();
1086  QStringList newArgumentsChecked = newArguments;
1087 
1088  // remove the executable argument if it exists at position 0
1089  if (!joinedArguments.isEmpty() && !joinedArguments.at(0).startsWith("-"))
1090  {
1091  joinedArguments.removeFirst();
1092  } // was cmd line argument
1093  if (!newArgumentsChecked.isEmpty() && !newArgumentsChecked.at(0).startsWith("-"))
1094  {
1095  newArgumentsChecked.removeFirst();
1096  } // was cmd line argument
1097 
1098  // remove all values before checking options
1099  static const QRegularExpression regExp("^-");
1100  QStringList toBeRemoved(newArgumentsChecked.filter(regExp));
1101  toBeRemoved.append(removeArguments.filter(regExp));
1102  toBeRemoved.append("--installer");
1103  toBeRemoved.removeDuplicates();
1104 
1105  if (!joinedArguments.isEmpty() && !toBeRemoved.isEmpty())
1106  {
1107  // remove all options from removeArguments
1108  // consider alias names, that is why we check on option
1109  for (const QCommandLineOption &option : m_allOptions)
1110  {
1111  const int n = indexOfCommandLineOption(option, toBeRemoved);
1112  if (n >= 0) { argumentsWithoutOption(option, joinedArguments); }
1113  }
1114  }
1115 
1116  joinedArguments.append(newArgumentsChecked);
1117  return joinedArguments;
1118  }
1119 
1120  // ---------------------------------------------------------------------------------
1121  // Contexts
1122  // ---------------------------------------------------------------------------------
1123 
1124  shared_state::CDataLinkDBus *CApplication::getDataLinkDBus() { return getCoreFacade()->getDataLinkDBus(); }
1125 
1126  bool CApplication::supportsContexts(bool ignoreShutdownTest) const
1127  {
1128  if (!ignoreShutdownTest && m_shutdown) { return false; }
1129  if (m_coreFacade.isNull()) { return false; }
1130  if (!m_coreFacade->getIContextApplication()) { return false; }
1131  return (!m_coreFacade->getIContextApplication()->isEmptyObject());
1132  }
1133 
1134  const IContextNetwork *CApplication::getIContextNetwork() const
1135  {
1136  if (!supportsContexts()) { return nullptr; }
1137  return m_coreFacade->getIContextNetwork();
1138  }
1139 
1140  const IContextAudio *CApplication::getIContextAudio() const
1141  {
1142  if (!supportsContexts()) { return nullptr; }
1143  return m_coreFacade->getIContextAudio();
1144  }
1145 
1146  const CContextAudioBase *CApplication::getCContextAudioBase() const
1147  {
1148  if (!supportsContexts()) { return nullptr; }
1149  return m_coreFacade->getCContextAudioBase();
1150  }
1151 
1152  const IContextApplication *CApplication::getIContextApplication() const
1153  {
1154  if (!supportsContexts()) { return nullptr; }
1155  return m_coreFacade->getIContextApplication();
1156  }
1157 
1158  const IContextOwnAircraft *CApplication::getIContextOwnAircraft() const
1159  {
1160  if (!supportsContexts()) { return nullptr; }
1161  return m_coreFacade->getIContextOwnAircraft();
1162  }
1163 
1164  const IContextSimulator *CApplication::getIContextSimulator() const
1165  {
1166  if (!supportsContexts()) { return nullptr; }
1167  return m_coreFacade->getIContextSimulator();
1168  }
1169 
1170  IContextNetwork *CApplication::getIContextNetwork()
1171  {
1172  if (!supportsContexts()) { return nullptr; }
1173  return m_coreFacade->getIContextNetwork();
1174  }
1175 
1176  IContextAudio *CApplication::getIContextAudio()
1177  {
1178  if (!supportsContexts()) { return nullptr; }
1179  return m_coreFacade->getIContextAudio();
1180  }
1181 
1182  CContextAudioBase *CApplication::getCContextAudioBase()
1183  {
1184  if (!supportsContexts()) { return nullptr; }
1185  return m_coreFacade->getCContextAudioBase();
1186  }
1187 
1188  IContextApplication *CApplication::getIContextApplication()
1189  {
1190  if (!supportsContexts()) { return nullptr; }
1191  return m_coreFacade->getIContextApplication();
1192  }
1193 
1194  IContextOwnAircraft *CApplication::getIContextOwnAircraft()
1195  {
1196  if (!supportsContexts()) { return nullptr; }
1197  return m_coreFacade->getIContextOwnAircraft();
1198  }
1199 
1200  IContextSimulator *CApplication::getIContextSimulator()
1201  {
1202  if (!supportsContexts()) { return nullptr; }
1203  return m_coreFacade->getIContextSimulator();
1204  }
1205 
1206  void CApplication::onCoreFacadeStarted()
1207  {
1208  // void
1209  }
1210 
1211  // ---------------------------------------------------------------------------------
1212  // Setup
1213  // ---------------------------------------------------------------------------------
1214 
1215  bool CApplication::hasSetupReader() const { return !m_setupReader.isNull(); }
1216 
1217  CSetupReader *CApplication::getSetupReader() const { return m_setupReader.data(); }
1218 
1219  CStatusMessageList CApplication::loadSetup()
1220  {
1221  if (m_shutdown) { return CStatusMessage(this).warning(u"Shutting down, not reading"); }
1222  if (!m_setupReader) { return CStatusMessage(this).error(u"No reader for setup/version"); }
1223  Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Not yet parsed");
1224  const CStatusMessageList requestMsgs = m_setupReader->loadSetup();
1225  return requestMsgs;
1226  }
1227 
1228  CUrl CApplication::getVatsimMetarUrl() const
1229  {
1230  if (m_shutdown) { return {}; }
1231  if (m_webDataServices)
1232  {
1233  const CUrl url(m_webDataServices->getVatsimMetarUrl());
1234  if (!url.isEmpty()) { return url; }
1235  }
1236  if (m_setupReader) { return m_setupReader->getSetup().getVatsimMetarsUrl(); }
1237  return {};
1238  }
1239 
1240  CUrl CApplication::getVatsimDataFileUrl() const
1241  {
1242  if (m_shutdown) { return {}; }
1243  if (m_webDataServices)
1244  {
1245  const CUrl url(m_webDataServices->getVatsimDataFileUrl());
1246  if (!url.isEmpty()) { return url; }
1247  }
1248  if (m_setupReader) { return m_setupReader->getSetup().getVatsimDataFileUrl(); }
1249  return {};
1250  }
1251 
1252  CUrl CApplication::getVatsimServerFileUrl() const
1253  {
1254  if (m_shutdown || !m_setupReader) { return {}; }
1255 
1256  return m_setupReader->getSetup().getVatsimServerFileUrl();
1257  }
1258 
1259  CUrl CApplication::getVatsimFsdHttpUrl() const
1260  {
1261  if (m_shutdown || !m_setupReader) { return {}; }
1262 
1263  return m_setupReader->getSetup().getVatsimFsdHttpUrl();
1264  }
1265 
1266  void CApplication::onCrashDumpUploadEnabledChanged()
1267  {
1268  const bool enabled = CBuildConfig::isReleaseBuild() && m_crashDumpUploadEnabled.getThreadLocal();
1269  this->enableCrashDumpUpload(enabled);
1270  }
1271 
1272  void CApplication::simulateCrash() { CCrashHandler::instance()->simulateCrash(); }
1273 
1274  void CApplication::simulateAssert() { CCrashHandler::instance()->simulateAssert(); }
1275 
1276  void CApplication::enableCrashDumpUpload(bool enable) { CCrashHandler::instance()->setUploadsEnabled(enable); }
1277 
1278  bool CApplication::isSupportingCrashpad() const
1279  {
1280 #ifdef SWIFT_USE_CRASHPAD
1281  return true;
1282 #else
1283  return false;
1284 #endif
1285  }
1286 
1287  void CApplication::httpRequestImplInQAMThread(const QNetworkRequest &request, int logId,
1288  const CallbackSlot &callback, const ProgressSlot &progress,
1289  int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
1290  {
1291  // run in QAM thread
1292  if (this->isShuttingDown()) { return; }
1293  QTimer::singleShot(0, m_accessManager, [=] {
1294  // should be now in QAM thread
1295  if (!sApp || sApp->isShuttingDown()) { return; }
1296  Q_ASSERT_X(CThreadUtils::isInThisThread(sApp->m_accessManager), Q_FUNC_INFO,
1297  "Wrong thread, must be QAM thread");
1298  this->httpRequestImpl(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
1299  });
1300  }
1301 
1302  QNetworkReply *CApplication::httpRequestImpl(const QNetworkRequest &request, int logId,
1303  const CApplication::CallbackSlot &callback, int maxRedirects,
1304  NetworkRequestOrPostFunction requestOrPostMethod)
1305  {
1306  ProgressSlot progress;
1307  return this->httpRequestImpl(request, logId, callback, progress, maxRedirects, requestOrPostMethod);
1308  }
1309 
1310  QNetworkReply *CApplication::httpRequestImpl(const QNetworkRequest &request, int logId,
1311  const CallbackSlot &callback, const ProgressSlot &progress,
1312  int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
1313  {
1314  if (this->isShuttingDown()) { return nullptr; }
1315 
1316  QWriteLocker locker(&m_accessManagerLock);
1317  Q_ASSERT_X(m_accessManager->thread() == qApp->thread(), Q_FUNC_INFO,
1318  "Network manager supposed to be in main thread");
1319  if (!CThreadUtils::isInThisThread(m_accessManager))
1320  {
1321  this->httpRequestImplInQAMThread(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
1322  return nullptr; // not yet started, will be called again in QAM thread
1323  }
1324 
1325  Q_ASSERT_X(CThreadUtils::isInThisThread(m_accessManager), Q_FUNC_INFO, "Network manager thread mismatch");
1326  QNetworkRequest copiedRequest =
1327  CNetworkUtils::getSwiftNetworkRequest(request, this->getApplicationNameAndVersion());
1328 
1329  QNetworkReply *reply = getPostOrDeleteRequest(*m_accessManager, copiedRequest);
1330  reply->setProperty("started", QVariant(QDateTime::currentMSecsSinceEpoch()));
1331  reply->setProperty(CUrlLog::propertyNameId(), QVariant(logId));
1332  const QUrl url(reply->url());
1333  QString urlStr = url.toString();
1334 
1335  if (progress)
1336  {
1337  connect(reply, &QNetworkReply::downloadProgress, progress.object(),
1338  [=](qint64 current, qint64 max) { progress(logId, current, max, url); });
1339  }
1340 
1341  if (callback)
1342  {
1343  Q_ASSERT_X(callback.object(), Q_FUNC_INFO, "Need callback object (to determine thread)");
1344  connect(
1345  reply, &QNetworkReply::finished, callback.object(),
1346  [=] {
1347  // Called when finished!
1348  // QNetworkRequest::FollowRedirectsAttribute would allow auto redirect, but we use our approach as
1349  // it gives us better control \fixme: Check again on Qt 5.9: Added redirects policy to
1350  // QNetworkAccessManager (ManualRedirectsPolicy, NoLessSafeRedirectsPolicy,
1351  // SameOriginRedirectsPolicy, UserVerifiedRedirectsPolicy)
1352  const bool isRedirect = CNetworkUtils::isHttpStatusRedirect(reply);
1353  if (isRedirect && maxRedirects > 0)
1354  {
1355  const QUrl redirectUrl = CNetworkUtils::getHttpRedirectUrl(reply);
1356  if (!redirectUrl.isEmpty())
1357  {
1358  QNetworkRequest redirectRequest(redirectUrl);
1359  const int redirectsLeft = maxRedirects - 1;
1360  CLogMessage(sApp).info(u"Redirecting '%1' to '%2'") << urlStr << redirectUrl.toString();
1361  this->httpRequestImplInQAMThread(redirectRequest, logId, callback, progress, redirectsLeft,
1362  getPostOrDeleteRequest);
1363  return;
1364  }
1365  }
1366  // called when there are no more callbacks
1367  callback(reply);
1368  },
1369  Qt::QueuedConnection); // called in callback thread
1370  }
1371  return reply;
1372  }
1373 
1374  void CApplication::tagApplicationDataDirectory()
1375  {
1377  const QDir dir(d);
1378  if (!dir.exists() || !dir.isReadable()) { return; }
1379  const QString aiStr(this->getApplicationInfo().toJsonString());
1380  const QString filePath(CFileUtils::appendFilePaths(
1381  dir.path(), CApplicationInfo::fileName())); // will be overridden by next swift app
1382  CFileUtils::writeStringToFile(aiStr, filePath);
1383  }
1384 } // 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?
static void processEventsFor(int milliseconds)
Process all events for some time.
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:60
static const QSet< CWorkerBase * > & allWorkers()
All workers currently existing.
Definition: worker.h:136
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:76
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:23
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26