diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 14cc6a8..a216b59 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -23,6 +23,8 @@ set(SDK_SOURCES module_proxy.h token_manager.cpp token_manager.h + logos_mode.h + plugin_registry.h ) # Create the SDK library as STATIC instead of SHARED @@ -56,5 +58,7 @@ install(FILES logos_api_provider.h module_proxy.h token_manager.h + logos_mode.h + plugin_registry.h DESTINATION include ) \ No newline at end of file diff --git a/cpp/logos_api_consumer.cpp b/cpp/logos_api_consumer.cpp index e1b6bcd..da18318 100644 --- a/cpp/logos_api_consumer.cpp +++ b/cpp/logos_api_consumer.cpp @@ -2,6 +2,8 @@ #include "module_proxy.h" #include "logos_api_client.h" #include "token_manager.h" +#include "logos_mode.h" +#include "plugin_registry.h" #include #include #include @@ -18,8 +20,13 @@ LogosAPIConsumer::LogosAPIConsumer(const QString& module_to_talk_to, const QStri , m_connected(false) , m_token_manager(token_manager) { - m_node = new QRemoteObjectNode(this); - connectToRegistry(); + if (LogosModeConfig::isLocal()) { + qDebug() << "LogosAPIConsumer: Using Local mode - skipping QRemoteObjectNode"; + m_connected = true; + } else { + m_node = new QRemoteObjectNode(this); + connectToRegistry(); + } } LogosAPIConsumer::~LogosAPIConsumer() @@ -37,16 +44,27 @@ LogosAPIConsumer::~LogosAPIConsumer() QObject* LogosAPIConsumer::requestObject(const QString& objectName, int timeoutMs) { qDebug() << "LogosAPIConsumer: Requesting object:" << objectName << "at" << QTime::currentTime().toString("hh:mm:ss.zzz"); - if (!m_connected) { - qWarning() << "LogosAPIConsumer: Not connected to registry. Cannot request object:" << objectName; - return nullptr; - } if (objectName.isEmpty()) { qWarning() << "LogosAPIConsumer: Object name cannot be empty"; return nullptr; } + if (LogosModeConfig::isLocal()) { + QObject* plugin = PluginRegistry::getPlugin(objectName); + if (!plugin) { + qWarning() << "LogosAPIConsumer: Plugin not found in registry:" << objectName; + return nullptr; + } + qDebug() << "LogosAPIConsumer: Successfully found plugin:" << objectName; + return plugin; + } + + if (!m_connected) { + qWarning() << "LogosAPIConsumer: Not connected to registry. Cannot request object:" << objectName; + return nullptr; + } + qDebug() << "LogosAPIConsumer: Requesting object:" << objectName; // Acquire the dynamic replica @@ -80,6 +98,11 @@ QString LogosAPIConsumer::registryUrl() const bool LogosAPIConsumer::reconnect() { + if (LogosModeConfig::isLocal()) { + m_connected = true; + return true; + } + qDebug() << "LogosAPIConsumer: Attempting to reconnect to registry:" << m_registryUrl; // Disconnect first if already connected @@ -134,25 +157,30 @@ QVariant LogosAPIConsumer::invokeRemoteMethod(const QString& authToken, const QS // This method handles both ModuleProxy-wrapped modules (template_module, package_manager) // and direct remote object calls for other modules - QObject* replica = requestObject(objectName, timeoutMs); - if (!replica) { - qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << objectName; + QObject* plugin = requestObject(objectName, timeoutMs); + if (!plugin) { + qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << objectName; return QVariant(); } - // Try to cast to ModuleProxy first (in case the replica is a wrapped module) - ModuleProxy* moduleProxy = qobject_cast(replica); + ModuleProxy* moduleProxy = qobject_cast(plugin); if (moduleProxy) { QVariant result = moduleProxy->callRemoteMethod(authToken, methodName, args); - delete replica; + if (!LogosModeConfig::isLocal()) { + delete plugin; + } return result; } - // Fallback: use QMetaObject::invokeMethod directly - // Note: Remote objects' callRemoteMethod returns QRemoteObjectPendingCall, not QVariant + if (LogosModeConfig::isLocal()) { + qWarning() << "LogosAPIConsumer: Local mode requires ModuleProxy-wrapped objects"; + return QVariant(); + } + + // Remote mode: callRemoteMethod returns QRemoteObjectPendingCall, not QVariant QRemoteObjectPendingCall pendingCall; bool success = QMetaObject::invokeMethod( - replica, + plugin, "callRemoteMethod", Qt::DirectConnection, Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), @@ -163,13 +191,13 @@ QVariant LogosAPIConsumer::invokeRemoteMethod(const QString& authToken, const QS if (!success) { qWarning() << "LogosAPIConsumer: Failed to invoke callRemoteMethod on replica for object:" << objectName; - delete replica; + delete plugin; return QVariant(); } // Wait for the result pendingCall.waitForFinished(timeoutMs); - delete replica; + delete plugin; if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { qWarning() << "LogosAPIConsumer: Remote callRemoteMethod failed or timed out:" << pendingCall.error(); @@ -236,17 +264,30 @@ bool LogosAPIConsumer::informModuleToken(const QString& authToken, const QString { qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token; - // Request the ModuleProxy object - QObject* replica = requestObject("capability_module", 20000); - if (!replica) { - qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << "capability_module"; + QObject* plugin = requestObject("capability_module", 20000); + if (!plugin) { + qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object: capability_module"; + return false; + } + + ModuleProxy* moduleProxy = qobject_cast(plugin); + if (moduleProxy) { + bool result = moduleProxy->informModuleToken(authToken, moduleName, token); + qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; + if (!LogosModeConfig::isLocal()) { + delete plugin; + } + return result; + } + + if (LogosModeConfig::isLocal()) { + qWarning() << "LogosAPIConsumer: Local mode requires ModuleProxy-wrapped objects"; return false; } - // Use QRemoteObjectPendingCall similar to invokeRemoteMethod QRemoteObjectPendingCall pendingCall; bool success = QMetaObject::invokeMethod( - replica, + plugin, "informModuleToken", Qt::DirectConnection, Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), @@ -257,13 +298,12 @@ bool LogosAPIConsumer::informModuleToken(const QString& authToken, const QString if (!success) { qWarning() << "LogosAPIConsumer: Failed to invoke informModuleToken on replica"; - delete replica; + delete plugin; return false; } - // Wait for the result pendingCall.waitForFinished(20000); - delete replica; + delete plugin; if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); @@ -280,17 +320,29 @@ bool LogosAPIConsumer::informModuleToken_module(const QString& authToken, const { qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token; - // Request the ModuleProxy object - QObject* replica = requestObject(originModule, 20000); - if (!replica) { - qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << "capability_module"; + QObject* plugin = requestObject(originModule, 20000); + if (!plugin) { + qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << originModule; return false; } - // Use QRemoteObjectPendingCall similar to invokeRemoteMethod + ModuleProxy* moduleProxy = qobject_cast(plugin); + if (moduleProxy) { + bool result = moduleProxy->informModuleToken(authToken, moduleName, token); + qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; + if (!LogosModeConfig::isLocal()) { + delete plugin; + } + return result; + } + + if (LogosModeConfig::isLocal()) { + qWarning() << "LogosAPIConsumer: Local mode requires ModuleProxy-wrapped objects"; + return false; + } QRemoteObjectPendingCall pendingCall; bool success = QMetaObject::invokeMethod( - replica, + plugin, "informModuleToken", Qt::DirectConnection, Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), @@ -301,13 +353,12 @@ bool LogosAPIConsumer::informModuleToken_module(const QString& authToken, const if (!success) { qWarning() << "LogosAPIConsumer: Failed to invoke informModuleToken on replica"; - delete replica; + delete plugin; return false; } - // Wait for the result pendingCall.waitForFinished(20000); - delete replica; + delete plugin; if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); diff --git a/cpp/logos_api_consumer.h b/cpp/logos_api_consumer.h index ae2d787..9716ecc 100644 --- a/cpp/logos_api_consumer.h +++ b/cpp/logos_api_consumer.h @@ -9,6 +9,8 @@ #include #include +#include "logos_mode.h" + class QRemoteObjectNode; class TokenManager; diff --git a/cpp/logos_api_provider.cpp b/cpp/logos_api_provider.cpp index a268864..0f90e62 100644 --- a/cpp/logos_api_provider.cpp +++ b/cpp/logos_api_provider.cpp @@ -1,6 +1,8 @@ #include "logos_api_provider.h" #include "module_proxy.h" #include "logos_api.h" +#include "logos_mode.h" +#include "plugin_registry.h" #include #include #include @@ -16,6 +18,9 @@ LogosAPIProvider::LogosAPIProvider(const QString& module_name, QObject *parent) LogosAPIProvider::~LogosAPIProvider() { + if (LogosModeConfig::isLocal() && !m_registeredObjectName.isEmpty()) { + PluginRegistry::unregisterPlugin(m_registeredObjectName); + } // QRemoteObjectRegistryHost will be deleted automatically as it's a child object // ModuleProxy will be deleted automatically as it's a child object } @@ -60,20 +65,29 @@ bool LogosAPIProvider::registerObject(const QString& name, QObject* object) m_moduleProxy = new ModuleProxy(object, this); object = m_moduleProxy; - if (!m_registryHost) { - m_registryHost = new QRemoteObjectRegistryHost(QUrl(m_registryUrl)); - if (!m_registryHost) { - qCritical() << "LogosAPIProvider: Failed to create registry host"; - return false; - } - qDebug() << "LogosAPIProvider: Created registry host with URL:" << m_registryUrl; - } + bool success = false; - bool success = m_registryHost->enableRemoting(object, name); - if (success) { + if (LogosModeConfig::isLocal()) { + PluginRegistry::registerPlugin(object, name); + m_registeredObjectName = name; + success = true; qDebug() << "LogosAPIProvider: Successfully registered object with name:" << name; } else { - qCritical() << "LogosAPIProvider: Failed to register object with name:" << name; + if (!m_registryHost) { + m_registryHost = new QRemoteObjectRegistryHost(QUrl(m_registryUrl)); + if (!m_registryHost) { + qCritical() << "LogosAPIProvider: Failed to create registry host"; + return false; + } + qDebug() << "LogosAPIProvider: Created registry host with URL:" << m_registryUrl; + } + + success = m_registryHost->enableRemoting(object, name); + if (success) { + qDebug() << "LogosAPIProvider: Successfully registered object with name:" << name; + } else { + qCritical() << "LogosAPIProvider: Failed to register object with name:" << name; + } } return success; diff --git a/cpp/logos_api_provider.h b/cpp/logos_api_provider.h index c116417..5319851 100644 --- a/cpp/logos_api_provider.h +++ b/cpp/logos_api_provider.h @@ -10,6 +10,8 @@ class QRemoteObjectRegistryHost; class ModuleProxy; +#include "logos_mode.h" + /** * @brief LogosAPIProvider handles registering objects for remote access * @@ -72,6 +74,7 @@ private: QString m_registryUrl; QMap m_tokens; ModuleProxy* m_moduleProxy; + QString m_registeredObjectName; }; diff --git a/cpp/logos_mode.h b/cpp/logos_mode.h new file mode 100644 index 0000000..1511e18 --- /dev/null +++ b/cpp/logos_mode.h @@ -0,0 +1,74 @@ +#ifndef LOGOS_MODE_H +#define LOGOS_MODE_H + +#include + +/** + * @brief LogosMode defines the communication mode for the SDK + * + * - Remote: Uses QRemoteObjects for inter-process communication (desktop apps) + * - Local: Uses in-process PluginRegistry (mobile apps, single process) + */ +enum class LogosMode { + Remote, // Default: Use QRemoteObjects (IPC) + Local // Use in-process PluginRegistry +}; + +/** + * @brief LogosModeConfig provides global mode configuration + * + * Set the mode once at application startup before creating any LogosAPI instances. + * + * Example usage: + * LogosModeConfig::setMode(LogosMode::Local); // For mobile apps + * LogosAPI api("my_module"); // Will use local mode + */ +namespace LogosModeConfig { + + /** + * @brief Get the current mode (internal storage) + * @return Reference to the static mode variable + */ + inline LogosMode& modeStorage() { + static LogosMode mode = LogosMode::Remote; // Default to Remote + return mode; + } + + /** + * @brief Set the SDK communication mode + * @param mode The mode to use (Remote or Local) + * + * Call this before creating any LogosAPI instances. + */ + inline void setMode(LogosMode mode) { + modeStorage() = mode; + qDebug() << "LogosModeConfig: Mode set to" << (mode == LogosMode::Local ? "Local" : "Remote"); + } + + /** + * @brief Get the current SDK communication mode + * @return The current mode + */ + inline LogosMode getMode() { + return modeStorage(); + } + + /** + * @brief Check if the SDK is in Local mode + * @return true if Local mode, false if Remote mode + */ + inline bool isLocal() { + return modeStorage() == LogosMode::Local; + } + + /** + * @brief Check if the SDK is in Remote mode + * @return true if Remote mode, false if Local mode + */ + inline bool isRemote() { + return modeStorage() == LogosMode::Remote; + } + +} + +#endif // LOGOS_MODE_H diff --git a/cpp/module_proxy.cpp b/cpp/module_proxy.cpp index 55ac5bb..c0ba4ec 100644 --- a/cpp/module_proxy.cpp +++ b/cpp/module_proxy.cpp @@ -186,6 +186,12 @@ QVariant ModuleProxy::callRemoteMethod(const QString& authToken, const QString& return QVariant(); } + // TODO: review this. note this method is part of ModuleProxy + if (methodName == "getPluginMethods" && args.isEmpty()) { + qDebug() << "ModuleProxy: Handling getPluginMethods() directly"; + return QVariant(getPluginMethods()); + } + qDebug() << "ModuleProxy: Auth token received:" << authToken; qDebug() << "ModuleProxy: Calling method" << methodName << "on module" << m_module << "with args:" << args; diff --git a/cpp/plugin_registry.h b/cpp/plugin_registry.h new file mode 100644 index 0000000..ebff422 --- /dev/null +++ b/cpp/plugin_registry.h @@ -0,0 +1,130 @@ +#ifndef PLUGIN_REGISTRY_H +#define PLUGIN_REGISTRY_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief PluginRegistry provides in-process plugin registration for Local mode + * + * This namespace provides functions to register, unregister, and retrieve plugins + * using QCoreApplication properties. This is used as an alternative to QRemoteObjects + * for mobile apps that run in a single process. + */ +namespace PluginRegistry { + + // Mutex for thread-safe access to the registry + inline QMutex& registryMutex() { + static QMutex mutex; + return mutex; + } + + // Prefix for plugin keys to avoid conflicts with other properties + inline QString pluginKeyPrefix() { + return QStringLiteral("logos_plugin_"); + } + + /** + * @brief Convert a plugin name to a standardized key + * @param name The plugin name + * @return QString The standardized key + */ + inline QString toPluginKey(const QString& name) { + return pluginKeyPrefix() + name.toLower().replace(" ", "_"); + } + + /** + * @brief Register a plugin in the application properties + * @param plugin The plugin object to register + * @param name The name to register the plugin under + */ + inline void registerPlugin(QObject* plugin, const QString& name) { + if (!plugin) { + qWarning() << "PluginRegistry: Cannot register null plugin"; + return; + } + if (name.isEmpty()) { + qWarning() << "PluginRegistry: Cannot register plugin with empty name"; + return; + } + + QMutexLocker locker(®istryMutex()); + QString pluginKey = toPluginKey(name); + QCoreApplication::instance()->setProperty(pluginKey.toUtf8().constData(), QVariant::fromValue(plugin)); + qDebug() << "PluginRegistry: Registered plugin with key:" << pluginKey; + } + + /** + * @brief Unregister a plugin from the application properties + * @param name The name of the plugin to unregister + * @return bool true if successful + */ + inline bool unregisterPlugin(const QString& name) { + if (name.isEmpty()) { + qWarning() << "PluginRegistry: Cannot unregister plugin with empty name"; + return false; + } + + QMutexLocker locker(®istryMutex()); + QString pluginKey = toPluginKey(name); + QCoreApplication::instance()->setProperty(pluginKey.toUtf8().constData(), QVariant()); + qDebug() << "PluginRegistry: Unregistered plugin with key:" << pluginKey; + return true; + } + + /** + * @brief Get a plugin by name with automatic casting to the requested type + * @param name The name of the plugin to retrieve + * @return T* Pointer to the plugin, or nullptr if not found + */ + template + inline T* getPlugin(const QString& name) { + if (name.isEmpty()) { + qWarning() << "PluginRegistry: Cannot get plugin with empty name"; + return nullptr; + } + + QMutexLocker locker(®istryMutex()); + QString pluginKey = toPluginKey(name); + QVariant pluginVariant = QCoreApplication::instance()->property(pluginKey.toUtf8().constData()); + + if (pluginVariant.isValid()) { + QObject* obj = pluginVariant.value(); + if (obj) { + T* result = qobject_cast(obj); + if (result) { + qDebug() << "PluginRegistry: Found plugin:" << name; + return result; + } + } + } + + qDebug() << "PluginRegistry: Plugin not found:" << name; + return nullptr; + } + + /** + * @brief Check if a plugin is registered + * @param name The name of the plugin to check + * @return bool true if the plugin is registered + */ + inline bool hasPlugin(const QString& name) { + if (name.isEmpty()) { + return false; + } + + QMutexLocker locker(®istryMutex()); + QString pluginKey = toPluginKey(name); + QVariant pluginVariant = QCoreApplication::instance()->property(pluginKey.toUtf8().constData()); + return pluginVariant.isValid() && pluginVariant.value() != nullptr; + } + +} + +#endif // PLUGIN_REGISTRY_H diff --git a/nix/include.nix b/nix/include.nix index cc533a8..ecc283f 100644 --- a/nix/include.nix +++ b/nix/include.nix @@ -27,12 +27,16 @@ pkgs.stdenv.mkDerivation { # Install cpp headers and sources for file in logos_api.cpp logos_api.h logos_api_client.cpp logos_api_client.h \ logos_api_consumer.cpp logos_api_consumer.h logos_api_provider.cpp logos_api_provider.h \ - token_manager.cpp token_manager.h module_proxy.cpp module_proxy.h; do + token_manager.cpp token_manager.h module_proxy.cpp module_proxy.h logos_mode.h; do if [ -f cpp/$file ]; then cp cpp/$file $out/include/cpp/ fi done + if [ -f cpp/logos_mode.h ]; then + cp cpp/logos_mode.h $out/include/ + fi + runHook postInstall ''; }