From bba07ef4c4b0e91225998e9e241417523f7e016c Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Mon, 27 Apr 2020 19:04:01 +0200 Subject: [PATCH] [AFV] Signal/handling for authentication failures * authentication issues in AFV were silently handled * and there was no re-try, which could mean an initial glitch COULD cause AFV not working properly * NOW handling in AFV client and context * also reset connection data so for a new session no old authentication token or check time is used * check for "invalid" QDateTime(s) --- src/blackcore/afv/clients/afvclient.cpp | 54 ++++++++++++++++--- src/blackcore/afv/clients/afvclient.h | 12 ++++- .../afv/connection/apiserverconnection.cpp | 10 ++-- .../afv/connection/clientconnection.cpp | 16 +++--- .../afv/connection/clientconnectiondata.cpp | 26 +++++++-- .../afv/connection/clientconnectiondata.h | 10 ++-- src/blackcore/context/contextaudio.cpp | 7 +++ src/blackcore/context/contextaudio.h | 13 +++-- src/blackcore/context/contextaudioproxy.cpp | 6 ++- .../audiodevicevolumesetupcomponent.cpp | 4 +- src/swiftguistandard/swiftguistd.cpp | 8 +++ src/swiftguistandard/swiftguistd.h | 3 ++ src/swiftguistandard/swiftguistdinit.cpp | 11 +++- 13 files changed, 146 insertions(+), 34 deletions(-) diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 0ee8048f1..46b6f372d 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -62,7 +62,7 @@ namespace BlackCore m_output(new COutput(this)), m_voiceServerTimer(new QTimer(this)) { - this->setObjectName("AFV client: " + apiServer ); + this->setObjectName("AFV client: " + apiServer); m_connection->setReceiveAudio(false); connect(m_input, &CInput::opusDataAvailable, this, &CAfvClient::opusDataAvailable); @@ -144,7 +144,7 @@ namespace BlackCore } const bool integrated = sApp->getIContextSimulator()->getSimulatorSettings().isComIntegrated(); - const bool changed = integrated != m_integratedComUnit; + const bool changed = integrated != m_integratedComUnit; m_integratedComUnit = integrated; if (changed) @@ -167,9 +167,21 @@ namespace BlackCore this->connectWithContexts(); this->setCallsign(callsign); + QPointer myself(this); + if (!this->isConnected() && m_retryConnectAttempt == 0) + { + // check if connect simply did NOT receive an answer + QTimer::singleShot(20 * 1000, this, [ = ] + { + if (!myself) { return; } + if (m_retryConnectAttempt > 0) { return; } // already handled + + // this will reconnect ONLY if not already connected + this->retryConnectTo(cid, password, callsign, client, QStringLiteral("No connection afer 20secs")); + }); + } // thread safe connect { - QPointer myself(this); QMutexLocker lock(&m_mutexConnection); // async connection @@ -194,10 +206,12 @@ namespace BlackCore QMutexLocker lock(&m_mutex); if (m_voiceServerTimer) { m_voiceServerTimer->start(PositionUpdatesMs); } } + m_retryConnectAttempt = 0; emit this->connectionStatusChanged(Connected); } else { + myself->retryConnectTo(cid, password, callsign, client, QStringLiteral("AFV authentication failed for '%1' callsign '%2'").arg(cid, callsign)); emit this->connectionStatusChanged(Disconnected); } } @@ -1024,12 +1038,12 @@ namespace BlackCore void CAfvClient::autoLogoffWithoutFsdNetwork() { - if (!hasContexts()) { return; } - if (!this->isConnected()) { m_connectMismatches = 0; return; } + if (!hasContexts()) { return; } + if (!this->isConnected()) { m_fsdConnectMismatches = 0; return; } // AFV is connected - if (sApp->getIContextNetwork()->isConnected()) { m_connectMismatches = 0; return; } - if (++m_connectMismatches < 2) { return; } // avoid a single issue causing logoff + if (sApp->getIContextNetwork()->isConnected()) { m_fsdConnectMismatches = 0; return; } + if (++m_fsdConnectMismatches < 2) { return; } // avoid a single issue causing logoff CLogMessage(this).warning(u"Auto logoff AFV client because FSD no longer connected"); this->disconnectFrom(); @@ -1161,6 +1175,32 @@ namespace BlackCore emit this->receivingCallsignsChanged(args); } + void CAfvClient::retryConnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, const QString &reason) + { + if (this->isConnected()) { return; } + m_retryConnectAttempt++; + + const int retrySecs = qMin(3 * 60, m_retryConnectAttempt * 30); + const CStatusMessage msg = CStatusMessage(this).validationError(reason + ". Retry in %1secs. Attempt %2.") << retrySecs << m_retryConnectAttempt; + this->reconnectTo(cid, password, callsign, client, retrySecs * 1000, msg); + } + + void CAfvClient::reconnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, int delayMs, const CStatusMessage &msg) + { + if (msg.isFailure()) + { + emit this->afvConnectionFailure(msg); + } + + QPointer myself(this); + QTimer::singleShot(delayMs, this, [ = ] + { + if (!myself) { return; } + if (myself->isConnected()) { return; } + this->connectTo(cid, password, callsign, client); + }); + } + QVector CAfvClient::getAliasedStations() const { QMutexLocker lock(&m_mutex); diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index 8097ef3f0..91617a3a2 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -293,6 +293,9 @@ namespace BlackCore //! Connection status has been changed void connectionStatusChanged(ConnectionStatus status); + //! Authentication has failed with AFV server + void afvConnectionFailure(const BlackMisc::CStatusMessage &msg); + //! Client updated from own aicraft data void updatedFromOwnAircraftCockpit(); @@ -332,6 +335,12 @@ namespace BlackCore void onUpdateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); void onReceivingCallsignsChanged(const BlackCore::Afv::Audio::TransceiverReceivingCallsignsChangedArgs &args); + //! Re-try connection to server + void retryConnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, const QString &reason); + + //! Connect again in given ms + void reconnectTo(const QString &cid, const QString &password, const QString &callsign, const QString &client, int delayMs, const BlackMisc::CStatusMessage &msg); + //! All aliased stations //! \threadsafe //! @{ @@ -375,7 +384,8 @@ namespace BlackCore QSet m_enabledTransceivers; static const QVector &allTransceiverIds() { static const QVector transceiverIds{0, 1}; return transceiverIds; } - std::atomic_int m_connectMismatches { 0 }; + std::atomic_int m_fsdConnectMismatches { 0 }; //!< FSD no longer connected? + std::atomic_int m_retryConnectAttempt { 0 }; //!< Try to connect the n-th time std::atomic_bool m_isStarted { false }; std::atomic_bool m_loopbackOn { false }; std::atomic_bool m_enableAliased { true }; diff --git a/src/blackcore/afv/connection/apiserverconnection.cpp b/src/blackcore/afv/connection/apiserverconnection.cpp index e876011ad..eda6585f9 100644 --- a/src/blackcore/afv/connection/apiserverconnection.cpp +++ b/src/blackcore/afv/connection/apiserverconnection.cpp @@ -107,9 +107,9 @@ namespace BlackCore const qint64 validFromSecs = doc.object().value("nbf").toInt(-1); if (validFromSecs < 0) { break; } const qint64 localSecsSinceEpoch = QDateTime::currentSecsSinceEpoch(); - serverToUserOffsetSecs = validFromSecs - localSecsSinceEpoch; + serverToUserOffsetSecs = validFromSecs - localSecsSinceEpoch; const qint64 serverExpirySecs = doc.object().value("exp").toInt(); - const qint64 expiryLocalUtc = serverExpirySecs - serverToUserOffsetSecs; + const qint64 expiryLocalUtc = serverExpirySecs - serverToUserOffsetSecs; lifeTimeSecs = expiryLocalUtc - localSecsSinceEpoch; } while (false); @@ -117,7 +117,7 @@ namespace BlackCore if (lifeTimeSecs > 0) { m_serverToUserOffsetMs = serverToUserOffsetSecs * 1000; - m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs(lifeTimeSecs); + m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs(lifeTimeSecs); m_isAuthenticated = true; } @@ -254,7 +254,7 @@ namespace BlackCore // posted in QAM thread, reply is nullptr if called from another thread sApp->postToNetwork(request, CApplication::NoLogRequestId, json.toJson(), { - this, [ = ](QNetworkReply *nwReply) + this, [ = ](QNetworkReply * nwReply) { // called in "this" thread const QScopedPointer reply(nwReply); @@ -297,7 +297,7 @@ namespace BlackCore void CApiServerConnection::checkExpiry() { - if (QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60)) + if (!m_expiryLocalUtc.isValid() || QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60)) { QPointer myself(this); this->connectTo(m_username, m_password, m_client, m_networkVersion, diff --git a/src/blackcore/afv/connection/clientconnection.cpp b/src/blackcore/afv/connection/clientconnection.cpp index 327a229d4..de16b2c5c 100644 --- a/src/blackcore/afv/connection/clientconnection.cpp +++ b/src/blackcore/afv/connection/clientconnection.cpp @@ -31,10 +31,10 @@ namespace BlackCore CLogMessage(this).debug(u"ClientConnection instantiated"); // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); - // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); - connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat); + connect(m_voiceServerTimer, &QTimer::timeout, this, &CClientConnection::voiceServerHeartbeat); // sends heartbeat to server connect(m_udpSocket, &QUdpSocket::readyRead, this, &CClientConnection::readPendingDatagrams); connect(m_udpSocket, qOverload(&QUdpSocket::error), this, &CClientConnection::handleSocketError); } @@ -57,7 +57,7 @@ namespace BlackCore this, [ = ](bool authenticated) { // callback when connection has been established - if (!myself) { return; } + if (!myself) { return; } if (authenticated) { @@ -70,6 +70,10 @@ namespace BlackCore CLogMessage(this).info(u"Connected: '%1' to voice server, socket open: %2") << callsign << boolToYesNo(m_udpSocket->isOpen()); } + else + { + m_connection.reset(); + } // Make sure crypto channels etc. are created m_connection.setConnected(authenticated); @@ -92,15 +96,15 @@ namespace BlackCore // TODO emit disconnected(reason) CLogMessage(this).debug(u"Disconnected client: %1") << reason; - if (! m_connection.getCallsign().isEmpty()) + if (!m_connection.getCallsign().isEmpty()) { m_apiServerConnection->removeCallsign(m_connection.getCallsign()); } - // TODO connectionCheckCancelTokenSource.Cancel(); //Stops connection check loop + // TODO connectionCheckCancelTokenSource.Cancel(); // Stops connection check loop disconnectFromVoiceServer(); m_apiServerConnection->forceDisconnect(); - m_connection.setTokens({}); + m_connection.reset(); CLogMessage(this).debug(u"Disconnection complete"); } diff --git a/src/blackcore/afv/connection/clientconnectiondata.cpp b/src/blackcore/afv/connection/clientconnectiondata.cpp index 73ac8e5d1..0fce66e8e 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.cpp +++ b/src/blackcore/afv/connection/clientconnectiondata.cpp @@ -32,7 +32,14 @@ namespace BlackCore bool CClientConnectionData::isVoiceServerAlive() const { - return m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs; + return m_lastVoiceServerHeartbeatAckUtc.isValid() && + m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs; + } + + bool CClientConnectionData::isDataServerAlive() const + { + return m_lastDataServerHeartbeatAckUtc.isValid() && + m_lastDataServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > ServerTimeoutSecs; } void CClientConnectionData::createCryptoChannels() @@ -52,13 +59,24 @@ namespace BlackCore void CClientConnectionData::setTsHeartbeatToNow() { - m_lastVoiceServerHeartbeatAckUtc = QDateTime::currentDateTimeUtc(); + const QDateTime now = QDateTime::currentDateTimeUtc(); + m_lastVoiceServerHeartbeatAckUtc = now; + m_lastDataServerHeartbeatAckUtc = now; + } + + void CClientConnectionData::reset() + { + m_userName.clear(); + m_callsign.clear(); + m_authenticatedDateTimeUtc = QDateTime(); + m_lastVoiceServerHeartbeatAckUtc = QDateTime(); + this->setTokens({}); } bool CClientConnectionData::voiceServerAlive() const { - return timeSinceAuthenticationSecs() < ServerTimeoutSecs || - m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < ServerTimeoutSecs; + return (m_authenticatedDateTimeUtc.isValid() && timeSinceAuthenticationSecs() < ServerTimeoutSecs) || + (m_lastVoiceServerHeartbeatAckUtc.isValid() && m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < ServerTimeoutSecs); } } // ns } // ns diff --git a/src/blackcore/afv/connection/clientconnectiondata.h b/src/blackcore/afv/connection/clientconnectiondata.h index 00b67d8f7..e963b5513 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.h +++ b/src/blackcore/afv/connection/clientconnectiondata.h @@ -78,11 +78,14 @@ namespace BlackCore void setTsHeartbeatToNow(); //! @} + //! Reset all login related data + void reset(); + /* TODO - public long VoiceServerBytesSent { get; set; } + public long VoiceServerBytesSent { get; set; } public long VoiceServerBytesReceived { get; set; } - public long DataServerBytesSent { get; set; } - public long DataServerBytesReceived { get; set; } + public long DataServerBytesSent { get; set; } + public long DataServerBytesReceived { get; set; } */ QScopedPointer m_voiceCryptoChannel; //!< used crypto channel @@ -101,6 +104,7 @@ namespace BlackCore QDateTime m_authenticatedDateTimeUtc; QDateTime m_lastVoiceServerHeartbeatAckUtc; + QDateTime m_lastDataServerHeartbeatAckUtc; bool m_receiveAudio = true; //!< audio? bool m_connected = false; //!< connected? diff --git a/src/blackcore/context/contextaudio.cpp b/src/blackcore/context/contextaudio.cpp index 4b8ce073a..076da9a47 100644 --- a/src/blackcore/context/contextaudio.cpp +++ b/src/blackcore/context/contextaudio.cpp @@ -208,6 +208,7 @@ namespace BlackCore connect(m_voiceClient, &CAfvClient::stoppedAudio, this, &CContextAudioBase::stoppedAudio, Qt::QueuedConnection); connect(m_voiceClient, &CAfvClient::ptt, this, &CContextAudioBase::ptt, Qt::QueuedConnection); connect(m_voiceClient, &CAfvClient::connectionStatusChanged, this, &CContextAudioBase::onAfvConnectionStatusChanged, Qt::QueuedConnection); + connect(m_voiceClient, &CAfvClient::afvConnectionFailure, this, &CContextAudioBase::onAfvConnectionFailure, Qt::QueuedConnection); } void CContextAudioBase::terminateVoiceClient() @@ -640,6 +641,12 @@ namespace BlackCore } } + void CContextAudioBase::onAfvConnectionFailure(const CStatusMessage &msg) + { + if (!m_voiceClient) { return; } + emit this->voiceClientFailure(msg); + } + bool CContextAudioBase::isRunningWithLocalCore() { return sApp && sApp->isLocalContext(); diff --git a/src/blackcore/context/contextaudio.h b/src/blackcore/context/contextaudio.h index 6f549b222..d91037f09 100644 --- a/src/blackcore/context/contextaudio.h +++ b/src/blackcore/context/contextaudio.h @@ -83,8 +83,10 @@ namespace BlackCore //! Factory method static IContextAudio *create(CCoreFacade *runtime, CCoreFacadeConfig::ContextMode mode, BlackMisc::CDBusServer *server, QDBusConnection &connection); + // ------------- only use DBus signals here ------------- signals: - // only use DBus signals here + //! Authentication failed, .... + void voiceClientFailure(const BlackMisc::CStatusMessage &msg); public slots: // ------------- DBus --------------- @@ -285,7 +287,9 @@ namespace BlackCore //! PTT in voice client received void ptt(bool active, BlackMisc::Audio::PTTCOM pttcom, const BlackMisc::CIdentifier &identifier); - /** Workaround those must be invisible for DBus + /* + * Workaround those must be invisible for DBus + * //! VU levels @{ void inputVolumePeakVU (double value); @@ -298,7 +302,7 @@ namespace BlackCore //! Client updated from own aicraft data void updatedFromOwnAircraftCockpit(); - ** Workaround **/ + * end workaround */ // ------------ local signals ------- @@ -338,6 +342,9 @@ namespace BlackCore //! AFV client connection status changed void onAfvConnectionStatusChanged(int status); + //! AFV client authentication failed + void onAfvConnectionFailure(const BlackMisc::CStatusMessage &msg); + CActionBind m_actionPtt { BlackMisc::Input::pttHotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionComActive }; CActionBind m_actionPttCom1 { BlackMisc::Input::pttCom1HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionCom1 }; CActionBind m_actionPttCom2 { BlackMisc::Input::pttCom2HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudioBase::setVoiceTransmissionCom2 }; diff --git a/src/blackcore/context/contextaudioproxy.cpp b/src/blackcore/context/contextaudioproxy.cpp index b57f8bc25..0976fd8ec 100644 --- a/src/blackcore/context/contextaudioproxy.cpp +++ b/src/blackcore/context/contextaudioproxy.cpp @@ -83,8 +83,12 @@ namespace BlackCore void CContextAudioProxy::relaySignals(const QString &serviceName, QDBusConnection &connection) { - /** bool s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(), + "voiceClientFailure", this, SIGNAL(voiceClientFailure(BlackMisc::CStatusMessage))); + Q_ASSERT(s); + + /** + s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(), "changedAudioVolume", this, SIGNAL(changedAudioVolume(int))); Q_ASSERT(s); s = connection.connect(serviceName, IContextAudio::ObjectPath(), IContextAudio::InterfaceName(), diff --git a/src/blackgui/components/audiodevicevolumesetupcomponent.cpp b/src/blackgui/components/audiodevicevolumesetupcomponent.cpp index 45573e802..18b7504c3 100644 --- a/src/blackgui/components/audiodevicevolumesetupcomponent.cpp +++ b/src/blackgui/components/audiodevicevolumesetupcomponent.cpp @@ -161,11 +161,11 @@ namespace BlackGui afv->setRxTx(true, true, true, false); QPointer myself(this); - c = connect(afv, &CAfvClient::connectionStatusChanged, this, [ = ] + c = connect(afv, &CAfvClient::connectionStatusChanged, this, [ = ](CAfvClient::ConnectionStatus status) { if (!myself || !sGui || sGui->isShuttingDown()) { return; } myself->setTransmitReceiveInUiFromVoiceClient(); - + Q_UNUSED(status) }, ct); Q_ASSERT(c); m_afvConnections.append(c); diff --git a/src/swiftguistandard/swiftguistd.cpp b/src/swiftguistandard/swiftguistd.cpp index 7c5375825..08cb7874d 100644 --- a/src/swiftguistandard/swiftguistd.cpp +++ b/src/swiftguistandard/swiftguistd.cpp @@ -444,6 +444,14 @@ void SwiftGuiStd::onRequestedConsoleMessage(const QString &logMsg, bool clear) log->appendPlainTextToConsole(logMsg); } +void SwiftGuiStd::onAudioClientFailure(const CStatusMessage &msg) +{ + if (msg.isEmpty()) { return; } + if (!sGui || sGui->isShuttingDown()) { return; } + + ui->fr_CentralFrameInside->showOverlayHTMLMessage(msg); +} + void SwiftGuiStd::focusInMainEntryField() { ui->comp_MainKeypadArea->focusInEntryField(); diff --git a/src/swiftguistandard/swiftguistd.h b/src/swiftguistandard/swiftguistd.h index d413ec031..c86dd02f2 100644 --- a/src/swiftguistandard/swiftguistd.h +++ b/src/swiftguistandard/swiftguistd.h @@ -281,6 +281,9 @@ private: //! UI Console message has been recevied void onRequestedConsoleMessage(const QString &logMsg, bool clear); + //! Reported issue with the client + void onAudioClientFailure(const BlackMisc::CStatusMessage &msg); + //! Focus in main entry window void focusInMainEntryField(); diff --git a/src/swiftguistandard/swiftguistdinit.cpp b/src/swiftguistandard/swiftguistdinit.cpp index ae57be881..bf5463c79 100644 --- a/src/swiftguistandard/swiftguistdinit.cpp +++ b/src/swiftguistandard/swiftguistdinit.cpp @@ -30,6 +30,7 @@ #include "blackcore/webdataservices.h" #include "blackcore/context/contextnetwork.h" #include "blackcore/context/contextsimulator.h" +#include "blackcore/context/contextaudio.h" #include "blacksound/audioutilities.h" #include "blackmisc/network/networkutils.h" #include "blackmisc/loghandler.h" @@ -65,14 +66,14 @@ void SwiftGuiStd::init() // POST(!) GUI init Q_ASSERT_X(sGui, Q_FUNC_INFO, "Missing sGui"); Q_ASSERT_X(sGui->getWebDataServices(), Q_FUNC_INFO, "Missing web services"); - Q_ASSERT_X(sGui->supportsContexts(), Q_FUNC_INFO, "Missing contexts"); + Q_ASSERT_X(sGui->supportsContexts(), Q_FUNC_INFO, "Missing contexts"); if (m_init) { return; } ui->dw_InfoBarStatus->initialFloating(); this->setVisible(false); // hide all, so no flashing windows during init - m_mwaStatusBar = &m_statusBar; + m_mwaStatusBar = &m_statusBar; m_mwaOverlayFrame = ui->fr_CentralFrameInside; m_mwaLogComponent = ui->comp_MainInfoArea->getLogComponent(); sGui->initMainApplicationWidget(this); @@ -140,6 +141,12 @@ void SwiftGuiStd::init() Q_ASSERT(s); s = connect(&m_timerContextWatchdog, &QTimer::timeout, this, &SwiftGuiStd::handleTimerBasedUpdates); Q_ASSERT(s); + + if (sGui->getIContextAudio()) + { + s = connect(sGui->getIContextAudio(), &IContextAudio::voiceClientFailure, this, &SwiftGuiStd::onAudioClientFailure, Qt::QueuedConnection); + Q_ASSERT(s); + } Q_UNUSED(s) // check if DB data have been loaded