diff --git a/src/blackcore/blackcorefreefunctions.cpp b/src/blackcore/blackcorefreefunctions.cpp index 6462a5efb..69efd9b87 100644 --- a/src/blackcore/blackcorefreefunctions.cpp +++ b/src/blackcore/blackcorefreefunctions.cpp @@ -9,6 +9,7 @@ #include "voice_channel.h" #include "network.h" #include "simulator.h" +#include "context_application.h" #include namespace BlackCore @@ -19,6 +20,8 @@ namespace BlackCore qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); } bool isCurrentThreadObjectThread(QObject *toBeTested) diff --git a/src/blackcore/context_application.cpp b/src/blackcore/context_application.cpp index 5f40b6239..95221b70f 100644 --- a/src/blackcore/context_application.cpp +++ b/src/blackcore/context_application.cpp @@ -48,6 +48,22 @@ namespace BlackCore { this->logMessage(message, {}); }); + connect(CLogHandler::instance(), &CLogHandler::subscriptionAdded, this, [this](const CLogPattern &pattern) + { + this->addLogSubscription({}, pattern); + }); + connect(CLogHandler::instance(), &CLogHandler::subscriptionRemoved, this, [this](const CLogPattern &pattern) + { + this->removeLogSubscription({}, pattern); + }); + connect(this, &IContextApplication::logSubscriptionAdded, this, [this](const CIdentifier &subscriber, const CLogPattern &pattern) + { + this->m_logSubscriptions[subscriber].push_back(pattern); + }); + connect(this, &IContextApplication::logSubscriptionRemoved, this, [this](const CIdentifier &subscriber, const CLogPattern &pattern) + { + this->m_logSubscriptions[subscriber].removeAll(pattern); + }); connect(CSettingsCache::instance(), &CSettingsCache::valuesChangedByLocal, [this](const CVariantMap &settings) { @@ -122,3 +138,18 @@ namespace BlackCore } } // namespace + +QDBusArgument &operator <<(QDBusArgument &arg, const BlackCore::CLogSubscriptionHash &hash) +{ + QList listOfPairs; + for (auto it = hash.begin(); it != hash.end(); ++it) { listOfPairs.push_back({ it.key(), it.value() }); } + return arg << listOfPairs; +} + +const QDBusArgument &operator >>(const QDBusArgument &arg, BlackCore::CLogSubscriptionHash &hash) +{ + QList listOfPairs; + arg >> listOfPairs; + for (const auto &pair : listOfPairs) { hash.insert(pair.first, pair.second); } + return arg; +} diff --git a/src/blackcore/context_application.h b/src/blackcore/context_application.h index 47dca3d9b..0778489e4 100644 --- a/src/blackcore/context_application.h +++ b/src/blackcore/context_application.h @@ -18,6 +18,7 @@ #include "blackmisc/audio/voiceroomlist.h" #include "blackmisc/identifierlist.h" #include "blackmisc/variantmap.h" +#include "blackmisc/logpattern.h" #include #include @@ -36,6 +37,12 @@ namespace BlackCore { class CInputManager; + //! Used by application context to track which processes are subscribed to which patterns of log message + using CLogSubscriptionHash = QHash>; + + //! Used when marshalling CLogSubscriptionHash, as a QHash with CIdentifier keys can't be marshalled + using CLogSubscriptionPair = QPair>; + //! Application context interface class BLACKCORE_EXPORT IContextApplication : public CContext { @@ -78,6 +85,14 @@ namespace BlackCore //! \note Used with CLogMessage, do not use directly void messageLogged(const BlackMisc::CStatusMessage &message, const BlackMisc::CIdentifier &origin); + //! A process subscribed to a particular pattern of log messages + //! \note Used with CLogMessage, do not use directly + void logSubscriptionAdded(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern); + + //! A process unsubscribed from a particular pattern of log messages + //! \note Used with CLogMessage, do not use directly + void logSubscriptionRemoved(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern); + //! One or more settings were changed //! \note Used for cache relay, do not use directly void settingsChanged(const BlackMisc::CVariantMap &settings, const BlackMisc::CIdentifier &origin); @@ -93,12 +108,30 @@ namespace BlackCore //! Work around for audio context, #382 void fakedSetComVoiceRoom(const BlackMisc::Audio::CVoiceRoomList &requestedRooms); + protected: + //! Tracks which processes are subscribed to which patterns of log messages. + CLogSubscriptionHash m_logSubscriptions; + public slots: //! Log a log message //! \note Not pure because it can be called from the base class constructor. //! \note this is the function which relays CLogMessage via DBus virtual void logMessage(const BlackMisc::CStatusMessage &message, const BlackMisc::CIdentifier &origin) { Q_UNUSED(message); Q_UNUSED(origin); } + //! Subscribe a process to a particular pattern of log messages + //! \note This is the function which relays subscription changes via DBus + virtual void addLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) = 0; + + //! Unsubscribe a process from a particular pattern of log messages + //! \note This is the function which relays subscription changes via DBus + virtual void removeLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) = 0; + + //! Returns hash identifying which processes are subscribed to which patterns of log message + virtual BlackCore::CLogSubscriptionHash getAllLogSubscriptions() const = 0; + + //! Update log subscriptions hash from core + virtual void synchronizeLogSubscriptions() = 0; + //! Ratify some settings changed by another process //! \note Not pure because it can be called from the base class constructor. //! \note This is the function which relays cache changes via DBus. @@ -154,4 +187,13 @@ namespace BlackCore }; } +//! DBus marshalling for CLogSubscriptionHash, needed because QtDBus can't marshal a QHash with CIdentifier keys. +QDBusArgument &operator <<(QDBusArgument &arg, const BlackCore::CLogSubscriptionHash &); + +//! DBus unmarshalling for CLogSubscriptionHash, needed because QtDBus can't marshal a QHash with CIdentifier keys. +const QDBusArgument &operator >>(const QDBusArgument &arg, BlackCore::CLogSubscriptionHash &); + +Q_DECLARE_METATYPE(BlackCore::CLogSubscriptionHash) +Q_DECLARE_METATYPE(BlackCore::CLogSubscriptionPair) + #endif // guard diff --git a/src/blackcore/context_application_impl.cpp b/src/blackcore/context_application_impl.cpp index 3bbeb3298..edd1077d8 100644 --- a/src/blackcore/context_application_impl.cpp +++ b/src/blackcore/context_application_impl.cpp @@ -41,6 +41,29 @@ namespace BlackCore emit this->messageLogged(message, origin); } + void CContextApplication::addLogSubscription(const CIdentifier &subscriber, const CLogPattern &pattern) + { + if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } + emit this->logSubscriptionAdded(subscriber, pattern); + } + + void CContextApplication::removeLogSubscription(const CIdentifier &subscriber, const CLogPattern &pattern) + { + if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } + emit this->logSubscriptionRemoved(subscriber, pattern); + } + + CLogSubscriptionHash CContextApplication::getAllLogSubscriptions() const + { + if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } + return m_logSubscriptions; + } + + void CContextApplication::synchronizeLogSubscriptions() + { + // no-op: proxy implements this method by calling getAllLogSubscriptions + } + void CContextApplication::changeSettings(const CVariantMap &settings, const CIdentifier &origin) { // Intentionally don't check for round trip here diff --git a/src/blackcore/context_application_impl.h b/src/blackcore/context_application_impl.h index 697e23aa7..f5a261766 100644 --- a/src/blackcore/context_application_impl.h +++ b/src/blackcore/context_application_impl.h @@ -32,6 +32,18 @@ namespace BlackCore //! \copydoc IContextApplication::logMessage virtual void logMessage(const BlackMisc::CStatusMessage &message, const BlackMisc::CIdentifier &origin) override; + //! \copydoc IContextApplication::addLogSubscription + virtual void addLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) override; + + //! \copydoc IContextApplication::removeLogSubscription + virtual void removeLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) override; + + //! \copydoc IContextApplication::getAllLogSubscriptions + virtual CLogSubscriptionHash getAllLogSubscriptions() const; + + //! \copydoc IContextApplication::synchronizeLogSubscriptions + virtual void synchronizeLogSubscriptions(); + //! \copydoc IContextApplication::changeSettings virtual void changeSettings(const BlackMisc::CVariantMap &settings, const BlackMisc::CIdentifier &origin) override; diff --git a/src/blackcore/context_application_proxy.cpp b/src/blackcore/context_application_proxy.cpp index 3714a357d..e553f93d5 100644 --- a/src/blackcore/context_application_proxy.cpp +++ b/src/blackcore/context_application_proxy.cpp @@ -43,6 +43,12 @@ namespace BlackCore bool s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), "messageLogged", this, SIGNAL(messageLogged(BlackMisc::CStatusMessage, BlackMisc::CIdentifier))); Q_ASSERT(s); + s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), + "logSubscriptionAdded", this, SIGNAL(logSubscriptionAdded(BlackMisc::CIdentifier, BlackMisc::CLogPattern))); + Q_ASSERT(s); + s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), + "logSubscriptionRemoved", this, SIGNAL(logSubscriptionRemoved(BlackMisc::CIdentifier, BlackMisc::CLogPattern))); + Q_ASSERT(s); s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), "settingsChanged", this, SIGNAL(settingsChanged(BlackMisc::CVariantMap, BlackMisc::CIdentifier))); Q_ASSERT(s); @@ -62,6 +68,28 @@ namespace BlackCore this->m_dBusInterface->callDBus(QLatin1Literal("logMessage"), message, origin); } + void CContextApplicationProxy::addLogSubscription(const CIdentifier &subscriber, const CLogPattern &pattern) + { + this->m_dBusInterface->callDBus(QLatin1Literal("addLogSubscription"), subscriber, pattern); + } + + void CContextApplicationProxy::removeLogSubscription(const CIdentifier &subscriber, const CLogPattern &pattern) + { + this->m_dBusInterface->callDBus(QLatin1Literal("removeLogSubscription"), subscriber, pattern); + } + + CLogSubscriptionHash CContextApplicationProxy::getAllLogSubscriptions() const + { + return this->m_dBusInterface->callDBusRet(QLatin1Literal("getAllLogSubscriptions")); + } + + void CContextApplicationProxy::synchronizeLogSubscriptions() + { + // note this proxy method does not call synchronizeLogSubscriptions in core + m_logSubscriptions = getAllLogSubscriptions(); + for (const auto &pattern : CLogHandler::instance()->getAllSubscriptions()) { this->addLogSubscription({}, pattern); } + } + void CContextApplicationProxy::changeSettings(const CVariantMap &settings, const CIdentifier &origin) { this->m_dBusInterface->callDBus(QLatin1Literal("changeSettings"), settings, origin); diff --git a/src/blackcore/context_application_proxy.h b/src/blackcore/context_application_proxy.h index de91a9e4c..76e996cdd 100644 --- a/src/blackcore/context_application_proxy.h +++ b/src/blackcore/context_application_proxy.h @@ -28,6 +28,18 @@ namespace BlackCore //! \copydoc IContextApplication::logMessage virtual void logMessage(const BlackMisc::CStatusMessage &message, const BlackMisc::CIdentifier &origin) override; + //! \copydoc IContextApplication::addLogSubscription + virtual void addLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) override; + + //! \copydoc IContextApplication::removeLogSubscription + virtual void removeLogSubscription(const BlackMisc::CIdentifier &subscriber, const BlackMisc::CLogPattern &pattern) override; + + //! \copydoc IContextApplication::getAllLogSubscriptions + virtual CLogSubscriptionHash getAllLogSubscriptions() const; + + //! \copydoc IContextApplication::synchronizeLogSubscriptions + virtual void synchronizeLogSubscriptions(); + //! \copydoc IContextApplication::changeSettings virtual void changeSettings(const BlackMisc::CVariantMap &settings, const BlackMisc::CIdentifier &origin) override; diff --git a/src/blackmisc/blackmiscfreefunctions.cpp b/src/blackmisc/blackmiscfreefunctions.cpp index 595f7a024..db1c641fe 100644 --- a/src/blackmisc/blackmiscfreefunctions.cpp +++ b/src/blackmisc/blackmiscfreefunctions.cpp @@ -24,6 +24,7 @@ #include "pixmap.h" #include "iconlist.h" #include "identifierlist.h" +#include "logpattern.h" #include #include #include @@ -83,6 +84,7 @@ void BlackMisc::registerMetadata() CIconList::registerMetadata(); CLogCategory::registerMetadata(); CLogCategoryList::registerMetadata(); + CLogPattern::registerMetadata(); CPixmap::registerMetadata(); CIdentifier::registerMetadata(); CIdentifierList::registerMetadata(); diff --git a/src/blackmisc/loghandler.cpp b/src/blackmisc/loghandler.cpp index 23888c4ce..9dfa27d98 100644 --- a/src/blackmisc/loghandler.cpp +++ b/src/blackmisc/loghandler.cpp @@ -57,7 +57,7 @@ namespace BlackMisc auto it = std::find_if(m_patternHandlers.begin(), m_patternHandlers.end(), finder); if (it == m_patternHandlers.end()) { - auto *handler = new CLogPatternHandler(this); + auto *handler = new CLogPatternHandler(this, pattern); topologicallySortedInsert(m_patternHandlers, PatternPair(pattern, handler), comparator); return handler; } @@ -149,8 +149,21 @@ namespace BlackMisc } } - CLogPatternHandler::CLogPatternHandler(CLogHandler *parent) : - QObject(parent), m_parent(parent) + QList CLogHandler::getAllSubscriptions() const + { + QList result; + for (const auto &pair : m_patternHandlers) + { + if (pair.second->isSignalConnected(QMetaMethod::fromSignal(&CLogPatternHandler::messageLogged))) + { + result.push_back(pair.first); + } + } + return result; + } + + CLogPatternHandler::CLogPatternHandler(CLogHandler *parent, const CLogPattern &pattern) : + QObject(parent), m_parent(parent), m_pattern(pattern) { connect(&m_subscriptionUpdateTimer, &QTimer::timeout, this, &CLogPatternHandler::updateSubscription); m_subscriptionUpdateTimer.start(1); @@ -163,7 +176,20 @@ namespace BlackMisc m_subscriptionNeedsUpdate = false; bool isSubscribed = isSignalConnected(QMetaMethod::fromSignal(&CLogPatternHandler::messageLogged)); - if (m_inheritFallThrough && ! isSubscribed) + if (isSubscribed != m_isSubscribed) + { + m_isSubscribed = isSubscribed; + if (m_isSubscribed) + { + emit m_parent->subscriptionAdded(m_pattern); + } + else + { + emit m_parent->subscriptionRemoved(m_pattern); + } + } + + if (m_inheritFallThrough && ! m_isSubscribed) { m_parent->removePatternHandler(this); } diff --git a/src/blackmisc/loghandler.h b/src/blackmisc/loghandler.h index eb9cbdbf7..da90e8fe8 100644 --- a/src/blackmisc/loghandler.h +++ b/src/blackmisc/loghandler.h @@ -63,6 +63,9 @@ namespace BlackMisc return handlerForPattern(CLogPattern::exactMatch(CLogCategory::validation()).withSeverityAtOrAbove(CStatusMessage::SeverityWarning)); } + //! Returns all log patterns for which there are currently subscribed log pattern handlers. + QList getAllSubscriptions() const; + signals: //! Emitted when a message is logged in this process. void localMessageLogged(const BlackMisc::CStatusMessage &message); @@ -70,6 +73,12 @@ namespace BlackMisc //! Emitted when a log message is relayed from a different process. void remoteMessageLogged(const BlackMisc::CStatusMessage &message); + //! Emitted when an object subscribes to a pattern of log messages. + void subscriptionAdded(const BlackMisc::CLogPattern &pattern); + + //! Emitted when an object unsubscribes from a pattern of log messages. + void subscriptionRemoved(const BlackMisc::CLogPattern &pattern); + public slots: //! Called by our QtMessageHandler to log a message. void logLocalMessage(const BlackMisc::CStatusMessage &message); @@ -178,11 +187,13 @@ namespace BlackMisc private: friend class CLogHandler; - CLogPatternHandler(CLogHandler *parent); + CLogPatternHandler(CLogHandler *parent, const CLogPattern &pattern); CLogHandler *m_parent = nullptr; + CLogPattern m_pattern; bool m_inheritFallThrough = true; bool m_enableFallThrough = true; - std::atomic m_subscriptionNeedsUpdate = false; + bool m_isSubscribed = false; + std::atomic m_subscriptionNeedsUpdate { false }; QTimer m_subscriptionUpdateTimer; void updateSubscription(); }; diff --git a/src/swiftgui_standard/swiftguistd.cpp b/src/swiftgui_standard/swiftguistd.cpp index 4225960ae..c38d983f7 100644 --- a/src/swiftgui_standard/swiftguistd.cpp +++ b/src/swiftgui_standard/swiftguistd.cpp @@ -319,6 +319,7 @@ void SwiftGuiStd::setContextAvailability() if (this->m_coreAvailable) { // core has just become available + this->getIContextApplication()->synchronizeLogSubscriptions(); this->getIContextApplication()->synchronizeLocalSettings(); } else