Files
pilotclient/src/blackcore/application.h
Lars Toenning 0d62facea7 refactor: Remove webservice state/config member from CApplication
Reduce complexity and state of the CApplication. These members are used
anyway just to initialize the web service.
2024-02-17 21:30:05 +01:00

671 lines
27 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
//! \file
#ifndef BLACKCORE_APPLICATION_H
#define BLACKCORE_APPLICATION_H
#include "blackcore/blackcoreexport.h"
#include "blackcore/corefacadeconfig.h"
#include "blackcore/db/databasereaderconfig.h"
#include "blackcore/data/globalsetup.h"
#include "blackcore/githubpackagesreader.h"
#include "blackcore/application/applicationsettings.h"
#include "blackcore/inputmanager.h"
#include "blackcore/webreaderflags.h"
#include "blackmisc/db/updateinfo.h"
#include "blackmisc/network/url.h"
#include "blackmisc/network/networkutils.h"
#include "blackmisc/identifiable.h"
#include "blackmisc/slot.h"
#include "blackmisc/digestsignal.h"
#include "blackmisc/applicationinfolist.h"
#include "blackmisc/statusmessagelist.h"
#include "blackmisc/crashinfo.h"
#include <QByteArray>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QReadWriteLock>
#include <QStringList>
#include <QString>
#include <QList>
#include <QObject>
#include <QScopedPointer>
#include <QByteArray>
#include <atomic>
#include <functional>
class QHttpMultiPart;
class QNetworkReply;
class QNetworkRequest;
namespace BlackMisc
{
class CFileLogger;
class CLogCategoryList;
namespace SharedState
{
class CDataLinkDBus;
}
}
namespace BlackCore
{
class CCoreFacade;
class CCookieManager;
class CSetupReader;
class CWebDataServices;
class ISimulator;
namespace Context
{
class IContextApplication;
class IContextAudio;
class CContextAudioBase;
class IContextNetwork;
class IContextOwnAircraft;
class IContextSimulator;
}
/*!
* Our runtime. Normally one instance is to be initialized at the beginning of main, and thereafter
* it can be used everywhere via QApplication::instance
*
* - A swift standard cmd line parser is part of the application.
* Hence cmd arguments can be obtained any time / everywhere when required.
* Also some standard swift cmd arguments do not need to be re-implemented for each swift application.
* - The core facade (aka core runtime) is now part of the application. It can be started via cmd line arguments.
* - Settings are loaded
* - Setup is loaded (load the so called bootstrap file) to find servers and other resources
* - Update information (new swift versions etc.) are loaded
* - If applicable VATSIM status data (where are the VATSIM files?) are loaded
* - An end of lifetime can be specified, aka time bombing
*
* \sa BlackGui::CGuiApplication for the GUI version of application
*/
class BLACKCORE_EXPORT CApplication :
public QObject,
public BlackMisc::CIdentifiable
{
Q_OBJECT
public:
//! Similar to \sa QCoreApplication::instance() returns the single instance
static CApplication *instance();
//! Own log categories
static const QStringList &getLogCategories();
//! Constructor
explicit CApplication(BlackMisc::CApplicationInfo::Application application, bool init = true);
//! Constructor
explicit CApplication(const QString &applicationName = executable(), BlackMisc::CApplicationInfo::Application application = BlackMisc::CApplicationInfo::Unknown, bool init = true);
//! Destructor
~CApplication() override;
//! Information about all running apps (including this one only if exec() has already been called)
static BlackMisc::CApplicationInfoList getRunningApplications();
//! Is application running?
static bool isApplicationRunning(BlackMisc::CApplicationInfo::Application application);
//! True if this swift application is already running (including different versions)
bool isAlreadyRunning() const;
//! Graceful shutdown
virtual void gracefulShutdown();
//! Is application shutting down?
//! \threadsafe
bool isShuttingDown() const;
//! Is incognito mode?
//! \threadsafe
bool isIncognito() const;
//! Set incognito mode
//! \threadsafe
void setIncognito(bool incognito);
//! Toggle incognito mode
//! \threadsafe
void toggleIncognito();
//! swift application running
const BlackMisc::CApplicationInfo &getApplicationInfo() const { return m_applicationInfo; }
//! Application name and version
const QString &getApplicationName() const { return m_applicationName; }
//! Application name and version
const QString &getApplicationNameAndVersion() const;
//! Version, name beta and dev info
const QString &getApplicationNameVersionDetailed() const;
//! Executable names for the given applications
QString getExecutableForApplication(BlackMisc::CApplicationInfo::Application application) const;
//! Start the launcher
bool startLauncher();
//! Start the launcher and quit
bool startLauncherAndQuit();
//! Global setup
//! \threadsafe
Data::CGlobalSetup getGlobalSetup() const;
//! Update info
BlackMisc::Db::CUpdateInfo getUpdateInfo() const;
//! Reload update info
void reloadUpdateInfo();
//! Own distribution
//! \threadsafe
BlackMisc::Db::CDistribution getOwnDistribution() const;
//! String with beta, dev. and version
const QString &versionStringDetailed() const;
//! swift info string
const QString &swiftVersionString() const;
//! swift info string
const char *swiftVersionChar();
//! Running with dev.flag?
bool isDeveloperFlagSet() const { return m_devFlag; }
//! Comprehensive info
QString getInfoString(const QString &separator) const;
//! Stop and restart application
void restartApplication(const QStringList &newArguments = {}, const QStringList &removeArguments = {});
//! Finishes initialization and executes the event loop
int exec();
//! Directory for temporary files
static QString getTemporaryDirectory();
//! Register as running
//! \note Normally done automatically when CApplication::exec is called
static bool registerAsRunning();
//! Unregister from running
//! \note Normally done automatically, needed for restart
static bool unregisterAsRunning();
//! Exit application, perform graceful shutdown and exit
static void exit(int retcode = EXIT_SUCCESS);
//! Process all events for some time
//! \remark unlike QCoreApplication::processEvents this will spend at least the given time in the function, using QThread::msleep
//! \remark using processEventsFor can lead to undesired behaviour: A function may be called again before it is finished, even with only one thread
//! \sa BlackMisc::CEventLoop
static void processEventsFor(int milliseconds);
//! Clear the caches
//! \return all cache files
static QStringList clearCaches();
// ----------------------- settings -------------------------------
//! Unsaved settings
bool hasUnsavedSettings() const;
//! Save settings on shutdown
void saveSettingsOnShutdown(bool saveSettings);
//! All unsaved settings
QStringList getUnsavedSettingsKeys() const;
//! Save all settings
BlackMisc::CStatusMessage saveSettingsByKey(const QStringList &keys);
// ----------------------- cmd line args / parsing ----------------------------------------
//! Current parameters replaced by new arguments without the cmd line argument
QStringList argumentsJoined(const QStringList &newArguments = {}, const QStringList &removeArguments = {}) const;
//! Similar to QCoreApplication::arguments
static QStringList arguments();
//! \name cmd line args and parsing of command line options
//! @{
//! \copydoc QCommandLineParser::addOption
bool addParserOption(const QCommandLineOption &option);
//! \copydoc QCommandLineParser::addOptions
bool addParserOptions(const QList<QCommandLineOption> &options);
//! CMD line argument for DBus address
void addDBusAddressOption();
//! DBus address from CMD line, otherwise ""
QString getCmdDBusAddressValue() const;
//! Add the VATLIB options
void addVatlibOptions();
//! Add the audio options
void addAudioOptions();
//! Skip the single application check
bool skipSingleApplicationCheck() const;
//! Delegates to QCommandLineParser::isSet
bool isParserOptionSet(const QString &option) const;
//! Delegates to QCommandLineParser::isSet
bool isParserOptionSet(const QCommandLineOption &option) const;
//! Delegates to QCommandLineParser::value
QString getParserValue(const QString &option) const;
//! Delegates to QCommandLineParser::value
QString getParserValue(const QCommandLineOption &option) const;
//! Combined function that does a startup check, parses the command line arguments and loads the setup
//! \see startupCheck
//! \see parseCommandLineArguments
//! \see loadSetupAndHandleErrors
//! \remark This function cannot automatically called from the constructor because (1) it calls virtual
//! functions to show error messages and (2) the some command line arguments are added after constructing the object
bool parseCommandLineArgsAndLoadSetup();
//! Display error message
virtual void cmdLineErrorMessage(const QString &text, const QString &informativeText) const;
//! Display error message
virtual void cmdLineErrorMessage(const BlackMisc::CStatusMessageList &msgs) const;
//! @}
//! Simulate a crash
//! \private only for testing purposes
void simulateCrash();
//! Simulate an ASSERT
//! \private only for testing purposes
void simulateAssert();
//! Enable crash upload
//! \remark only change for testing
void enableCrashDumpUpload(bool enable);
//! Has crashpad support?
bool isSupportingCrashpad() const;
// ----------------------- Input ----------------------------------------
//! The input manager, if available
CInputManager *getInputManager() const { return m_inputManager; }
// ----------------------- simulator ----------------------------------------
//! The simulator plugin, if available
QPointer<ISimulator> getISimulator() const;
//! Simulator object available?
bool hasSimulator() const;
// ----------------------- contexts ----------------------------------------
//! Transport mechanism for sharing state between applications
BlackMisc::SharedState::CDataLinkDBus *getDataLinkDBus();
//! \name Context / core facade related
//! @{
//! Supports contexts
//! \remark checks the real availability of the contexts, so it can happen that we want to use contexts, and they are not yet initialized (false here)
//! \sa m_useContexts we use or we will use contexts
bool supportsContexts(bool ignoreShutdownTest = false) const;
//! The core facade config
const CCoreFacadeConfig &getCoreFacadeConfig() const { return m_coreFacadeConfig; }
//! Init the contexts part and start core facade
//! \sa coreFacadeStarted
//! \remark requires setup loaded
BlackMisc::CStatusMessageList initContextsAndStartCoreFacade(const CCoreFacadeConfig &coreConfig);
//! Starts the core facade without any contexts
//! \sa coreFacadeStarted
//! \remark requires setup loaded
BlackMisc::CStatusMessageList startCoreFacadeWithoutContexts();
//! Init web data services and start them
//! \sa webDataServicesStarted
//! \remark requires setup loaded
BlackMisc::CStatusMessageList initAndStartWebDataServices(CWebReaderFlags::WebReader webReader, const Db::CDatabaseReaderConfigList &dbReaderConfig);
//! Get the facade
CCoreFacade *getCoreFacade() { return m_coreFacade.data(); }
//! Local application? (not DBus)
bool isLocalContext() const;
//! DBus application? (not Local)
bool isDBusContext() const;
//! Get the facade
const CCoreFacade *getCoreFacade() const { return m_coreFacade.data(); }
//! @}
//! Direct access to contexts if a CCoreFacade has been initialized
//! @{
const Context::IContextNetwork *getIContextNetwork() const;
const Context::IContextAudio *getIContextAudio() const;
const Context::CContextAudioBase *getCContextAudioBase() const;
const Context::IContextApplication *getIContextApplication() const;
const Context::IContextOwnAircraft *getIContextOwnAircraft() const;
const Context::IContextSimulator *getIContextSimulator() const;
Context::IContextNetwork *getIContextNetwork();
Context::IContextAudio *getIContextAudio();
Context::CContextAudioBase *getCContextAudioBase();
Context::IContextApplication *getIContextApplication();
Context::IContextOwnAircraft *getIContextOwnAircraft();
Context::IContextSimulator *getIContextSimulator();
//! @}
// ----------------------- setup data ---------------------------------
//! Minimum mapping version check
virtual bool hasMinimumMappingVersion() const;
//! Setup reader?
bool hasSetupReader() const;
//! Access to setup reader
//! \remark supposed to be used only in special cases
CSetupReader *getSetupReader() const;
//! Setup already synchronized
bool isSetupAvailable() const;
//! Consolidated version of METAR URLs, either from CGlobalSetup or CVatsimSetup
//! \threadsafe
BlackMisc::Network::CUrl getVatsimMetarUrl() const;
//! Consolidated version of data file URL, either from CGlobalSetup or CVatsimSetup
//! \threadsafe
BlackMisc::Network::CUrl getVatsimDataFileUrl() const;
//! Get URL to file which contains the list of VATSIM servers
BlackMisc::Network::CUrl getVatsimServerFileUrl() const;
//! Get VATSIM FSD HTTP URL
BlackMisc::Network::CUrl getVatsimFsdHttpUrl() const;
//! Start services, if not yet parsed call CApplication::parse
virtual bool start();
// ------------------------- network -----------------------------------------------
//! \name network
//! @{
static constexpr int NoRedirects = -1; //!< network request not allowing redirects
static constexpr int NoLogRequestId = -1; //!< network request without logging
static constexpr int DefaultMaxRedirects = 2; //!< network request, default for max.redirects
//! The network reply callback when request is completed
using CallbackSlot = BlackMisc::CSlot<void(QNetworkReply *)>;
//! The progress slot
using ProgressSlot = BlackMisc::CSlot<void(int, qint64, qint64, const QUrl &)>;
//! Delete all cookies from cookie manager
void deleteAllCookies();
//! Access to access manager
//! \remark supposed to be used only in special cases
const QNetworkAccessManager *getNetworkAccessManager() const { return m_accessManager; }
//! Access to access manager
//! \remark supposed to be used only in special cases
QNetworkAccessManager *getNetworkAccessManager() { return m_accessManager; }
//! Web data services available?
//! \threadsafe
bool hasWebDataServices() const;
//! Get the web data services
//! \remark use hasWebDataServices to test if services are available
CWebDataServices *getWebDataServices() const;
//! Request to get network reply
//! \threadsafe
QNetworkReply *getFromNetwork(const BlackMisc::Network::CUrl &url,
const CallbackSlot &callback, int maxRedirects = DefaultMaxRedirects);
//! Request to get network reply
//! \threadsafe
QNetworkReply *getFromNetwork(const BlackMisc::Network::CUrl &url,
const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects = DefaultMaxRedirects);
//! Request to get network reply, supporting BlackMisc::Network::CUrlLog
//! \threadsafe
QNetworkReply *getFromNetwork(const BlackMisc::Network::CUrl &url, int logId,
const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects = DefaultMaxRedirects);
//! Request to get network reply
//! \threadsafe
QNetworkReply *getFromNetwork(const QNetworkRequest &request, const CallbackSlot &callback, int maxRedirects = DefaultMaxRedirects);
//! Request to get network reply
//! \threadsafe
QNetworkReply *getFromNetwork(const QNetworkRequest &request,
const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects = DefaultMaxRedirects);
//! Request to get network reply, supporting BlackMisc::Network::CUrlLog
//! \threadsafe
QNetworkReply *getFromNetwork(const QNetworkRequest &request, int logId,
const CallbackSlot &callback, const ProgressSlot &progress, int maxRedirects = DefaultMaxRedirects);
//! Request to delete a network resource from network, supporting BlackMisc::Network::CUrlLog
//! \threadsafe
QNetworkReply *deleteResourceFromNetwork(const QNetworkRequest &request, int logId,
const CallbackSlot &callback,
int maxRedirects = DefaultMaxRedirects);
//! Post to network
//! \threadsafe
QNetworkReply *postToNetwork(const QNetworkRequest &request, int logId, const QByteArray &data, const CallbackSlot &callback);
//! Post to network
//! \note This method takes ownership over \c multiPart.
//! \threadsafe
QNetworkReply *postToNetwork(const QNetworkRequest &request, int logId, QHttpMultiPart *multiPart, const CallbackSlot &callback);
//! Request to get network repy using HTTP's HEADER method
//! \threadsafe
QNetworkReply *headerFromNetwork(const BlackMisc::Network::CUrl &url, const CallbackSlot &callback, int maxRedirects = NoRedirects);
//! Request to get network repy using HTTP's HEADER method
//! \threadsafe
QNetworkReply *headerFromNetwork(const QNetworkRequest &request, const CallbackSlot &callback, int maxRedirects = NoRedirects);
//! Download file from network and store it as passed
//! \threadsafe
QNetworkReply *downloadFromNetwork(const BlackMisc::Network::CUrl &url, const QString &saveAsFileName,
const BlackMisc::CSlot<void(const BlackMisc::CStatusMessage &)> &callback, int maxRedirects = DefaultMaxRedirects);
//! @}
signals:
//! Update info available (cache, web load)
void updateInfoAvailable(bool success);
//! Startup has been completed
//! Will be triggered shortly before starting the event loop
void startUpCompleted(bool success);
//! Facade started
void coreFacadeStarted();
//! Web data services started
void webDataServicesStarted(bool success);
//! About to shutdown
void aboutToShutdown();
protected:
//! Display the failures caused by loading the setup file
virtual void displaySetupLoadFailure(BlackMisc::CStatusMessageList msgs);
//! Startup completed
virtual void onStartUpCompleted();
//! Init class, allows to init from BlackGui::CGuiApplication as well (pseudo virtual)
void init(bool withMetadata);
//! Is the command line option represented in the given arguments?
static int indexOfCommandLineOption(const QCommandLineOption &option, const QStringList &args = CApplication::arguments());
//! Arguments without that given option
static void argumentsWithoutOption(const QCommandLineOption &option, QStringList &args);
//! Can be used to parse specialized arguments
virtual bool parsingHookIn() { return true; }
//! Called when facade/contexts have been started
virtual void onCoreFacadeStarted();
//! Can be used to start special services
virtual BlackMisc::CStatusMessageList startHookIn() { return BlackMisc::CStatusMessageList(); }
//! Flag set or explicitly set to true
bool isSet(const QCommandLineOption &option) const;
//! Start the core facade
//! \note does nothing when setup is not yet loaded
BlackMisc::CStatusMessageList startCoreFacade();
//! Start the web data services
//! \note does nothing when setup is not yet loaded
BlackMisc::CStatusMessageList startWebDataServices(CWebReaderFlags::WebReader webReader, const Db::CDatabaseReaderConfigList &dbReaderConfig);
//! executable name
static const QString &executable();
//! Register metadata
static void registerMetadata();
// cmd parsing
QList<QCommandLineOption> m_allOptions; //!< All registered options
QCommandLineParser m_parser; //!< cmd parser
QCommandLineOption m_cmdHelp { "help" }; //!< help option
QCommandLineOption m_cmdVersion { "version" }; //!< version option
QCommandLineOption m_cmdDBusAddress { "emptyDBus" }; //!< DBus address
QCommandLineOption m_cmdDevelopment { "dev" }; //!< Development flag
QCommandLineOption m_cmdClearCache { "clearcache" }; //!< Clear cache
QCommandLineOption m_cmdTestCrashpad { "testcrashpad" }; //!< Test a crasphpad upload
QCommandLineOption m_cmdSkipSingleApp { "skipsa" }; //!< Skip test for single application
bool m_parsed = false; //!< Parsing accomplished?
bool m_started = false; //!< Started with success?
bool m_alreadyRunning = false; //!< Application already running
std::atomic_bool m_shutdown { false }; //!< Is being shutdown?
std::atomic_bool m_incognito { false }; //!< Incognito mode?
std::atomic_bool m_shutdownInProgress { false }; //!< shutdown in progress?
private:
//! Read the setup
BlackMisc::CStatusMessageList loadSetup();
//! Display help message
void cmdLineHelpMessage();
//! Display version message
void cmdLineVersionMessage();
//! Init network
void initNetwork();
//! init logging system
void initLogging();
//! Init parser
void initParser();
//! Dev.environment, return value will be used for m_devEnv
bool initIsRunningInDeveloperEnvironment() const;
//! Init the local settings
BlackMisc::CStatusMessage initLocalSettings();
using NetworkRequestOrPostFunction = std::function<QNetworkReply *(QNetworkAccessManager &, const QNetworkRequest &)>;
//! Implementation for getFromNetwork(), postToNetwork() and headerFromNetwork()
//! \return QNetworkReply reply will only be returned, if the QNetworkAccessManager is in the same thread
QNetworkReply *httpRequestImpl(const QNetworkRequest &request,
int logId, const CallbackSlot &callback,
int maxRedirects, NetworkRequestOrPostFunction requestOrPostMethod);
//! Implementation for getFromNetwork(), postToNetwork() and headerFromNetwork()
//! \return QNetworkReply reply will only be returned, if the QNetworkAccessManager is in the same thread
QNetworkReply *httpRequestImpl(const QNetworkRequest &request,
int logId, const CallbackSlot &callback, const ProgressSlot &progress,
int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest);
//! Call httpRequestImpl in correct thread
void httpRequestImplInQAMThread(const QNetworkRequest &request,
int logId, const CallbackSlot &callback, const ProgressSlot &progress,
int maxRedirects, NetworkRequestOrPostFunction getPostOrDeleteRequest);
//! Write meta information into the application directory so other swift versions can display them
void tagApplicationDataDirectory();
//! Check if all required runtime files are in place
//! \returns true if the check succeeded
bool startupCheck() const;
//! Loads the setup (bootstrap) and handles/displays warnings and error messages
//! \returns true if loading succeeded without errors
bool loadSetupAndHandleErrors();
//! Parses and handles the standard options such as help, version, parse error
//! \note in some cases (error, version, help) application is terminated during this step
//! \sa parsingHookIn
//! \return true means to continue, false to stop
bool parseCommandLineArguments();
CInputManager *m_inputManager = nullptr; //!< Input devices and hotkeys
QNetworkAccessManager *m_accessManager = nullptr; //!< single network access manager
BlackMisc::CApplicationInfo m_applicationInfo; //!< Application if specified
QScopedPointer<CCoreFacade> m_coreFacade; //!< core facade if any
QScopedPointer<CSetupReader> m_setupReader; //!< setup reader
QScopedPointer<CGitHubPackagesReader> m_gitHubPackagesReader; //!< github packages reader
QScopedPointer<CWebDataServices> m_webDataServices; //!< web data services
QScopedPointer<BlackMisc::CFileLogger> m_fileLogger; //!< file logger
QPointer<CCookieManager> m_cookieManager; //!< single cookie manager for our access manager
const QString m_applicationName; //!< application name
QReadWriteLock m_accessManagerLock; //!< lock to make access manager access threadsafe
CCoreFacadeConfig m_coreFacadeConfig; //!< Core facade config if any
bool m_useContexts = false; //!< use contexts
bool m_devFlag = false; //!< dev. environment
bool m_saveSettingsOnShutdown = true; //!< saving all settings on shutdown
bool m_localSettingsLoaded = false; //!< local settings loaded?
// -------------- crashpad -----------------
BlackMisc::CSettingReadOnly<Application::TCrashDumpSettings> m_crashDumpSettings { this, &CApplication::onCrashDumpUploadEnabledChanged };
//! Upload settings changed
void onCrashDumpUploadEnabledChanged();
};
} // namespace
//! Single instance of application object
extern BLACKCORE_EXPORT BlackCore::CApplication *sApp;
#endif // guard