Files
pilotclient/src/blackcore/application.cpp
Lars Toenning d31084db3a refactor: Directly use shared URL from global setup
Previous we relyed on the watchdog providing a working shared URL (or
empty URL if no URL was reachable). This creates some overhead and
might not work in all cases because of the time between the call to
getWorkingSharedUrl() and the actual network request. Further, not all
calls that are fetching data from our servers were using the shared URL
from the watchdog anyway.
Lastly, this careful checking if the URL is available is only done for
the shared URL and not for all the other URLs that are queried by swift.
2024-04-15 22:01:45 +02:00

1695 lines
64 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "blackcore/application.h"
#include "blackconfig/buildconfig.h"
#include "blackcore/db/networkwatchdog.h"
#include "blackcore/context/contextnetwork.h"
#include "blackcore/context/contextsimulatorimpl.h"
#include "blackcore/context/contextaudio.h"
#include "blackcore/context/contextapplication.h"
#include "blackcore/cookiemanager.h"
#include "blackcore/corefacade.h"
#include "blackcore/registermetadata.h"
#include "blackcore/setupreader.h"
#include "blackcore/webdataservices.h"
#include "blackcore/inputmanager.h"
#include "blackmisc/applicationinfo.h"
#include "blackmisc/crashhandler.h"
#include "blackmisc/datacache.h"
#include "blackmisc/dbusserver.h"
#include "blackmisc/swiftdirectories.h"
#include "blackmisc/eventloop.h"
#include "blackmisc/filelogger.h"
#include "blackmisc/loghandler.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/logpattern.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/registermetadata.h"
#include "blackmisc/settingscache.h"
#include "blackmisc/stringutils.h"
#include "blackmisc/threadutils.h"
#include "blackmisc/verify.h"
#include <cstdio>
#include <QCoreApplication>
#include <QDateTime>
#include <QEventLoop>
#include <QFileInfo>
#include <QHttpMultiPart>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkConfigurationManager>
#include <QProcess>
#include <QRegularExpression>
#include <QSslSocket>
#include <QStandardPaths>
#include <QStringBuilder>
#include <QStringList>
#include <QTemporaryDir>
#include <QThread>
#include <QTimer>
#include <QTranslator>
#include <QWriteLocker>
#include <Qt>
#include <QtGlobal>
#include <QSysInfo>
#include <cstdlib>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Db;
using namespace BlackMisc::Network;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::Weather;
using namespace BlackCore;
using namespace BlackCore::Context;
using namespace BlackCore::Vatsim;
using namespace BlackCore::Data;
using namespace BlackCore::Db;
BlackCore::CApplication *sApp = nullptr; // set by constructor
//! \private
static const QString &swiftDataRoot()
{
static const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/org.swift-project/";
return path;
}
namespace BlackCore
{
CApplication::CApplication(CApplicationInfo::Application application, bool init) : CApplication(executable(), application, init)
{}
CApplication::CApplication(const QString &applicationName, CApplicationInfo::Application application, bool init) : CIdentifiable(this),
m_networkConfigManager(new QNetworkConfigurationManager(this)),
m_accessManager(new QNetworkAccessManager(this)),
m_applicationInfo(application),
m_applicationName(applicationName), m_coreFacadeConfig(CCoreFacadeConfig::allEmpty())
{
Q_ASSERT_X(!sApp, Q_FUNC_INFO, "already initialized");
Q_ASSERT_X(QCoreApplication::instance(), Q_FUNC_INFO, "no application object");
m_applicationInfo.setApplicationDataDirectory(CSwiftDirectories::normalizedApplicationDataDirectory());
QCoreApplication::setApplicationName(m_applicationName);
QCoreApplication::setApplicationVersion(CBuildConfig::getVersionString());
this->setObjectName(m_applicationName);
this->thread()->setObjectName(m_applicationName); // normally no effect as thread already runs, but does not harm either
// init skipped when called from CGuiApplication
if (init)
{
this->init(true);
}
}
void CApplication::init(bool withMetadata)
{
if (!sApp)
{
// notify when app goes down
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &CApplication::gracefulShutdown);
// metadata
if (withMetadata) { CApplication::registerMetadata(); }
// unit test
if (this->getApplicationInfo().getApplication() == CApplicationInfo::UnitTest)
{
const QString tempPath(CApplication::getTemporaryDirectory());
BlackMisc::setMockCacheRootDirectory(tempPath);
}
m_alreadyRunning = CApplication::getRunningApplications().containsApplication(CApplication::getApplicationInfo().getApplication());
this->initParser();
this->initLogging();
this->tagApplicationDataDirectory();
//
// cmd line arguments not yet parsed here
//
// Translations
QTranslator translator;
if (translator.load("blackmisc_i18n_de", ":blackmisc/translations/")) { CLogMessage(this).debug() << "Translator loaded"; }
QCoreApplication::installTranslator(&translator);
// main app
sApp = this;
this->initNetwork();
// global setup
m_setupReader.reset(new CSetupReader(this));
// check for updates
m_gitHubPackagesReader.reset(new CGitHubPackagesReader(this));
connect(m_gitHubPackagesReader.data(), &CGitHubPackagesReader::updateInfoAvailable, this, &CApplication::updateInfoAvailable, Qt::QueuedConnection);
reloadUpdateInfo();
// startup done
connect(this, &CApplication::startUpCompleted, this, &CApplication::onStartUpCompleted, Qt::QueuedConnection);
connect(this, &CApplication::coreFacadeStarted, this, &CApplication::onCoreFacadeStarted, Qt::QueuedConnection);
if (!this->getApplicationInfo().isUnitTest())
{
m_inputManager = new CInputManager(this);
m_inputManager->createDevices();
}
connect(this, &QObject::destroyed, [cat = CLogCategoryList(this)] {
for (CWorkerBase *worker : CWorkerBase::allWorkers())
{
CLogMessage(cat).debug(u"Worker named '%1' still exists after application destroyed") << worker->objectName();
}
});
}
}
bool CApplication::registerAsRunning()
{
//! \fixme KB 2017-11 maybe this code can be encapsulated somewhere
CApplicationInfoList apps = CApplication::getRunningApplications();
const CApplicationInfo myself = CApplication::instance()->getApplicationInfo();
if (!apps.contains(myself)) { apps.push_back(myself); }
const bool ok = CFileUtils::writeStringToLockedFile(apps.toJsonString(), CFileUtils::appendFilePaths(swiftDataRoot(), "apps.json"));
if (!ok) { CLogMessage(static_cast<CApplication *>(nullptr)).error(u"Failed to write to application list file"); }
return ok;
}
bool CApplication::unregisterAsRunning()
{
//! \fixme KB 2017-11 maybe this code can be encapsulated somewhere
CApplicationInfoList apps = CApplication::getRunningApplications();
const CApplicationInfo myself = CApplication::instance()->getApplicationInfo();
if (!apps.contains(myself)) { return true; }
apps.remove(myself);
const bool ok = CFileUtils::writeStringToLockedFile(apps.toJsonString(), CFileUtils::appendFilePaths(swiftDataRoot(), "apps.json"));
if (!ok) { CLogMessage(static_cast<CApplication *>(nullptr)).error(u"Failed to write to application list file"); }
return ok;
}
int CApplication::exec()
{
Q_ASSERT_X(instance(), Q_FUNC_INFO, "missing application");
emit this->startUpCompleted(true);
CApplication::registerAsRunning();
return QCoreApplication::exec();
}
void CApplication::restartApplication(const QStringList &newArguments, const QStringList &removeArguments)
{
CApplication::unregisterAsRunning();
const QString prg = QCoreApplication::applicationFilePath();
const QStringList args = this->argumentsJoined(newArguments, removeArguments);
this->gracefulShutdown();
QProcess::startDetached(prg, args);
CApplication::exit(0);
}
CApplication::~CApplication()
{
this->gracefulShutdown();
}
CApplicationInfoList CApplication::getRunningApplications()
{
CApplicationInfoList apps;
apps.convertFromJsonNoThrow(CFileUtils::readLockedFileToString(swiftDataRoot() + "apps.json"), {}, {});
apps.removeIf([](const CApplicationInfo &info) { return !info.getProcessInfo().exists(); });
return apps;
}
bool CApplication::isApplicationRunning(CApplicationInfo::Application application)
{
const CApplicationInfoList running = CApplication::getRunningApplications();
return running.containsApplication(application);
}
bool CApplication::isAlreadyRunning() const
{
return getRunningApplications().containsBy([this](const CApplicationInfo &info) { return info.getApplication() == getApplicationInfo().getApplication(); });
}
bool CApplication::isShuttingDown() const
{
return m_shutdown || m_shutdownInProgress;
}
bool CApplication::isIncognito() const
{
return m_incognito;
}
void CApplication::setIncognito(bool incognito)
{
m_incognito = incognito;
}
void CApplication::toggleIncognito()
{
m_incognito = !m_incognito;
}
const QString &CApplication::getApplicationNameAndVersion() const
{
static const QString s(m_applicationName % u" " % CBuildConfig::getVersionString());
return s;
}
const QString &CApplication::getApplicationNameVersionDetailed() const
{
static const QString s(m_applicationName % u" " % this->versionStringDetailed());
return s;
}
QString CApplication::getExecutableForApplication(CApplicationInfo::Application application) const
{
QString searchFor;
switch (application)
{
case CApplicationInfo::PilotClientCore: searchFor = "core"; break;
case CApplicationInfo::Laucher: searchFor = "launcher"; break;
case CApplicationInfo::MappingTool: searchFor = "data"; break;
case CApplicationInfo::PilotClientGui: searchFor = "gui"; break;
default: break;
}
if (searchFor.isEmpty()) { return {}; }
for (const QString &executable : CFileUtils::getSwiftExecutables())
{
if (!executable.contains("swift", Qt::CaseInsensitive)) { continue; }
if (executable.contains(searchFor, Qt::CaseInsensitive)) { return executable; }
}
return {};
}
bool CApplication::startLauncher()
{
static const QString launcher = CApplication::getExecutableForApplication(CApplicationInfo::Application::Laucher);
if (launcher.isEmpty() || CApplication::isApplicationRunning(CApplicationInfo::Laucher)) { return false; }
// const QStringList args = this->argumentsJoined({}, { "--dbus", "--core", "--coreaudio" });
const QStringList args = this->argumentsJoined({}, { "--dbus", "--core" });
return QProcess::startDetached(launcher, args);
}
bool CApplication::startLauncherAndQuit()
{
const bool started = CApplication::startLauncher();
if (!started) { return false; }
this->gracefulShutdown();
CApplication::exit();
return true;
}
CGlobalSetup CApplication::getGlobalSetup() const
{
if (m_shutdown) { return CGlobalSetup(); }
const CSetupReader *r = m_setupReader.data();
if (!r) { return CGlobalSetup(); }
return r->getSetup();
}
CUpdateInfo CApplication::getUpdateInfo() const
{
if (m_shutdown) { return CUpdateInfo(); }
if (!m_gitHubPackagesReader) { return CUpdateInfo(); }
return m_gitHubPackagesReader->getUpdateInfo();
}
void CApplication::reloadUpdateInfo()
{
if (m_shutdown) { return; }
if (!m_gitHubPackagesReader) { return; }
m_gitHubPackagesReader->readUpdateInfo();
}
CDistribution CApplication::getOwnDistribution() const
{
if (CBuildConfig::isLocalDeveloperDebugBuild()) { return CDistribution::localDeveloperBuild(); }
const CUpdateInfo u = this->getUpdateInfo();
return u.anticipateOwnDistribution();
}
bool CApplication::start()
{
m_started = false; // reset
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
// parsing itself is done
CStatusMessageList msgs;
do
{
// clear cache?
if (this->isSet(m_cmdClearCache))
{
const QStringList files(CApplication::clearCaches());
msgs.push_back(CLogMessage(this).debug() << "Cleared cache, " << files.size() << " files");
}
// crashpad dump
if (this->isSet(m_cmdTestCrashpad))
{
msgs.push_back(CLogMessage(this).info(u"About to simulate crash"));
QTimer::singleShot(10 * 1000, [=] {
if (!sApp || sApp->isShuttingDown()) { return; }
this->simulateCrash();
});
}
Q_ASSERT_X(m_setupReader && m_setupReader->isSetupAvailable(), Q_FUNC_INFO, "Setup not available");
// start hookin
msgs.push_back(this->startHookIn());
if (msgs.isFailure()) { break; }
// Settings if not already initialized
msgs.push_back(this->initLocalSettings());
if (msgs.isFailure()) { break; }
}
while (false);
// terminate with failures, otherwise log messages
if (msgs.isFailure())
{
this->cmdLineErrorMessage(msgs);
return false;
}
else if (!msgs.isEmpty())
{
CLogMessage::preformatted(msgs);
}
m_started = true;
return m_started;
}
bool CApplication::isSetupAvailable() const
{
if (m_shutdown || !m_setupReader) { return false; }
return m_setupReader->isSetupAvailable();
}
bool CApplication::hasMinimumMappingVersion() const
{
return (this->getGlobalSetup().isSwiftVersionMinimumMappingVersion());
}
bool CApplication::hasWebDataServices() const
{
if (this->isShuttingDown()) { return false; } // service will not survive for long
return m_webDataServices;
}
CWebDataServices *CApplication::getWebDataServices() const
{
// use hasWebDataServices() to test if services are available
// getting the assert means web services are accessed before the are initialized
Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Missing web data services, use hasWebDataServices to test if existing");
return m_webDataServices.data();
}
const QString &CApplication::versionStringDetailed() const
{
if (this->isDeveloperFlagSet() && CBuildConfig::isLocalDeveloperDebugBuild())
{
static const QString s(CBuildConfig::getVersionStringPlatform() % u" [dev,DEVDBG]");
return s;
}
if (isDeveloperFlagSet())
{
static const QString s(CBuildConfig::getVersionStringPlatform() % u" [dev]");
return s;
}
if (CBuildConfig::isLocalDeveloperDebugBuild())
{
static const QString s(CBuildConfig::getVersionStringPlatform() % u" [DEVDBG]");
return s;
}
return CBuildConfig::getVersionStringPlatform();
}
const QString &CApplication::swiftVersionString() const
{
static const QString s(QStringLiteral("swift %1").arg(versionStringDetailed()));
return s;
}
const char *CApplication::swiftVersionChar()
{
static const QByteArray a(swiftVersionString().toUtf8());
return a.constData();
}
bool CApplication::initIsRunningInDeveloperEnvironment() const
{
//
// assumption: restricted distributions are development versions
//
if (this->getApplicationInfo().isSampleOrUnitTest()) { return true; }
if (CBuildConfig::isLocalDeveloperDebugBuild()) { return true; }
const CDistribution d(this->getOwnDistribution());
if (d.isRestricted() && this->isSet(m_cmdDevelopment)) { return true; }
return false;
}
CStatusMessage CApplication::initLocalSettings()
{
if (m_localSettingsLoaded) { return CStatusMessage(); }
m_localSettingsLoaded = true;
// trigger loading and saving of settings in appropriate scenarios
if (m_coreFacadeConfig.getModeApplication() != CCoreFacadeConfig::Remote)
{
// facade running here locally
const CStatusMessage msg = CSettingsCache::instance()->loadFromStore();
if (msg.isFailure()) { return msg; }
// Settings are distributed via DBus. So only one application is responsible for saving. `enableLocalSave()` means
// "this is the application responsible for saving". If swiftgui requests a setting to be saved, it is sent to swiftcore and saved by swiftcore.
CSettingsCache::instance()->enableLocalSave();
}
return CStatusMessage();
}
bool CApplication::hasUnsavedSettings() const
{
return !this->getUnsavedSettingsKeys().isEmpty();
}
void CApplication::saveSettingsOnShutdown(bool saveSettings)
{
m_saveSettingsOnShutdown = saveSettings;
}
QStringList CApplication::getUnsavedSettingsKeys() const
{
return this->supportsContexts() ?
this->getIContextApplication()->getUnsavedSettingsKeys() :
CSettingsCache::instance()->getAllUnsavedKeys();
}
CStatusMessage CApplication::saveSettingsByKey(const QStringList &keys)
{
if (keys.isEmpty()) { return CStatusMessage(); }
return this->supportsContexts() ?
this->getIContextApplication()->saveSettingsByKey(keys) :
CSettingsCache::instance()->saveToStore(keys);
}
QString CApplication::getTemporaryDirectory()
{
static const QTemporaryDir tempDir;
if (tempDir.isValid()) { return tempDir.path(); }
return QDir::tempPath();
}
QString CApplication::getInfoString(const QString &separator) const
{
QString str =
CBuildConfig::getVersionString() %
u" " % (CBuildConfig::isReleaseBuild() ? u"Release build" : u"Debug build") %
separator %
u"Local dev.dbg.: " %
boolToYesNo(CBuildConfig::isLocalDeveloperDebugBuild()) %
separator %
u"dev.env.: " %
boolToYesNo(this->isDeveloperFlagSet()) %
separator %
u"distribution: " %
this->getOwnDistribution().toQString(true) %
separator %
u"Windows NT: " %
boolToYesNo(CBuildConfig::isRunningOnWindowsNtPlatform()) %
u" Windows 10: " %
boolToYesNo(CBuildConfig::isRunningOnWindows10()) %
separator %
u"Linux: " %
boolToYesNo(CBuildConfig::isRunningOnLinuxPlatform()) %
" Unix: " %
boolToYesNo(CBuildConfig::isRunningOnUnixPlatform()) %
separator %
u"MacOS: " %
boolToYesNo(CBuildConfig::isRunningOnMacOSPlatform()) %
separator %
"Build Abi: " %
QSysInfo::buildAbi() %
separator %
u"Build CPU: " %
QSysInfo::buildCpuArchitecture() %
separator %
CBuildConfig::compiledWithInfo(false);
if (this->supportsContexts())
{
str += (separator % u"Supporting contexts");
if (this->getIContextNetwork())
{
str += (separator % this->getIContextNetwork()->getLibraryInfo(true));
}
}
return str;
}
QNetworkReply *CApplication::getFromNetwork(const CUrl &url, const CApplication::CallbackSlot &callback, int maxRedirects)
{
const CApplication::ProgressSlot progress;
return this->getFromNetwork(url, callback, progress, maxRedirects);
}
QNetworkReply *CApplication::getFromNetwork(const CUrl &url, const CApplication::CallbackSlot &callback, const CApplication::ProgressSlot &progress, int maxRedirects)
{
return this->getFromNetwork(url.toNetworkRequest(), NoLogRequestId, callback, progress, maxRedirects);
}
QNetworkReply *CApplication::getFromNetwork(const CUrl &url, int logId, const CApplication::CallbackSlot &callback, const CApplication::ProgressSlot &progress, int maxRedirects)
{
return this->getFromNetwork(url.toNetworkRequest(), logId, callback, progress, maxRedirects);
}
QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request, const CApplication::CallbackSlot &callback, int maxRedirects)
{
const CApplication::ProgressSlot progress;
return this->getFromNetwork(request, callback, progress, maxRedirects);
}
QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request, const CApplication::CallbackSlot &callback, const CApplication::ProgressSlot &progress, int maxRedirects)
{
return this->getFromNetwork(request, NoLogRequestId, callback, progress, maxRedirects);
}
QNetworkReply *CApplication::getFromNetwork(const QNetworkRequest &request, int logId, const CApplication::CallbackSlot &callback, const CApplication::ProgressSlot &progress, int maxRedirects)
{
return this->httpRequestImpl(request, logId, callback, progress, maxRedirects, [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
QNetworkReply *nr = qam.get(request);
return nr;
});
}
QNetworkReply *CApplication::deleteResourceFromNetwork(const QNetworkRequest &request, int logId, const CApplication::CallbackSlot &callback, int maxRedirects)
{
const CApplication::ProgressSlot progress;
return this->httpRequestImpl(request, logId, callback, progress, maxRedirects, [](QNetworkAccessManager &qam, const QNetworkRequest &request) {
QNetworkReply *nr = qam.deleteResource(request);
return nr;
});
}
QNetworkReply *CApplication::postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data, const CSlot<void(QNetworkReply *)> &callback)
{
return this->httpRequestImpl(request, logId, callback, NoRedirects, [data](QNetworkAccessManager &qam, const QNetworkRequest &request) {
QNetworkReply *nr = qam.post(request, data);
return nr;
});
}
QNetworkReply *CApplication::postToNetwork(const QNetworkRequest &request, int logId, QHttpMultiPart *multiPart, const CSlot<void(QNetworkReply *)> &callback)
{
if (!this->isNetworkAccessible()) { return nullptr; }
if (multiPart->thread() != m_accessManager->thread())
{
multiPart->moveToThread(m_accessManager->thread());
}
QPointer<CApplication> myself(this);
return httpRequestImpl(request, logId, callback, NoRedirects, [=](QNetworkAccessManager &qam, const QNetworkRequest &request) {
QNetworkReply *nr = nullptr;
if (!myself) { return nr; }
if (!multiPart) { return nr; }
nr = qam.post(request, multiPart);
multiPart->setParent(nr);
return nr;
});
}
QNetworkReply *CApplication::headerFromNetwork(const CUrl &url, const CallbackSlot &callback, int maxRedirects)
{
return headerFromNetwork(url.toNetworkRequest(), callback, maxRedirects);
}
QNetworkReply *CApplication::headerFromNetwork(const QNetworkRequest &request, const CallbackSlot &callback, int maxRedirects)
{
return httpRequestImpl(request, NoLogRequestId, callback, maxRedirects, [](QNetworkAccessManager &qam, const QNetworkRequest &request) { return qam.head(request); });
}
QNetworkReply *CApplication::downloadFromNetwork(const CUrl &url, const QString &saveAsFileName, const CSlot<void(const CStatusMessage &)> &callback, int maxRedirects)
{
// upfront checks
if (url.isEmpty()) { return nullptr; }
if (saveAsFileName.isEmpty()) { return nullptr; }
const QFileInfo fi(saveAsFileName);
if (!fi.dir().exists()) { return nullptr; }
// function called with reply when done
CallbackSlot callbackSlot(this, [=](QNetworkReply *reply) {
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> nwReply(reply);
CStatusMessage msg;
if (reply->error() != QNetworkReply::NoError)
{
msg = CStatusMessage(this, CStatusMessage::SeverityError, u"Download for '%1' failed: '%2'") << url.getFullUrl() << nwReply->errorString();
}
else
{
const bool ok = CFileUtils::writeByteArrayToFile(reply->readAll(), saveAsFileName);
msg = ok ?
CStatusMessage(this, CStatusMessage::SeverityInfo, u"Saved file '%1' downloaded from '%2'") << saveAsFileName << url.getFullUrl() :
CStatusMessage(this, CStatusMessage::SeverityError, u"Saving file '%1' downloaded from '%2' failed") << saveAsFileName << url.getFullUrl();
}
nwReply->close();
QTimer::singleShot(0, callback.object(), [=] {
if (!sApp || sApp->isShuttingDown()) { return; }
callback(msg);
});
});
ProgressSlot progressSlot(this, [=](int, qint64, qint64, const QUrl &) {
// so far not implemented
});
QNetworkReply *reply = this->getFromNetwork(url, callbackSlot, progressSlot, maxRedirects);
return reply;
}
void CApplication::deleteAllCookies()
{
m_cookieManager->deleteAllCookies();
}
CNetworkWatchdog *CApplication::getNetworkWatchdog() const
{
return m_networkWatchDog;
}
void CApplication::setSwiftDbAccessibility(bool accessible)
{
if (!m_networkWatchDog) { return; }
m_networkWatchDog->setDbAccessibility(accessible);
}
int CApplication::triggerNetworkWatchdogChecks()
{
if (!m_networkWatchDog) { return -1; }
return m_networkWatchDog->triggerCheck();
}
bool CApplication::isNetworkAccessible() const
{
// skip test if there is no proper network config
if (m_networkWatchDog && m_networkWatchDog->isNetworkAccessibilityCheckDisabled()) { return true; }
Q_ASSERT_X(m_accessManager, Q_FUNC_INFO, "no access manager");
const QNetworkAccessManager::NetworkAccessibility a = m_accessManager->networkAccessible();
if (a == QNetworkAccessManager::Accessible) { return true; }
// currently I also accept unknown because of that issue with Network Manager
return a == QNetworkAccessManager::UnknownAccessibility;
}
bool CApplication::isInternetAccessible() const
{
return m_networkWatchDog && m_networkWatchDog->isInternetAccessible();
}
bool CApplication::isSwiftDbAccessible() const
{
return m_networkWatchDog && m_networkWatchDog->isSwiftDbAccessible();
}
void CApplication::exit(int retcode)
{
if (sApp) { instance()->gracefulShutdown(); }
// when the event loop is not running, this does nothing
QCoreApplication::exit(retcode);
}
QStringList CApplication::arguments()
{
return QCoreApplication::arguments();
}
int CApplication::indexOfCommandLineOption(const QCommandLineOption &option, const QStringList &args)
{
const QStringList names = option.names();
if (names.isEmpty() || args.isEmpty()) { return -1; }
int i = -1;
for (const QString &arg : args)
{
i++;
QString a;
if (arg.startsWith("--")) { a = arg.mid(2); }
else if (arg.startsWith("-")) { a = arg.mid(1); }
else { continue; }
if (names.contains(a, Qt::CaseInsensitive)) { return i; }
}
return -1;
}
void CApplication::argumentsWithoutOption(const QCommandLineOption &option, QStringList &args)
{
const int index = indexOfCommandLineOption(option, args);
if (index < 0) { return; }
// remove argument and its value
args.removeAt(index);
if (!option.valueName().isEmpty() && args.size() > index) { args.removeAt(index); }
}
void CApplication::processEventsFor(int milliseconds)
{
// sApp check allows to use it in test cases without sApp
if (sApp && sApp->isShuttingDown()) { return; }
QEventLoop eventLoop;
QTimer::singleShot(milliseconds, &eventLoop, &QEventLoop::quit);
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
}
CStatusMessageList CApplication::initContextsAndStartCoreFacade(const CCoreFacadeConfig &coreConfig)
{
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
m_useContexts = true;
m_coreFacadeConfig = coreConfig;
const CStatusMessage msg = this->initLocalSettings();
if (msg.isFailure()) { return msg; }
// now we can use settings
// if not yet initialized, init web data services
if (!m_webDataServices)
{
const CStatusMessageList msgs = this->initAndStartWebDataServices(CWebReaderFlags::AllReaders, CDatabaseReaderConfigList::forPilotClient());
if (msgs.hasErrorMessages()) { return msgs; }
}
return this->startCoreFacade(); // will do nothing if setup is not yet loaded
}
CStatusMessageList CApplication::startCoreFacadeWithoutContexts()
{
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
m_useContexts = true; // otherwise startCoreFacade will early-return
m_coreFacadeConfig = CCoreFacadeConfig::allEmpty();
const CStatusMessage msg = this->initLocalSettings();
if (msg.isFailure()) { return msg; }
return this->startCoreFacade(); // will do nothing if setup is not yet loaded
}
CStatusMessageList CApplication::initAndStartWebDataServices(CWebReaderFlags::WebReader webReader, const Db::CDatabaseReaderConfigList &dbReaderConfig)
{
Q_ASSERT_X(m_webDataServices.isNull(), Q_FUNC_INFO, "Services already started");
BLACK_VERIFY_X(QSslSocket::supportsSsl(), Q_FUNC_INFO, "No SSL");
if (!QSslSocket::supportsSsl())
{
return CStatusMessage(this).error(u"No SSL supported, can`t be used");
}
m_webReadersUsed = webReader;
m_dbReaderConfig = dbReaderConfig;
return this->startWebDataServices();
}
bool CApplication::isLocalContext() const
{
return this->getIContextApplication() && this->getIContextApplication()->isUsingImplementingObject();
}
bool CApplication::isDBusContext() const
{
return this->getIContextApplication() && !this->getIContextApplication()->isUsingImplementingObject() && !this->getIContextApplication()->isEmptyObject();
}
CStatusMessageList CApplication::startCoreFacade()
{
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
if (!m_useContexts) { return CStatusMessage(this).error(u"No need to start core facade"); } // we do not use context, so no need to startup
if (!m_setupReader || !m_setupReader->isSetupAvailable()) { return CStatusMessage(this).error(u"No setup reader or setup available"); }
Q_ASSERT_X(m_coreFacade.isNull(), Q_FUNC_INFO, "Cannot alter facade");
Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No facade without setup possible");
Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Need running web data services");
const CStatusMessageList msgs(CStatusMessage(this).info(u"Will start core facade now"));
m_coreFacade.reset(new CCoreFacade(m_coreFacadeConfig));
emit this->coreFacadeStarted();
return msgs;
}
CStatusMessageList CApplication::startWebDataServices()
{
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Call this function after parsing");
if (!m_setupReader || !m_setupReader->isSetupAvailable()) { return CStatusMessage(this).error(u"No setup reader or setup available"); }
Q_ASSERT_X(m_setupReader, Q_FUNC_INFO, "No web data services without setup possible");
CStatusMessageList msgs;
if (!m_webDataServices)
{
msgs.push_back(CStatusMessage(this).info(u"Will start web data services now"));
m_webDataServices.reset(
new CWebDataServices(m_webReadersUsed, m_dbReaderConfig, {}, this));
Q_ASSERT_X(m_webDataServices, Q_FUNC_INFO, "Missing web services");
// watchdog
if (m_networkWatchDog)
{
connect(m_webDataServices.data(), &CWebDataServices::swiftDbDataRead, m_networkWatchDog, &CNetworkWatchdog::setDbAccessibility);
}
emit this->webDataServicesStarted(true);
}
else
{
msgs.push_back(CStatusMessage(this).info(u"Web data services already running"));
}
return msgs;
}
void CApplication::initLogging()
{
CLogHandler::instance()->install(); // make sure we have a log handler!
// File logger
m_fileLogger.reset(new CFileLogger(this));
connect(CLogHandler::instance(), &CLogHandler::localMessageLogged, m_fileLogger.data(), &CFileLogger::writeStatusMessageToFile);
connect(CLogHandler::instance(), &CLogHandler::remoteMessageLogged, m_fileLogger.data(), &CFileLogger::writeStatusMessageToFile);
m_fileLogger->changeLogPattern(CLogPattern().withSeverityAtOrAbove(CStatusMessage::SeverityDebug));
}
void CApplication::initParser()
{
m_parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
m_parser.setApplicationDescription(m_applicationName);
m_cmdHelp = m_parser.addHelpOption();
m_cmdVersion = m_parser.addVersionOption();
m_allOptions.append(m_cmdHelp);
m_allOptions.append(m_cmdVersion);
// dev. system
m_cmdDevelopment = QCommandLineOption({ "dev", "development" },
QCoreApplication::translate("application", "Dev. system features?"));
this->addParserOption(m_cmdDevelopment);
// Skip single application check
m_cmdSkipSingleApp = QCommandLineOption({ "skipsa", "skipsingleapp" },
QCoreApplication::translate("application", "Skip the single app.test."));
this->addParserOption(m_cmdSkipSingleApp);
// reset caches upfront
m_cmdClearCache = QCommandLineOption({ "ccache", "clearcache" },
QCoreApplication::translate("application", "Clear (reset) the caches."));
this->addParserOption(m_cmdClearCache);
// test crashpad upload
m_cmdTestCrashpad = QCommandLineOption({ "testcp", "testcrashpad" },
QCoreApplication::translate("application", "Trigger crashpad situation."));
this->addParserOption(m_cmdTestCrashpad);
}
bool CApplication::isSet(const QCommandLineOption &option) const
{
return (m_parser.isSet(option));
}
void CApplication::registerMetadata()
{
BlackMisc::registerMetadata();
BlackCore::registerMetadata();
}
QStringList CApplication::clearCaches()
{
const QStringList files(CDataCache::instance()->enumerateStore());
CDataCache::instance()->clearAllValues();
return files;
}
void CApplication::gracefulShutdown()
{
if (m_shutdown) { return; }
if (m_shutdownInProgress) { return; }
m_shutdownInProgress = true;
CLogMessage(this).info(u"Graceful shutdown of CApplication started");
// info that we will shutdown
emit this->aboutToShutdown();
// Release all input devices to not cause any accidental hotkey triggers anymore.
// This is also necessary to properly free platform specific instances at a defined point in time.
if (m_inputManager)
{
CLogMessage(this).info(u"Graceful shutdown of CApplication, released devices");
m_inputManager->releaseDevices();
}
// mark as shutdown
if (m_networkWatchDog) { m_networkWatchDog->gracefulShutdown(); }
// save settings (but only when application was really alive)
if (m_parsed && m_saveSettingsOnShutdown)
{
const CStatusMessage m = this->supportsContexts() ?
this->getIContextApplication()->saveSettings() :
CSettingsCache::instance()->saveToStore();
CLogMessage(this).preformatted(m);
}
// from here on we really rip apart the application object
// and it should no longer be used
if (this->supportsContexts(true))
{
CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of contexts");
// clean up facade
m_coreFacade->gracefulShutdown();
m_coreFacade.reset();
}
if (m_webDataServices)
{
CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of web services");
m_webDataServices->gracefulShutdown();
m_webDataServices.reset();
}
if (m_gitHubPackagesReader)
{
m_gitHubPackagesReader.reset();
}
if (m_setupReader)
{
m_setupReader.reset();
}
if (m_networkWatchDog)
{
m_networkWatchDog->quitAndWait();
m_networkWatchDog = nullptr;
}
CLogMessage(this).info(u"Graceful shutdown of CApplication, shutdown of logger");
m_fileLogger->close();
// clean up all in "deferred delete state"
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
sApp->processEventsFor(500);
// completed
m_shutdown = true;
sApp = nullptr;
disconnect(this);
}
void CApplication::onStartUpCompleted()
{
// void
}
void CApplication::onChangedNetworkAccessibility(QNetworkAccessManager::NetworkAccessibility accessible)
{
switch (accessible)
{
case QNetworkAccessManager::Accessible:
m_accessManager->setNetworkAccessible(accessible); // for some reasons the queried value still is unknown
CLogMessage(this).info(u"Network is accessible");
break;
case QNetworkAccessManager::NotAccessible:
CLogMessage(this).error(u"Network not accessible");
break;
default:
CLogMessage(this).warning(u"Network accessibility unknown");
break;
}
}
void CApplication::onChangedInternetAccessibility(bool accessible)
{
if (accessible) { CLogMessage(this).info(u"Internet reported accessible"); }
else { CLogMessage(this).warning(u"Internet not accessible"); }
emit this->changedInternetAccessibility(accessible);
}
void CApplication::onChangedSwiftDbAccessibility(bool accessible, const CUrl &url)
{
if (accessible)
{
CLogMessage(this).info(u"swift DB reported accessible: '%1'") << url.toQString();
}
else
{
CLogMessage(this).warning(u"swift DB not accessible: '%1'") << url.toQString();
if (m_networkWatchDog)
{
CLogMessage(this).warning(m_networkWatchDog->getCheckInfo());
}
this->triggerNetworkAccessibilityCheck(10 * 1000); // crosscheck after some time
}
emit this->changedSwiftDbAccessibility(accessible, url);
}
void CApplication::onNetworkConfigurationsUpdateCompleted()
{
Q_ASSERT_X(m_networkConfigManager, Q_FUNC_INFO, "Need network config manager");
if (this->isShuttingDown()) { return; }
const QList<QNetworkConfiguration> allConfigurations = m_networkConfigManager->allConfigurations();
if (allConfigurations.isEmpty())
{
// this is an odd situation we cannot handle, network check will be disabled
if (m_networkWatchDog && m_networkWatchDog->isNetworkAccessibilityCheckEnabled())
{
m_networkWatchDog->disableNetworkAccessibilityCheck(true);
m_accessManager->setNetworkAccessible(QNetworkAccessManager::Accessible);
CLogMessage(this).warning(u"No network configurations found, disabling network accessibility checks");
}
}
else
{
int activeCount = 0;
int validCount = 0;
for (const QNetworkConfiguration &config : allConfigurations)
{
if (config.state() == QNetworkConfiguration::Active)
{
activeCount++;
m_noNwAccessPoint = false;
}
if (config.isValid()) { validCount++; }
}
Q_UNUSED(validCount)
const bool canStartIAP = (m_networkConfigManager->capabilities() & QNetworkConfigurationManager::CanStartAndStopInterfaces);
const bool disable = activeCount < 1; // only inactive
if (disable && m_networkWatchDog && m_networkWatchDog->isNetworkAccessibilityCheckEnabled())
{
CLogMessage(this).warning(u"Disabling network accessibility check in watchdog");
m_networkWatchDog->disableNetworkAccessibilityCheck(disable);
}
// Is there default access point, use it
const QNetworkConfiguration config = m_networkConfigManager->defaultConfiguration();
if (!config.isValid() || (!canStartIAP && config.state() != QNetworkConfiguration::Active))
{
if (!m_noNwAccessPoint)
{
m_noNwAccessPoint = true;
CLogMessage(this).warning(u"No network access point found for swift");
}
}
}
}
void CApplication::initNetwork()
{
if (!m_accessManager) { m_accessManager = new QNetworkAccessManager(this); }
if (!m_networkConfigManager) { m_networkConfigManager = new QNetworkConfigurationManager(this); }
if (!m_networkWatchDog)
{
// CNetworkWatchdog *nwWatchdog = new CNetworkWatchdog(this->isNetworkAccessible(), this);
CNetworkWatchdog *nwWatchdog = new CNetworkWatchdog(true, this); // WLAN bug, default to true
m_networkWatchDog = nwWatchdog; // not yet started
m_cookieManager = new CCookieManager({}, this);
m_cookieManager->setParent(m_accessManager);
m_accessManager->setCookieJar(m_cookieManager);
}
// Init network
Q_ASSERT_X(m_accessManager, Q_FUNC_INFO, "Need QAM");
Q_ASSERT_X(m_networkConfigManager, Q_FUNC_INFO, "Need config manager");
// into watchdog
connect(m_accessManager, &QNetworkAccessManager::networkAccessibleChanged, m_networkWatchDog, &CNetworkWatchdog::setNetworkAccessibility, Qt::QueuedConnection);
connect(m_networkConfigManager, &QNetworkConfigurationManager::onlineStateChanged, m_networkWatchDog, &CNetworkWatchdog::setOnline, Qt::QueuedConnection);
connect(m_networkConfigManager, &QNetworkConfigurationManager::updateCompleted, m_networkWatchDog, &CNetworkWatchdog::networkConfigurationsUpdateCompleted, Qt::QueuedConnection);
connect(m_networkConfigManager, &QNetworkConfigurationManager::updateCompleted, this, &CApplication::onNetworkConfigurationsUpdateCompleted, Qt::QueuedConnection);
m_networkConfigManager->updateConfigurations();
// out from watchdog to application
connect(m_networkWatchDog, &CNetworkWatchdog::changedNetworkAccessible, this, &CApplication::onChangedNetworkAccessibility, Qt::QueuedConnection);
connect(m_networkWatchDog, &CNetworkWatchdog::changedInternetAccessibility, this, &CApplication::onChangedInternetAccessibility, Qt::QueuedConnection);
connect(m_networkWatchDog, &CNetworkWatchdog::changedSwiftDbAccessibility, this, &CApplication::onChangedSwiftDbAccessibility, Qt::QueuedConnection);
CLogMessage::preformatted(CNetworkUtils::createNetworkReport(m_accessManager));
m_networkWatchDog->start(QThread::LowestPriority);
m_networkWatchDog->startUpdating(10);
// enable by setting accessible
// http://doc.qt.io/qt-5/qnetworkaccessmanager.html#setNetworkAccessible
m_accessManager->setNetworkAccessible(QNetworkAccessManager::Accessible);
// create a network report in the log
QTimer::singleShot(4000, this, [=] {
if (!sApp || sApp->isShuttingDown()) { return; }
const QString r = CNetworkUtils::createNetworkConfigurationReport(m_networkConfigManager, m_accessManager);
CLogMessage(this).info(u"Network report:\n%1") << r;
});
}
CApplication *BlackCore::CApplication::instance()
{
return sApp;
}
const QString &CApplication::executable()
{
static const QString e(QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName());
return e;
}
const QStringList &CApplication::getLogCategories()
{
static const QStringList l({ "swift.application", "swift." % executable() });
return l;
}
// ---------------------------------------------------------------------------------
// Parsing
// ---------------------------------------------------------------------------------
bool CApplication::addParserOption(const QCommandLineOption &option)
{
m_allOptions.append(option);
return m_parser.addOption(option);
}
bool CApplication::addParserOptions(const QList<QCommandLineOption> &options)
{
m_allOptions.append(options);
return m_parser.addOptions(options);
}
void CApplication::addDBusAddressOption()
{
m_cmdDBusAddress = QCommandLineOption({ "dbus", "dbusaddress" },
QCoreApplication::translate("application", "DBus address (session, system, P2P IP e.g. 192.168.23.5)"),
"dbusaddress");
this->addParserOption(m_cmdDBusAddress);
}
void CApplication::addVatlibOptions()
{
this->addParserOptions(IContextNetwork::getCmdLineOptions());
}
void CApplication::addAudioOptions()
{
this->addParserOptions(CContextAudioBase::getCmdLineOptions());
}
QString CApplication::getCmdDBusAddressValue() const
{
if (!this->isParserOptionSet(m_cmdDBusAddress)) { return {}; }
const QString v(this->getParserValue(m_cmdDBusAddress));
const QString dBusAddress(CDBusServer::normalizeAddress(v));
return dBusAddress;
}
bool CApplication::isParserOptionSet(const QString &option) const
{
return m_parser.isSet(option);
}
bool CApplication::skipSingleApplicationCheck() const
{
return this->isParserOptionSet(m_cmdSkipSingleApp);
}
bool CApplication::isParserOptionSet(const QCommandLineOption &option) const
{
return m_parser.isSet(option);
}
QString CApplication::getParserValue(const QString &option) const
{
return m_parser.value(option).trimmed();
}
QString CApplication::getParserValue(const QCommandLineOption &option) const
{
return m_parser.value(option).trimmed();
}
bool CApplication::parseCommandLineArgsAndLoadSetup()
{
if (!this->startupCheck()) return false;
if (!this->parseCommandLineArguments()) return false;
if (!this->loadSetupAndHandleErrors()) return false;
return true;
}
bool CApplication::parseCommandLineArguments()
{
if (m_parsed) { return m_parsed; } // already done
// we call parse because we also want to display a GUI error message when applicable
const QStringList args(QCoreApplication::arguments());
if (!m_parser.parse(args))
{
this->cmdLineErrorMessage("Parser error:", m_parser.errorText());
return false;
}
if (m_alreadyRunning && !this->skipSingleApplicationCheck())
{
this->cmdLineErrorMessage("Program must only run once", "You cannot run two or more instances side-by-side.");
return false;
}
// help/version
if (m_parser.isSet(m_cmdHelp))
{
// Important: parser help will already stop application
this->cmdLineHelpMessage();
return false;
}
if (m_parser.isSet(m_cmdVersion))
{
// Important: version will already stop application
this->cmdLineVersionMessage();
return false;
}
// dev.
m_devFlag = this->initIsRunningInDeveloperEnvironment();
// Hookin, other parsing
if (!this->parsingHookIn()) { return false; }
// setup reader
m_parsed = true;
return true;
}
bool CApplication::startupCheck() const
{
const QStringList verifyErrors = CSwiftDirectories::verifyRuntimeDirectoriesAndFiles();
if (!verifyErrors.isEmpty() && !m_applicationInfo.isUnitTest())
{
cmdLineErrorMessage("Missing runtime directories/files:", verifyErrors.join(", "));
return false;
}
return true;
}
bool CApplication::loadSetupAndHandleErrors()
{
const CStatusMessageList msgs = loadSetup();
if (msgs.isFailure())
{
displaySetupLoadFailure(msgs);
}
return msgs.isSuccess();
}
void CApplication::displaySetupLoadFailure(BlackMisc::CStatusMessageList)
{
// Ignore for CLI application
// Already logged to console
}
void CApplication::cmdLineErrorMessage(const QString &text, const QString &informativeText) const
{
fputs(qPrintable(text + informativeText), stderr);
}
void CApplication::cmdLineErrorMessage(const CStatusMessageList &msgs) const
{
if (msgs.isEmpty()) { return; }
if (!msgs.hasErrorMessages()) { return; }
CApplication::cmdLineErrorMessage(msgs.toQString(true), "");
}
QPointer<ISimulator> CApplication::getISimulator() const
{
if (!this->hasSimulator()) { return nullptr; }
return this->getCoreFacade()->getCContextSimulator()->simulator();
}
bool CApplication::hasSimulator() const
{
if (!this->getCoreFacade()) { return false; }
if (!this->getCoreFacade()->getIContextSimulator()->isUsingImplementingObject()) { return false; }
return (this->getCoreFacade()->getCContextSimulator()); // should always be true
}
void CApplication::cmdLineHelpMessage()
{
m_parser.showHelp(); // terminates
Q_UNREACHABLE();
}
void CApplication::cmdLineVersionMessage()
{
m_parser.showVersion(); // terminates
Q_UNREACHABLE();
}
QStringList CApplication::argumentsJoined(const QStringList &newArguments, const QStringList &removeArguments) const
{
QStringList joinedArguments = CApplication::arguments();
QStringList newArgumentsChecked = newArguments;
// remove the executable argument if it exists at position 0
if (!joinedArguments.isEmpty() && !joinedArguments.at(0).startsWith("-")) { joinedArguments.removeFirst(); } // was cmd line argument
if (!newArgumentsChecked.isEmpty() && !newArgumentsChecked.at(0).startsWith("-")) { newArgumentsChecked.removeFirst(); } // was cmd line argument
// remove all values before checking options
static const QRegularExpression regExp("^-");
QStringList toBeRemoved(newArgumentsChecked.filter(regExp));
toBeRemoved.append(removeArguments.filter(regExp));
toBeRemoved.append("--installer");
toBeRemoved.removeDuplicates();
if (!joinedArguments.isEmpty() && !toBeRemoved.isEmpty())
{
// remove all options from removeArguments
// consider alias names, that is why we check on option
for (const QCommandLineOption &option : m_allOptions)
{
const int n = indexOfCommandLineOption(option, toBeRemoved);
if (n >= 0)
{
argumentsWithoutOption(option, joinedArguments);
}
}
}
joinedArguments.append(newArgumentsChecked);
return joinedArguments;
}
// ---------------------------------------------------------------------------------
// Contexts
// ---------------------------------------------------------------------------------
SharedState::CDataLinkDBus *CApplication::getDataLinkDBus()
{
return getCoreFacade()->getDataLinkDBus();
}
bool CApplication::supportsContexts(bool ignoreShutdownTest) const
{
if (!ignoreShutdownTest && m_shutdown) { return false; }
if (m_coreFacade.isNull()) { return false; }
if (!m_coreFacade->getIContextApplication()) { return false; }
return (!m_coreFacade->getIContextApplication()->isEmptyObject());
}
const IContextNetwork *CApplication::getIContextNetwork() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextNetwork();
}
const IContextAudio *CApplication::getIContextAudio() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextAudio();
}
const CContextAudioBase *CApplication::getCContextAudioBase() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getCContextAudioBase();
}
const IContextApplication *CApplication::getIContextApplication() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextApplication();
}
const IContextOwnAircraft *CApplication::getIContextOwnAircraft() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextOwnAircraft();
}
const IContextSimulator *CApplication::getIContextSimulator() const
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextSimulator();
}
IContextNetwork *CApplication::getIContextNetwork()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextNetwork();
}
IContextAudio *CApplication::getIContextAudio()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextAudio();
}
CContextAudioBase *CApplication::getCContextAudioBase()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getCContextAudioBase();
}
IContextApplication *CApplication::getIContextApplication()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextApplication();
}
IContextOwnAircraft *CApplication::getIContextOwnAircraft()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextOwnAircraft();
}
IContextSimulator *CApplication::getIContextSimulator()
{
if (!supportsContexts()) { return nullptr; }
return m_coreFacade->getIContextSimulator();
}
void CApplication::onCoreFacadeStarted()
{
// void
}
// ---------------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------------
bool CApplication::hasSetupReader() const
{
return m_setupReader;
}
CSetupReader *CApplication::getSetupReader() const
{
return m_setupReader.data();
}
CStatusMessageList CApplication::loadSetup()
{
if (m_shutdown) { return CStatusMessage(this).warning(u"Shutting down, not reading"); }
if (!m_setupReader) { return CStatusMessage(this).error(u"No reader for setup/version"); }
Q_ASSERT_X(m_parsed, Q_FUNC_INFO, "Not yet parsed");
const CStatusMessageList requestMsgs = m_setupReader->loadSetup();
return requestMsgs;
}
CUrlList CApplication::getVatsimMetarUrls() const
{
if (m_shutdown) { return CUrlList(); }
if (m_webDataServices)
{
const CUrlList urls(m_webDataServices->getVatsimMetarUrls());
if (!urls.isEmpty()) { return urls; }
}
if (m_setupReader)
{
return m_setupReader->getSetup().getVatsimMetarsUrls();
}
return CUrlList();
}
CUrlList CApplication::getVatsimDataFileUrls() const
{
if (m_shutdown) { return CUrlList(); }
if (m_webDataServices)
{
const CUrlList urls(m_webDataServices->getVatsimDataFileUrls());
if (!urls.isEmpty()) { return urls; }
}
if (m_setupReader)
{
return m_setupReader->getSetup().getVatsimDataFileUrls();
}
return CUrlList();
}
CUrl CApplication::getVatsimServerFileUrl() const
{
if (m_shutdown || !m_setupReader) { return {}; }
return m_setupReader->getSetup().getVatsimServerFileUrl();
}
CUrl CApplication::getVatsimFsdHttpUrl() const
{
if (m_shutdown || !m_setupReader) { return {}; }
return m_setupReader->getSetup().getVatsimFsdHttpUrl();
}
void CApplication::onCrashDumpUploadEnabledChanged()
{
const bool enabled = CBuildConfig::isReleaseBuild() && m_crashDumpSettings.getThreadLocal().isEnabled();
this->enableCrashDumpUpload(enabled);
}
void CApplication::simulateCrash()
{
CCrashHandler::instance()->simulateCrash();
}
void CApplication::simulateAssert()
{
CCrashHandler::instance()->simulateAssert();
}
void CApplication::enableCrashDumpUpload(bool enable)
{
CCrashHandler::instance()->setUploadsEnabled(enable);
}
bool CApplication::isSupportingCrashpad() const
{
#ifdef BLACK_USE_CRASHPAD
return true;
#else
return false;
#endif
}
void CApplication::httpRequestImplInQAMThread(const QNetworkRequest &request, int logId, const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
{
// run in QAM thread
if (this->isShuttingDown()) { return; }
QTimer::singleShot(0, m_accessManager, [=] {
// should be now in QAM thread
if (!sApp || sApp->isShuttingDown()) { return; }
Q_ASSERT_X(CThreadUtils::isInThisThread(sApp->m_accessManager), Q_FUNC_INFO, "Wrong thread, must be QAM thread");
this->httpRequestImpl(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
});
}
void CApplication::triggerNetworkAccessibilityCheck(int deferredMs)
{
if (this->isShuttingDown()) { return; }
if (!m_networkWatchDog) { return; }
QTimer::singleShot(deferredMs, m_accessManager, [=] {
// should be now in QAM thread
if (!sApp || sApp->isShuttingDown()) { return; }
Q_ASSERT_X(CThreadUtils::isInThisThread(sApp->m_accessManager), Q_FUNC_INFO, "Wrong thread, must be QAM thread");
const QNetworkAccessManager::NetworkAccessibility accessibility = m_accessManager->networkAccessible();
m_networkWatchDog->setNetworkAccessibility(accessibility);
});
}
QNetworkReply *CApplication::httpRequestImpl(
const QNetworkRequest &request, int logId,
const CApplication::CallbackSlot &callback, int maxRedirects, NetworkRequestOrPostFunction requestOrPostMethod)
{
ProgressSlot progress;
return this->httpRequestImpl(request, logId, callback, progress, maxRedirects, requestOrPostMethod);
}
QNetworkReply *CApplication::httpRequestImpl(
const QNetworkRequest &request, int logId,
const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest)
{
if (this->isShuttingDown()) { return nullptr; }
if (!this->isNetworkAccessible()) { return nullptr; }
QWriteLocker locker(&m_accessManagerLock);
Q_ASSERT_X(m_accessManager->thread() == qApp->thread(), Q_FUNC_INFO, "Network manager supposed to be in main thread");
if (!CThreadUtils::isInThisThread(m_accessManager))
{
this->httpRequestImplInQAMThread(request, logId, callback, progress, maxRedirects, getPostOrDeleteRequest);
return nullptr; // not yet started, will be called again in QAM thread
}
Q_ASSERT_X(CThreadUtils::isInThisThread(m_accessManager), Q_FUNC_INFO, "Network manager thread mismatch");
QNetworkRequest copiedRequest = CNetworkUtils::getSwiftNetworkRequest(request, this->getApplicationNameAndVersion());
QNetworkReply *reply = getPostOrDeleteRequest(*m_accessManager, copiedRequest);
reply->setProperty("started", QVariant(QDateTime::currentMSecsSinceEpoch()));
reply->setProperty(CUrlLog::propertyNameId(), QVariant(logId));
const QUrl url(reply->url());
QString urlStr = url.toString();
if (progress)
{
connect(reply, &QNetworkReply::downloadProgress, progress.object(), [=](qint64 current, qint64 max) {
progress(logId, current, max, url);
});
}
if (callback)
{
Q_ASSERT_X(callback.object(), Q_FUNC_INFO, "Need callback object (to determine thread)");
connect(
reply, &QNetworkReply::finished, callback.object(), [=] {
// Called when finished!
// QNetworkRequest::FollowRedirectsAttribute would allow auto redirect, but we use our approach as it gives us better control
// \fixme: Check again on Qt 5.9: Added redirects policy to QNetworkAccessManager (ManualRedirectsPolicy, NoLessSafeRedirectsPolicy, SameOriginRedirectsPolicy, UserVerifiedRedirectsPolicy)
const bool isRedirect = CNetworkUtils::isHttpStatusRedirect(reply);
if (isRedirect && maxRedirects > 0)
{
const QUrl redirectUrl = CNetworkUtils::getHttpRedirectUrl(reply);
if (!redirectUrl.isEmpty())
{
QNetworkRequest redirectRequest(redirectUrl);
const int redirectsLeft = maxRedirects - 1;
CLogMessage(sApp).info(u"Redirecting '%1' to '%2'") << urlStr << redirectUrl.toString();
this->httpRequestImplInQAMThread(redirectRequest, logId, callback, progress, redirectsLeft, getPostOrDeleteRequest);
return;
}
}
// called when there are no more callbacks
callback(reply);
},
Qt::QueuedConnection); // called in callback thread
}
return reply;
}
void CApplication::tagApplicationDataDirectory()
{
const QString d = CSwiftDirectories::normalizedApplicationDataDirectory();
const QDir dir(d);
if (!dir.exists() || !dir.isReadable()) { return; }
const QString aiStr(this->getApplicationInfo().toJsonString());
const QString filePath(CFileUtils::appendFilePaths(dir.path(), CApplicationInfo::fileName())); // will be overridden by next swift app
CFileUtils::writeStringToFile(aiStr, filePath);
}
} // ns