refs #485, first version of a Gui/Core application class

Also specialized GUI application class for standard GUI
This commit is contained in:
Klaus Basan
2016-03-14 23:55:33 +00:00
committed by Mathew Sutcliffe
parent d9aac6427b
commit 158efe819a
27 changed files with 1170 additions and 506 deletions

View File

@@ -0,0 +1,432 @@
/* Copyright (C) 2016
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "application.h"
#include "blackcore/corefacade.h"
#include "blackcore/setupreader.h"
#include "blackcore/contextapplication.h"
#include "blackcore/registermetadata.h"
#include "blackcore/cookiemanager.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/project.h"
#include "blackmisc/dbusserver.h"
#include "blackmisc/registermetadata.h"
#include "blackmisc/network/networkutils.h"
#include <QStandardPaths>
#include <QFile>
#include <QFileInfo>
using namespace BlackMisc;
using namespace BlackMisc::Network;
using namespace BlackCore;
BlackCore::CApplication *sApp = nullptr; // set by constructor
namespace BlackCore
{
CApplication::CApplication(const QString &applicationName) :
m_applicationName(applicationName)
{
Q_ASSERT_X(!sApp, Q_FUNC_INFO, "already initialized");
Q_ASSERT_X(QCoreApplication::instance(), Q_FUNC_INFO, "no application object");
if (!sApp)
{
CApplication::initEnvironment();
QCoreApplication::setApplicationName(applicationName);
QCoreApplication::setApplicationVersion(CProject::version());
this->setObjectName(applicationName);
this->initParser();
this->initLogging();
// Translations
QFile file(":blackmisc/translations/blackmisc_i18n_de.qm");
CLogMessage(this).debug() << (file.exists() ? "Found translations in resources" : "No translations in resources");
QTranslator translator;
if (translator.load("blackmisc_i18n_de", ":blackmisc/translations/"))
{
CLogMessage(this).debug() << "Translator loaded";
}
QCoreApplication::instance()->installTranslator(&translator);
// Global setup / bootstraping
CCookieManager::instance(); // init cookie manager if ever needed
// trigger loading of settings
//! \todo maybe loaded twice, context initializing might trigger loading of settings a second time
CStatusMessage m = CSettingsCache::instance()->loadFromStore();
if (!m.isEmpty())
{
m.setCategories(getLogCategories());
CLogMessage(this).preformatted(m);
}
// global setup
sApp = this;
this->m_setupReader.reset(new CSetupReader(this));
this->m_parser.addOptions(this->m_setupReader->getCmdLineOptions());
// notify when app goes down
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &CApplication::gracefulShutdown);
}
}
CApplication::~CApplication()
{
this->gracefulShutdown();
}
QString CApplication::getApplicationNameAndVersion() const
{
return QCoreApplication::instance()->applicationName() + " " + CProject::version();
}
bool CApplication::start()
{
if (!this->m_parsed)
{
bool s = this->parse();
if (!s) { return false; }
}
// parsing itself is done
if (this->m_startSetupReader)
{
CStatusMessage m(this->requestReloadOfSetupAndVersion());
if (m.isWarningOrAbove())
{
this->errorMessage(m.getMessage());
return false;
}
}
this->m_started = this->startHookIn();
return this->m_started;
}
bool CApplication::isSetupSyncronized() const
{
if (this->m_shutdown || !this->m_setupReader) { return false; }
return this->m_setupReader->isSetupSyncronized();
}
CStatusMessage CApplication::requestReloadOfSetupAndVersion()
{
if (!this->m_shutdown)
{
Q_ASSERT_X(this->m_setupReader, Q_FUNC_INFO, "Missing reader");
Q_ASSERT_X(this->m_parsed, Q_FUNC_INFO, "Not yet parsed");
return this->m_setupReader->asyncLoad();
}
else
{
return CStatusMessage(getLogCategories(), CStatusMessage::SeverityError, "No reader for setup/version");
}
}
QNetworkReply *CApplication::requestNetworkResource(const QNetworkRequest &request, const BlackMisc::CSlot<void(QNetworkReply *)> &callback)
{
if (this->m_shutdown) { return nullptr; }
QNetworkRequest r(request);
CNetworkUtils::ignoreSslVerification(r);
QNetworkReply *reply = this->m_accessManager.get(r);
if (callback)
{
connect(reply, &QNetworkReply::finished, callback.object(), [ = ] { callback(reply); });
}
return reply;
}
int CApplication::exec()
{
Q_ASSERT_X(instance(), Q_FUNC_INFO, "missing application");
return QCoreApplication::exec();
}
void CApplication::exit(int retcode)
{
if (instance())
{
instance()->gracefulShutdown();
}
// when the event loop is not running, this does nothing
QCoreApplication::exit(retcode);
}
QStringList CApplication::arguments()
{
return QCoreApplication::arguments();
}
void CApplication::useContexts(const CCoreFacadeConfig &coreConfig)
{
if (this->m_coreFacade.isNull())
{
this->m_coreFacade.reset(new CCoreFacade(coreConfig));
this->coreFacadeStarted();
}
}
void CApplication::initLogging()
{
CLogHandler::instance()->install(); // make sure we have a log handler!
// File logger
static const QString logPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/org.swift-project/logs";
this->m_fileLogger.reset(new CFileLogger(executable(), logPath));
this->m_fileLogger->changeLogPattern(CLogPattern().withSeverityAtOrAbove(CStatusMessage::SeverityDebug));
}
void CApplication::initParser()
{
this->m_parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
this->m_parser.setApplicationDescription(m_applicationName);
this->m_cmdHelp = this->m_parser.addHelpOption();
this->m_cmdVersion = this->m_parser.addVersionOption();
}
void CApplication::initEnvironment()
{
BlackMisc::registerMetadata();
BlackCore::registerMetadata();
}
void CApplication::gracefulShutdown()
{
if (this->m_shutdown) { return; }
this->m_shutdown = true;
// save settings (but only when application was really alive)
CStatusMessage m;
if (this->m_parsed)
{
if (this->supportsContexts())
{
m = this->getIContextApplication()->saveSettings();
}
else
{
m = CSettingsCache::instance()->saveToStore();
}
CLogMessage(getLogCategories()).preformatted(m);
}
// from here on we really rip appart the application object
// and it should no longer be used
sApp = nullptr;
disconnect(this);
if (this->m_setupReader)
{
this->m_setupReader->gracefulShutdown();
}
if (this->supportsContexts())
{
// clean up facade
this->m_coreFacade->gracefulShutdown();
this->m_coreFacade.reset();
}
this->m_fileLogger->close();
}
CApplication *BlackCore::CApplication::instance()
{
return sApp;
}
const QString &CApplication::executable()
{
static const QString e(QFileInfo(QCoreApplication::applicationFilePath()).completeBaseName());
return e;
}
const BlackMisc::CLogCategoryList &CApplication::getLogCategories()
{
static const CLogCategoryList l({ CLogCategory("swift.application"), CLogCategory("swift." + executable())});
return l;
}
// ---------------------------------------------------------------------------------
// Parsing
// ---------------------------------------------------------------------------------
bool CApplication::addParserOption(const QCommandLineOption &option)
{
return this->m_parser.addOption(option);
}
void CApplication::addDBusAddressOption()
{
this->m_cmdDBusAddress = QCommandLineOption({ "dbus", "dbus-address", "dbusaddress" },
QCoreApplication::translate("application", "DBus address."),
"dbusaddress");
this->addParserOption(this->m_cmdDBusAddress);
}
QString CApplication::getCmdDBusAddressValue() const
{
if (this->isParserOptionSet(this->m_cmdDBusAddress))
{
const QString v(this->getParserOptionValue(m_cmdDBusAddress));
const QString dBusAddress(CDBusServer:: normalizeAddress(v));
return dBusAddress;
}
else
{
return "";
}
}
bool CApplication::isParserOptionSet(const QString &option) const
{
return this->m_parser.isSet(option);
}
bool CApplication::isParserOptionSet(const QCommandLineOption &option) const
{
return this->m_parser.isSet(option);
}
QString CApplication::getParserOptionValue(const QString &option) const
{
return this->m_parser.value(option).trimmed();
}
QString CApplication::getParserOptionValue(const QCommandLineOption &option) const
{
return this->m_parser.value(option).trimmed();
}
bool CApplication::parse()
{
if (this->m_parsed) { return m_parsed; }
// we call parse because we also want to display a GUI error message when applicable
QStringList args(QCoreApplication::instance()->arguments());
if (!this->m_parser.parse(args))
{
this->errorMessage(this->m_parser.errorText());
return false;
}
// help/version
if (this->m_parser.isSet(this->m_cmdHelp))
{
// Important parser help will already stop application
this->helpMessage();
return true;
}
if (this->m_parser.isSet(this->m_cmdVersion))
{
this->versionMessage();
return true;
}
// Hookin, other parsing
if (!this->parsingHookIn()) { return false; }
// setup reader
this->m_startSetupReader = this->m_setupReader->parseCmdLineArguments();
this->m_parsed = true;
return true;
}
void CApplication::errorMessage(const QString &errorMessage) const
{
fputs(qPrintable(errorMessage), stderr);
fputs("\n\n", stderr);
fputs(qPrintable(this->m_parser.helpText()), stderr);
}
void CApplication::helpMessage()
{
this->m_parser.showHelp(); // terminates
Q_UNREACHABLE();
}
void CApplication::versionMessage() const
{
printf("%s %s\n", qPrintable(QCoreApplication::applicationName()), qPrintable(QCoreApplication::applicationVersion()));
}
// ---------------------------------------------------------------------------------
// Contexts
// ---------------------------------------------------------------------------------
bool CApplication::supportsContexts() const
{
if (m_coreFacade.isNull()) { return false; }
if (!this->m_coreFacade->getIContextApplication()) { return false; }
return (!this->m_coreFacade->getIContextApplication()->isEmptyObject());
}
const IContextNetwork *CApplication::getIContextNetwork() const
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextNetwork();
}
const IContextAudio *CApplication::getIContextAudio() const
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextAudio();
}
const IContextApplication *CApplication::getIContextApplication() const
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextApplication();
}
const IContextOwnAircraft *CApplication::getIContextOwnAircraft() const
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextOwnAircraft();
}
const IContextSimulator *CApplication::getIContextSimulator() const
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextSimulator();
}
IContextNetwork *CApplication::getIContextNetwork()
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextNetwork();
}
IContextAudio *CApplication::getIContextAudio()
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextAudio();
}
IContextApplication *CApplication::getIContextApplication()
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextApplication();
}
IContextOwnAircraft *CApplication::getIContextOwnAircraft()
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextOwnAircraft();
}
IContextSimulator *CApplication::getIContextSimulator()
{
if (!supportsContexts()) { return nullptr; }
return this->m_coreFacade->getIContextSimulator();
}
// ---------------------------------------------------------------------------------
} // ns

217
src/blackcore/application.h Normal file
View File

@@ -0,0 +1,217 @@
/* Copyright (C) 2016
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKCORE_APPLICATION_H
#define BLACKCORE_APPLICATION_H
#include "corefacadeconfig.h"
#include "blackmisc/logcategorylist.h"
#include "blackmisc/filelogger.h"
#include "blackmisc/slot.h"
#include "blackcoreexport.h"
#include <QObject>
#include <QScopedPointer>
#include <QNetworkAccessManager>
#include <QCommandLineParser>
namespace BlackCore
{
class CCoreFacade;
class CSetupReader;
class IContextApplication;
class IContextAudio;
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 optained 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 bootsrap file) to find servers and other resources
*
* \sa BlackGui::CGuiApplication for the GUI version of application
*/
class BLACKCORE_EXPORT CApplication : public QObject
{
Q_OBJECT
public:
//! Similar to \sa QCoreApplication::instance() returns the single instance
static CApplication *instance();
//! Constructor
CApplication(const QString &applicationName = executable());
//! Destructor
virtual ~CApplication();
//! Application name and version
QString getApplicationNameAndVersion() const;
//! Start services, if not yet parsed call CApplication::parse
virtual bool start();
//! Request to get network reply
QNetworkReply *requestNetworkResource(const QNetworkRequest &request,
const BlackMisc::CSlot<void(QNetworkReply *)> &callback);
//! Setup already syncronized
bool isSetupSyncronized() const;
//! Reload setup and version
BlackMisc::CStatusMessage requestReloadOfSetupAndVersion();
//! Run event loop
static int exec();
//! Exit application, perform graceful shutdown and exit
static void exit(int retcode = 0);
//! Similar to QCoreApplication::arguments
static QStringList arguments();
// ----------------------- parsing ----------------------------------------
//! \name parsing of command line options
//! @{
//! \copydoc QCommandLineParser::addOption(const QCommandLineOption &)
bool addParserOption(const QCommandLineOption &option);
//! CMD line argument for DBus address
void addDBusAddressOption();
//! DBus address from CMD line, otherwise ""
QString getCmdDBusAddressValue() 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 getParserOptionValue(const QString &option) const;
//! Delegates to QCommandLineParser::value
QString getParserOptionValue(const QCommandLineOption &option) const;
//! Display parser error message
virtual void errorMessage(const QString &errorMessage) const;
//! 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
bool parse();
//! @}
// ----------------------- contexts ----------------------------------------
//! \name Context / core facade related
//! @{
//! Supports contexts
bool supportsContexts() const;
//! Init the contexts part and start core facade
//! \sa coreFacadeStarted
void useContexts(const CCoreFacadeConfig &coreConfig);
//! Get the facade
CCoreFacade *getCoreFacade() { return m_coreFacade.data(); }
//! Get the facade
const CCoreFacade *getCoreFacade() const { return m_coreFacade.data(); }
//! @}
//! \name Direct access to contexts if a CCoreFacade has been initialized
//! @{
const IContextNetwork *getIContextNetwork() const;
const IContextAudio *getIContextAudio() const;
const IContextApplication *getIContextApplication() const;
const IContextOwnAircraft *getIContextOwnAircraft() const;
const IContextSimulator *getIContextSimulator() const;
IContextNetwork *getIContextNetwork();
IContextAudio *getIContextAudio();
IContextApplication *getIContextApplication();
IContextOwnAircraft *getIContextOwnAircraft();
IContextSimulator *getIContextSimulator();
//! @}
public slots:
//! Graceful shutdown
virtual void gracefulShutdown();
signals:
//! Facade started
void coreFacadeStarted();
protected:
//! Constructor
CApplication(const QString &applicationName, QCoreApplication *app);
//! executable name
static const QString &executable();
//! Own log categories
static const BlackMisc::CLogCategoryList &cats();
//! Display help message
virtual void helpMessage();
//! Display version message
virtual void versionMessage() const;
//! Can be used to parse specialized arguments
virtual bool parsingHookIn() { return true; }
//! Can be used to start special services
virtual bool startHookIn() { return true; }
// cmd parsing
QCommandLineParser m_parser; //!< cmd parser
QCommandLineOption m_cmdHelp {"help"}; //!< help option
QCommandLineOption m_cmdVersion { "version" }; //!< version option
QCommandLineOption m_cmdDBusAddress {"empty"}; //!< DBus address
bool m_parsed = false; //!< Parsing accomplished?
bool m_started = false; //!< started?
bool m_startSetupReader = false; //!< start the setup reader
private:
//! init logging system
void initLogging();
//! Init parser
void initParser();
//! static init part
static void initEnvironment();
QScopedPointer<CCoreFacade> m_coreFacade; //!< core facade if any
QScopedPointer<CSetupReader> m_setupReader; //!< setup reader
QScopedPointer<BlackMisc::CFileLogger> m_fileLogger; //!< file logger
QNetworkAccessManager m_accessManager { this }; //!< single network access manager
QString m_applicationName; //!< application name
bool m_shutdown = false; //!< is being shut down
};
} // namespace
//! Single instance of application object
extern BLACKCORE_EXPORT BlackCore::CApplication *sApp;
#endif // guard

View File

@@ -0,0 +1,61 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
//! \file
#ifndef BLACKGUI_GUIMODEENUMS_H
#define BLACKGUI_GUIMODEENUMS_H
#include <QString>
namespace BlackCore
{
//! Modes, how GUI can be started (core/GUI)
struct CoreModes
{
public:
//! Core runs how and where?
enum CoreMode
{
CoreInGuiProcess,
CoreExternalCoreAudio,
CoreExternalAudioGui
};
//! String to core mode
static CoreMode stringToCoreMode(const QString &m)
{
QString cm(m.toLower().trimmed());
if (cm.isEmpty()) { return CoreInGuiProcess; }
if (m == coreModeToString(CoreExternalCoreAudio)) { return CoreExternalCoreAudio; }
if (m == coreModeToString(CoreInGuiProcess)) { return CoreInGuiProcess; }
if (m == coreModeToString(CoreExternalAudioGui)) { return CoreExternalAudioGui; }
// some alternative names
if (cm.contains("audiolocal")) { return CoreExternalAudioGui; }
if (cm.contains("localaudio")) { return CoreExternalAudioGui; }
if (cm.contains("external")) { return CoreExternalCoreAudio; }
if (cm.contains("gui")) { return CoreInGuiProcess; }
return CoreInGuiProcess;
}
//! Core mode as string
static QString coreModeToString(CoreMode mode)
{
switch (mode)
{
case CoreInGuiProcess: return "coreinguiprocess";
case CoreExternalCoreAudio: return "coreexternal";
case CoreExternalAudioGui: return "coreexternalaudiogui";
}
return "";
}
};
}
#endif // guard

View File

@@ -24,9 +24,6 @@ using namespace BlackMisc::Aviation;
namespace BlackCore
{
/*
* Constructor
*/
CVoiceVatlib::CVoiceVatlib(QObject *parent) :
IVoice(parent),
m_audioService(Vat_CreateAudioService()),
@@ -38,9 +35,6 @@ namespace BlackCore
this->startTimer(10);
}
/*
* Destructor
*/
CVoiceVatlib::~CVoiceVatlib() {}
QSharedPointer<IVoiceChannel> CVoiceVatlib::createVoiceChannel()

View File

@@ -7,6 +7,7 @@
* contained in the LICENSE file.
*/
#include "blackcore/application.h"
#include "blackcore/setupreader.h"
#include "blackcore/webdataservices.h"
#include "blackcore/modeldatareader.h"
@@ -40,6 +41,7 @@ namespace BlackCore
QObject(parent), m_readerFlags(readerFlags), m_autoReadAfterSetupMs(autoReadAfterSetupSynchronizedMs)
{
Q_ASSERT_X(QSslSocket::supportsSsl(), Q_FUNC_INFO, "missing SSL support");
if (!sApp) { return; } // shutting doen
this->setObjectName("CWebDataReader");
this->initReaders(readerFlags);
this->initWriters();
@@ -47,14 +49,14 @@ namespace BlackCore
{
// wait for setup read completion
// in case this was already fired or will never be fired set a time out
if (CSetupReader::instance().updatedWithinLastMs(10 * 1000))
if (sApp->isSetupSyncronized())
{
QTimer::singleShot(500, this, &CWebDataServices::ps_setupTimedOut);
}
else
{
connect(&CSetupReader::instance(), &CSetupReader::setupSynchronized, this, &CWebDataServices::ps_setupRead);
QTimer::singleShot(10 * 1000, this, &CWebDataServices::ps_setupTimedOut);
//! \todo change !!!!!!
QTimer::singleShot(2500, this, &CWebDataServices::ps_setupTimedOut);
}
}
}