Merge pull request #1 from logos-co/support_local

Add support for a 'local' mode that does not use separate processes. required for some platforms such as mobile
This commit is contained in:
Iuri Matias
2025-12-02 13:26:32 -05:00
committed by GitHub
9 changed files with 335 additions and 47 deletions
+4
View File
@@ -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
)
+86 -35
View File
@@ -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 <QRemoteObjectNode>
#include <QRemoteObjectReplica>
#include <QRemoteObjectPendingCall>
@@ -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<QObject>(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<ModuleProxy*>(replica);
ModuleProxy* moduleProxy = qobject_cast<ModuleProxy*>(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<ModuleProxy*>(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<ModuleProxy*>(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();
+2
View File
@@ -9,6 +9,8 @@
#include <QMap>
#include <functional>
#include "logos_mode.h"
class QRemoteObjectNode;
class TokenManager;
+25 -11
View File
@@ -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 <QRemoteObjectRegistryHost>
#include <QDebug>
#include <QUrl>
@@ -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;
+3
View File
@@ -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<QString, QString> m_tokens;
ModuleProxy* m_moduleProxy;
QString m_registeredObjectName;
};
+74
View File
@@ -0,0 +1,74 @@
#ifndef LOGOS_MODE_H
#define LOGOS_MODE_H
#include <QDebug>
/**
* @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
+6
View File
@@ -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;
+130
View File
@@ -0,0 +1,130 @@
#ifndef PLUGIN_REGISTRY_H
#define PLUGIN_REGISTRY_H
#include <QMap>
#include <QString>
#include <QObject>
#include <QCoreApplication>
#include <QVariant>
#include <QDebug>
#include <QMutex>
#include <QMutexLocker>
/**
* @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(&registryMutex());
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(&registryMutex());
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<typename T>
inline T* getPlugin(const QString& name) {
if (name.isEmpty()) {
qWarning() << "PluginRegistry: Cannot get plugin with empty name";
return nullptr;
}
QMutexLocker locker(&registryMutex());
QString pluginKey = toPluginKey(name);
QVariant pluginVariant = QCoreApplication::instance()->property(pluginKey.toUtf8().constData());
if (pluginVariant.isValid()) {
QObject* obj = pluginVariant.value<QObject*>();
if (obj) {
T* result = qobject_cast<T*>(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(&registryMutex());
QString pluginKey = toPluginKey(name);
QVariant pluginVariant = QCoreApplication::instance()->property(pluginKey.toUtf8().constData());
return pluginVariant.isValid() && pluginVariant.value<QObject*>() != nullptr;
}
}
#endif // PLUGIN_REGISTRY_H
+5 -1
View File
@@ -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
'';
}