diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp index 4f7ac8f..de1899c 100644 --- a/cpp-generator/main.cpp +++ b/cpp-generator/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include static QJsonArray enumerateMethods(QObject* moduleInstance) @@ -741,6 +742,202 @@ static bool writeUmbrellaSourceFromDeps(const QString& genDirPath, const QJsonAr return true; } +// ── Provider-header mode: scan LOGOS_METHOD markers and generate dispatch ──── + +struct ParsedMethod { + QString returnType; + QString name; + QVector> params; // (type, name) +}; + +static QVector parseProviderHeader(const QString& headerPath, QTextStream& err) +{ + QVector methods; + + QFile file(headerPath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << "Cannot open header file: " << headerPath << "\n"; + return methods; + } + + QTextStream in(&file); + QRegularExpression re( + R"(^\s*LOGOS_METHOD\s+(.+?)\s+(\w+)\s*\(([^)]*)\)\s*;)" + ); + + while (!in.atEnd()) { + QString line = in.readLine(); + auto match = re.match(line); + if (!match.hasMatch()) continue; + + ParsedMethod m; + m.returnType = normalizeType(match.captured(1)); + m.name = match.captured(2); + + QString paramStr = match.captured(3).trimmed(); + if (!paramStr.isEmpty()) { + QStringList paramParts = paramStr.split(','); + for (const QString& part : paramParts) { + QString trimmed = part.trimmed(); + int lastSpace = trimmed.lastIndexOf(' '); + int lastAmp = trimmed.lastIndexOf('&'); + int splitAt = qMax(lastSpace, lastAmp); + if (splitAt > 0) { + QString type = normalizeType(trimmed.left(splitAt + 1)); + QString pname = trimmed.mid(splitAt + 1).trimmed(); + m.params.append({type, pname}); + } else { + m.params.append({normalizeType(trimmed), QString("arg%1").arg(m.params.size())}); + } + } + } + + methods.append(m); + } + + file.close(); + return methods; +} + +static QString toQVariantConversion(const QString& type, const QString& argExpr) +{ + if (type == "int") return argExpr + ".toInt()"; + if (type == "bool") return argExpr + ".toBool()"; + if (type == "double") return argExpr + ".toDouble()"; + if (type == "float") return argExpr + ".toFloat()"; + if (type == "QString") return argExpr + ".toString()"; + if (type == "QStringList") return argExpr + ".toStringList()"; + if (type == "QJsonArray") return "qvariant_cast(" + argExpr + ")"; + if (type == "QVariant") return argExpr; + if (type == "LogosResult") return argExpr + ".value()"; + return argExpr + ".toString()"; +} + +static int generateProviderDispatch(const QString& headerPath, const QString& outputDir, QTextStream& out, QTextStream& err) +{ + QFileInfo fi(headerPath); + if (!fi.exists()) { + err << "Header file does not exist: " << headerPath << "\n"; + return 2; + } + + QVector methods = parseProviderHeader(headerPath, err); + if (methods.isEmpty()) { + err << "No LOGOS_METHOD markers found in: " << headerPath << "\n"; + return 3; + } + + // Derive the class name from the header: parse for ": public LogosProviderBase" + QString className; + { + QFile f(headerPath); + f.open(QIODevice::ReadOnly | QIODevice::Text); + QTextStream ts(&f); + QRegularExpression classRe(R"(class\s+(\w+)\s*:\s*public\s+LogosProviderBase)"); + while (!ts.atEnd()) { + QString line = ts.readLine(); + auto m = classRe.match(line); + if (m.hasMatch()) { + className = m.captured(1); + break; + } + } + f.close(); + } + + if (className.isEmpty()) { + err << "Could not find class inheriting LogosProviderBase in: " << headerPath << "\n"; + return 4; + } + + QString headerBaseName = fi.fileName(); + + QString genDirPath = outputDir.isEmpty() ? fi.absolutePath() : outputDir; + QDir().mkpath(genDirPath); + + // Generate logos_provider_dispatch.cpp + QString content; + QTextStream s(&content); + + s << "// AUTO-GENERATED by logos-cpp-generator -- do not edit\n"; + s << "#include \"" << headerBaseName << "\"\n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \n"; + s << "#include \"logos_types.h\"\n\n"; + + // callMethod() + s << "QVariant " << className << "::callMethod(const QString& methodName, const QVariantList& args)\n"; + s << "{\n"; + for (const ParsedMethod& m : methods) { + s << " if (methodName == \"" << m.name << "\") {\n"; + if (m.returnType == "void" || m.returnType.isEmpty()) { + s << " " << m.name << "("; + for (int i = 0; i < m.params.size(); ++i) { + s << toQVariantConversion(m.params[i].first, QString("args.at(%1)").arg(i)); + if (i + 1 < m.params.size()) s << ", "; + } + s << ");\n"; + s << " return QVariant(true);\n"; + } else { + s << " return QVariant::fromValue(" << m.name << "("; + for (int i = 0; i < m.params.size(); ++i) { + s << toQVariantConversion(m.params[i].first, QString("args.at(%1)").arg(i)); + if (i + 1 < m.params.size()) s << ", "; + } + s << "));\n"; + } + s << " }\n"; + } + s << " qWarning() << \"" << className << "::callMethod: unknown method:\" << methodName;\n"; + s << " return QVariant();\n"; + s << "}\n\n"; + + // getMethods() + s << "QJsonArray " << className << "::getMethods()\n"; + s << "{\n"; + s << " QJsonArray methods;\n"; + for (const ParsedMethod& m : methods) { + s << " {\n"; + s << " QJsonObject obj;\n"; + s << " obj[\"name\"] = QStringLiteral(\"" << m.name << "\");\n"; + s << " obj[\"returnType\"] = QStringLiteral(\"" << m.returnType << "\");\n"; + s << " obj[\"isInvokable\"] = true;\n"; + QString sig = m.name + "("; + for (int i = 0; i < m.params.size(); ++i) { + sig += m.params[i].first; + if (i + 1 < m.params.size()) sig += ","; + } + sig += ")"; + s << " obj[\"signature\"] = QStringLiteral(\"" << sig << "\");\n"; + if (!m.params.isEmpty()) { + s << " QJsonArray params;\n"; + for (int i = 0; i < m.params.size(); ++i) { + s << " params.append(QJsonObject{{\"type\", QStringLiteral(\"" << m.params[i].first << "\")}, {\"name\", QStringLiteral(\"" << m.params[i].second << "\")}});\n"; + } + s << " obj[\"parameters\"] = params;\n"; + } + s << " methods.append(obj);\n"; + s << " }\n"; + } + s << " return methods;\n"; + s << "}\n"; + + QString outputPath = QDir(genDirPath).filePath("logos_provider_dispatch.cpp"); + QFile outFile(outputPath); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write dispatch file: " << outputPath << "\n"; + return 5; + } + outFile.write(content.toUtf8()); + outFile.close(); + + out << "Generated provider dispatch: " << outputPath << " (" << methods.size() << " methods from " << className << ")\n"; + out.flush(); + return 0; +} + static int generateFromPlugin(const QString& pluginInputPath, const QString& outputDir, bool moduleOnly, QTextStream& out, QTextStream& err) { QFileInfo fi(pluginInputPath); @@ -989,10 +1186,25 @@ int main(int argc, char* argv[]) } } + // --provider-header mode: scan LOGOS_METHOD markers and generate dispatch code + { + const int phIdx = args.indexOf("--provider-header"); + if (phIdx != -1) { + if (phIdx + 1 >= args.size()) { + err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " --provider-header /path/to/impl.h [--output-dir /path/to/output]\n"; + return 1; + } + QString headerArg = args.at(phIdx + 1); + if (headerArg.startsWith('@')) headerArg.remove(0, 1); + return generateProviderDispatch(headerArg, outputDir, out, err); + } + } + if (args.size() < 2) { err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " /absolute/path/to/plugin [--output-dir /path/to/output] [--module-only]\n"; err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json [--output-dir /path/to/output] [--module-only] [--general-only]\n"; err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json --general-only [--output-dir /path/to/output]\n"; + err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --provider-header /path/to/impl.h [--output-dir /path/to/output]\n"; return 1; } diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 3f4bbe2..8d1bd90 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -29,6 +29,10 @@ set(SDK_SOURCES logos_instance.h plugin_registry.h logos_object.h + logos_provider_object.cpp + logos_provider_object.h + qt_provider_object.cpp + qt_provider_object.h logos_transport.h logos_transport_factory.cpp logos_transport_factory.h @@ -88,6 +92,8 @@ install(FILES logos_instance.h plugin_registry.h logos_object.h + logos_provider_object.h + qt_provider_object.h logos_transport.h logos_transport_factory.h logos_registry.h diff --git a/cpp/logos_api_client.cpp b/cpp/logos_api_client.cpp index d1a4d57..0b7adaf 100644 --- a/cpp/logos_api_client.cpp +++ b/cpp/logos_api_client.cpp @@ -2,6 +2,7 @@ #include "logos_api_consumer.h" #include "logos_object.h" #include "token_manager.h" +#include LogosAPIClient::LogosAPIClient(const QString& module_to_talk_to, const QString& origin_module, TokenManager* token_manager, QObject *parent) : QObject(parent) @@ -108,6 +109,26 @@ void LogosAPIClient::onEventResponse(LogosObject* object, const QString& eventNa object->emitEvent(eventName, data); } +void LogosAPIClient::onEventResponse(QObject* object, const QString& eventName, const QVariantList& data) +{ + qDebug() << "[LogosObject] LogosAPIClient::onEventResponse (QObject* compat)" << eventName; + + if (eventName.isEmpty()) { + qWarning() << "LogosAPIClient: Event name cannot be empty"; + return; + } + + if (!object) { + qWarning() << "LogosAPIClient: Cannot emit event on null QObject"; + return; + } + + QMetaObject::invokeMethod(object, "eventResponse", + Qt::DirectConnection, + Q_ARG(QString, eventName), + Q_ARG(QVariantList, data)); +} + bool LogosAPIClient::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) { return m_consumer->informModuleToken(authToken, moduleName, token); diff --git a/cpp/logos_api_client.h b/cpp/logos_api_client.h index 38ed61e..d89732e 100644 --- a/cpp/logos_api_client.h +++ b/cpp/logos_api_client.h @@ -75,6 +75,14 @@ public: */ void onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data); + /** + * @brief Backward-compatible overload for QObject-based plugins. + * + * Old-API plugins call onEventResponse(this, ...) where `this` is a QObject*. + * This overload invokes the eventResponse signal on the QObject via QMetaObject. + */ + void onEventResponse(QObject* object, const QString& eventName, const QVariantList& data); + bool informModuleToken(const QString& authToken, const QString& moduleName, const QString& token); bool informModuleToken_module(const QString& authToken, const QString& originModule, const QString& moduleName, const QString& token); diff --git a/cpp/logos_api_provider.cpp b/cpp/logos_api_provider.cpp index 3cb9389..883133a 100644 --- a/cpp/logos_api_provider.cpp +++ b/cpp/logos_api_provider.cpp @@ -1,19 +1,19 @@ #include "logos_api_provider.h" #include "logos_object.h" +#include "logos_provider_object.h" +#include "qt_provider_object.h" #include "module_proxy.h" #include "logos_api.h" #include "logos_instance.h" #include "logos_transport.h" #include "logos_transport_factory.h" #include -#include -#include -#include LogosAPIProvider::LogosAPIProvider(const QString& module_name, QObject *parent) : QObject(parent) , m_registryUrl(LogosInstance::id(module_name)) , m_moduleProxy(nullptr) + , m_qtProviderObject(nullptr) { m_transport = LogosTransportFactory::createHost(m_registryUrl); } @@ -25,6 +25,7 @@ LogosAPIProvider::~LogosAPIProvider() } } +// QObject* path: auto-detects LogosProviderPlugin; falls back to QtProviderObject wrapper bool LogosAPIProvider::registerObject(const QString& name, QObject* object) { if (!object) { @@ -42,31 +43,61 @@ bool LogosAPIProvider::registerObject(const QString& name, QObject* object) return false; } - qDebug() << "LogosAPIProvider: Creating ModuleProxy for" << name << "wrapping the provided object"; - - int methodIndex = object->metaObject()->indexOfMethod("initLogos(LogosAPI*)"); - if (methodIndex != -1) { - qDebug() << "LogosAPIProvider: Calling initLogos on object before wrapping"; - bool methodSuccess = QMetaObject::invokeMethod(object, "initLogos", - Qt::DirectConnection, - Q_ARG(LogosAPI*, qobject_cast(parent()))); - if (methodSuccess) { - qDebug() << "LogosAPIProvider: Successfully called initLogos on object"; - } else { - qWarning() << "LogosAPIProvider: Failed to call initLogos on object"; + // Check if this plugin implements LogosProviderPlugin (new API) + LogosProviderPlugin* providerPlugin = qobject_cast(object); + if (providerPlugin) { + qDebug() << "[LogosProviderObject] LogosAPIProvider: detected LogosProviderPlugin for" << name; + LogosProviderObject* provider = providerPlugin->createProviderObject(); + if (provider) { + return registerObject(name, provider); } - } else { - qDebug() << "LogosAPIProvider: Object does not have initLogos method, skipping"; + qWarning() << "LogosAPIProvider: createProviderObject() returned null for" << name; } - m_moduleProxy = new ModuleProxy(object, this); + // Legacy path: wrap QObject in QtProviderObject adapter + qDebug() << "[LogosProviderObject] LogosAPIProvider: wrapping QObject in QtProviderObject for" << name; + + m_qtProviderObject = new QtProviderObject(object, this); + m_qtProviderObject->init(qobject_cast(parent())); + + return publishProvider(name, m_qtProviderObject); +} + +// New path: LogosProviderObject* -> ModuleProxy -> transport +bool LogosAPIProvider::registerObject(const QString& name, LogosProviderObject* provider) +{ + if (!provider) { + qWarning() << "LogosAPIProvider: Cannot register null provider"; + return false; + } + + if (name.isEmpty()) { + qWarning() << "LogosAPIProvider: Cannot register provider with empty name"; + return false; + } + + if (m_moduleProxy) { + qCritical() << "LogosAPIProvider: Object already registered. Only one registration per provider is allowed"; + return false; + } + + qDebug() << "[LogosProviderObject] LogosAPIProvider: registering LogosProviderObject directly for" << name; + + provider->init(qobject_cast(parent())); + + return publishProvider(name, provider); +} + +bool LogosAPIProvider::publishProvider(const QString& name, LogosProviderObject* provider) +{ + m_moduleProxy = new ModuleProxy(provider, this); bool success = m_transport->publishObject(name, m_moduleProxy); if (success) { m_registeredObjectName = name; - qDebug() << "LogosAPIProvider: Successfully registered object with name:" << name; + qDebug() << "[LogosProviderObject] LogosAPIProvider: successfully published" << name; } else { - qCritical() << "LogosAPIProvider: Failed to register object with name:" << name; + qCritical() << "LogosAPIProvider: Failed to publish" << name; } return success; @@ -84,7 +115,7 @@ bool LogosAPIProvider::saveToken(const QString& from_module_name, const QString& return false; } - qDebug() << "LogosAPIProvider: Delegating saveToken call to module proxy for module:" << from_module_name; + qDebug() << "LogosAPIProvider: Delegating saveToken to module proxy for:" << from_module_name; return m_moduleProxy->saveToken(from_module_name, token); } @@ -96,7 +127,6 @@ void LogosAPIProvider::onEventResponse(LogosObject* object, const QString& event qWarning() << "LogosAPIProvider: Event name cannot be empty"; return; } - if (!object) { qWarning() << "LogosAPIProvider: Cannot emit event on null object"; return; diff --git a/cpp/logos_api_provider.h b/cpp/logos_api_provider.h index 863bdb4..c04ce5c 100644 --- a/cpp/logos_api_provider.h +++ b/cpp/logos_api_provider.h @@ -11,14 +11,16 @@ class LogosTransportHost; class LogosObject; class ModuleProxy; +class LogosProviderObject; +class QtProviderObject; /** * @brief LogosAPIProvider handles registering objects for access by consumers * - * This class is responsible for the provider/server side functionality: - * - Wrapping module objects with ModuleProxy - * - Publishing them via the transport layer - * - Handling event responses + * Supports two registration paths: + * 1. registerObject(name, QObject*) — wraps in QtProviderObject, then ModuleProxy + * 2. registerObject(name, LogosProviderObject*) — wraps directly in ModuleProxy + * Both paths converge at ModuleProxy -> transport. */ class LogosAPIProvider : public QObject { @@ -29,30 +31,31 @@ public: ~LogosAPIProvider(); /** - * @brief Register an object to be available for access by consumers. - * - * The provider side remains Qt-based: plugins are QObjects loaded via - * QPluginLoader. Internally this wraps them in a ModuleProxy. + * @brief Register a legacy QObject-based plugin. + * Wraps in QtProviderObject, then ModuleProxy. */ bool registerObject(const QString& name, QObject* object); + /** + * @brief Register a new-API LogosProviderObject plugin. + * Wraps directly in ModuleProxy. + */ + bool registerObject(const QString& name, LogosProviderObject* provider); + QString registryUrl() const; bool saveToken(const QString& from_module_name, const QString& token); public slots: - /** - * @brief Handle event responses from objects - * @param object The LogosObject that should receive the event - * @param eventName The name of the event - * @param data The event data - */ void onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data); private: + bool publishProvider(const QString& name, LogosProviderObject* provider); + std::unique_ptr m_transport; QString m_registryUrl; QMap m_tokens; ModuleProxy* m_moduleProxy; + QtProviderObject* m_qtProviderObject; QString m_registeredObjectName; }; diff --git a/cpp/logos_provider_object.cpp b/cpp/logos_provider_object.cpp new file mode 100644 index 0000000..d8b18b5 --- /dev/null +++ b/cpp/logos_provider_object.cpp @@ -0,0 +1,39 @@ +#include "logos_provider_object.h" +#include "logos_api.h" +#include "token_manager.h" +#include + +void LogosProviderBase::init(void* apiInstance) +{ + m_logosAPI = static_cast(apiInstance); + qDebug() << "[LogosProviderObject] LogosProviderBase::init called"; + onInit(m_logosAPI); +} + +bool LogosProviderBase::informModuleToken(const QString& moduleName, const QString& token) +{ + if (!m_logosAPI) { + qWarning() << "[LogosProviderObject] informModuleToken: LogosAPI not available"; + return false; + } + + TokenManager* tokenManager = m_logosAPI->getTokenManager(); + if (!tokenManager) { + qWarning() << "[LogosProviderObject] informModuleToken: TokenManager not available"; + return false; + } + + qDebug() << "[LogosProviderObject] Saving token for module:" << moduleName; + tokenManager->saveToken(moduleName, token); + return true; +} + +void LogosProviderBase::emitEvent(const QString& eventName, const QVariantList& data) +{ + if (m_eventCallback) { + qDebug() << "[LogosProviderObject] emitEvent:" << eventName; + m_eventCallback(eventName, data); + } else { + qWarning() << "[LogosProviderObject] emitEvent: no listener set for" << eventName; + } +} diff --git a/cpp/logos_provider_object.h b/cpp/logos_provider_object.h new file mode 100644 index 0000000..4a5f96f --- /dev/null +++ b/cpp/logos_provider_object.h @@ -0,0 +1,96 @@ +#ifndef LOGOS_PROVIDER_OBJECT_H +#define LOGOS_PROVIDER_OBJECT_H + +#include +#include +#include +#include +#include + +class LogosAPI; + +// --------------------------------------------------------------------------- +// LogosProviderObject — abstract provider-side interface (framework internal) +// +// This is the provider-side counterpart of LogosObject (consumer side). +// ModuleProxy wraps a LogosProviderObject* and publishes it via the transport. +// Module authors do NOT implement this directly — they inherit LogosProviderBase. +// --------------------------------------------------------------------------- +class LogosProviderObject { +public: + virtual ~LogosProviderObject() = default; + + using EventCallback = std::function; + + virtual QVariant callMethod(const QString& methodName, const QVariantList& args) = 0; + virtual bool informModuleToken(const QString& moduleName, const QString& token) = 0; + virtual QJsonArray getMethods() = 0; + virtual void setEventListener(EventCallback callback) = 0; + virtual void init(void* apiInstance) = 0; + virtual QString providerName() const = 0; + virtual QString providerVersion() const = 0; +}; + +// --------------------------------------------------------------------------- +// LogosProviderBase — convenience base class for new-API modules +// +// Handles framework plumbing so the developer only writes business logic. +// callMethod() and getMethods() are provided by generated code produced +// by logos-cpp-generator --provider-header (analogous to Qt MOC). +// --------------------------------------------------------------------------- +class LogosProviderBase : public LogosProviderObject { +public: + // These two are implemented by generated code (logos_provider_dispatch.cpp): + // QVariant callMethod(const QString& methodName, const QVariantList& args) override; + // QJsonArray getMethods() override; + + void setEventListener(EventCallback callback) override { m_eventCallback = callback; } + bool informModuleToken(const QString& moduleName, const QString& token) override; + void init(void* apiInstance) override; + +protected: + void emitEvent(const QString& eventName, const QVariantList& data); + virtual void onInit(LogosAPI* api) {} + LogosAPI* logosAPI() const { return m_logosAPI; } + +private: + EventCallback m_eventCallback; + LogosAPI* m_logosAPI = nullptr; +}; + +// --------------------------------------------------------------------------- +// LogosProviderPlugin — Qt interface for plugin loading +// +// New-API plugins implement this so the runtime can detect them via +// qobject_cast() and use createProviderObject(). +// --------------------------------------------------------------------------- +class LogosProviderPlugin { +public: + virtual ~LogosProviderPlugin() = default; + virtual LogosProviderObject* createProviderObject() = 0; +}; + +#define LogosProviderPlugin_iid "org.logos.LogosProviderPlugin" +Q_DECLARE_INTERFACE(LogosProviderPlugin, LogosProviderPlugin_iid) + +// --------------------------------------------------------------------------- +// Macros — the developer-facing API +// --------------------------------------------------------------------------- + +// LOGOS_PROVIDER: declares providerName/providerVersion and a private typedef. +// Place at the top of the class body (like Q_OBJECT). +#define LOGOS_PROVIDER(ClassName, Name, Version) \ +public: \ + QString providerName() const override { return Name; } \ + QString providerVersion() const override { return Version; } \ + QVariant callMethod(const QString& methodName, const QVariantList& args) override; \ + QJsonArray getMethods() override; \ +private: \ + using _LogosProviderThisType = ClassName; + +// LOGOS_METHOD: marks a method as callable by the framework. +// Expands to nothing — scanned by logos-cpp-generator to produce +// callMethod() dispatch and getMethods() metadata (like Q_INVOKABLE + MOC). +#define LOGOS_METHOD + +#endif // LOGOS_PROVIDER_OBJECT_H diff --git a/cpp/module_proxy.cpp b/cpp/module_proxy.cpp index 9b5b766..102b389 100644 --- a/cpp/module_proxy.cpp +++ b/cpp/module_proxy.cpp @@ -1,200 +1,24 @@ #include "module_proxy.h" +#include "logos_provider_object.h" #include -#include -#include -#include -#include -#include -#include -#include "../core/interface.h" -#include "logos_api.h" -#include "token_manager.h" -// Helper macro to simplify method invocation with return types -#define INVOKE_METHOD_WITH_RETURN(returnType, castType) \ - do { \ - castType* result = static_cast(returnValue); \ - switch (args.size()) { \ - case 0: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result)); \ - case 1: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg); \ - case 2: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg); \ - case 3: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); \ - case 4: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); \ - case 5: \ - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); \ - default: \ - qWarning() << "ModuleProxy: Currently supports 0-5 arguments. Got:" << args.size(); \ - return false; \ - } \ - } while(0) - -namespace { - class ScopedQArg { - public: - ScopedQArg(QMetaMethodArgument a, std::function d) - : arg(a), deleter(std::move(d)) {} - - ~ScopedQArg() { - if (deleter) { - deleter(arg.data); - } - } - - ScopedQArg(ScopedQArg&& other) - : arg(std::move(other.arg)), deleter(std::move(other.deleter)) { - other.deleter = nullptr; - } - ScopedQArg& operator=(ScopedQArg&&) = delete; - ScopedQArg(const ScopedQArg&) = delete; - ScopedQArg& operator=(const ScopedQArg&) = delete; - - QMetaMethodArgument arg; - - private: - std::function deleter; - }; - - auto toScopedQArgs(const QVariantList& args) - { - auto scopedArgs = std::vector{}; - for (const auto& arg : args) { - switch (arg.typeId()) { - case QMetaType::Int: { - auto value = new int{arg.toInt()}; - scopedArgs.emplace_back( - Q_ARG(int, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - case QMetaType::QStringList: { - auto value = new QStringList{arg.toStringList()}; - scopedArgs.emplace_back( - Q_ARG(QStringList, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - case QMetaType::QByteArray: { - auto value = new QByteArray{arg.toByteArray()}; - scopedArgs.emplace_back( - Q_ARG(QByteArray, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - case QMetaType::QUrl: { - auto value = new QUrl{arg.toUrl()}; - scopedArgs.emplace_back( - Q_ARG(QUrl, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - case QMetaType::Bool: { - auto value = new bool{arg.toBool()}; - scopedArgs.emplace_back( - Q_ARG(bool, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - case QMetaType::QString: - default: { - auto value = new QString{arg.toString()}; - scopedArgs.emplace_back( - Q_ARG(QString, *value), - [](const void* data) { - delete static_cast(data); - } - ); - break; - } - } - } - return scopedArgs; - } - - // Helper method to invoke methods with different return types and argument counts - bool invokeMethodByArgCount(QObject *module, const QString& methodName, const QVariantList& args, void* returnValue, const char* returnTypeName) - { - // Store the UTF-8 data to ensure it stays in scope - QByteArray methodNameBytes = methodName.toUtf8(); - const char* methodNameCStr = methodNameBytes.constData(); - - auto scopedArgs = toScopedQArgs(args); - - if (returnValue == nullptr) { - // Void method - no return value - switch (args.size()) { - case 0: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection); - case 1: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg); - case 2: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg); - case 3: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); - case 4: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); - case 5: - return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); - default: - qWarning() << "ModuleProxy: Currently supports 0-5 arguments. Got:" << args.size(); - return false; - } - } else if (strcmp(returnTypeName, "bool") == 0) { - qDebug() << "ModuleProxy: invokeMethodByArgCount - bool case with" << args.size() << "arguments"; - INVOKE_METHOD_WITH_RETURN(bool, bool); - } else if (strcmp(returnTypeName, "int") == 0) { - INVOKE_METHOD_WITH_RETURN(int, int); - } else if (strcmp(returnTypeName, "QString") == 0) { - INVOKE_METHOD_WITH_RETURN(QString, QString); - } else if (strcmp(returnTypeName, "LogosResult") == 0) { - INVOKE_METHOD_WITH_RETURN(LogosResult, LogosResult); - } else if (strcmp(returnTypeName, "QVariant") == 0) { - INVOKE_METHOD_WITH_RETURN(QVariant, QVariant); - } else if (strcmp(returnTypeName, "QJsonArray") == 0) { - INVOKE_METHOD_WITH_RETURN(QJsonArray, QJsonArray); - } else if (strcmp(returnTypeName, "QStringList") == 0) { - INVOKE_METHOD_WITH_RETURN(QStringList, QStringList); - } else { - qWarning() << "ModuleProxy: Unsupported return type in invokeMethodByArgCount:" << returnTypeName; - return false; - } - } -} - -ModuleProxy::ModuleProxy(QObject* module, QObject* parent) +ModuleProxy::ModuleProxy(LogosProviderObject* provider, QObject* parent) : QObject(parent) - , m_module(module) + , m_provider(provider) { - // Connect to the wrapped object's eventResponse signal to forward events - if (m_module) { - QObject::connect(m_module, SIGNAL(eventResponse(QString, QVariantList)), - this, SIGNAL(eventResponse(QString, QVariantList))); - qDebug() << "ModuleProxy: Connected to wrapped object's eventResponse signal"; + if (m_provider) { + m_provider->setEventListener([this](const QString& eventName, const QVariantList& data) { + qDebug() << "[LogosProviderObject] ModuleProxy: forwarding event" << eventName << "as Qt signal"; + emit eventResponse(eventName, data); + }); + qDebug() << "[LogosProviderObject] ModuleProxy: created, wrapping LogosProviderObject" + << m_provider->providerName(); } } ModuleProxy::~ModuleProxy() { - qDebug() << "ModuleProxy: Destroyed for module:" << m_module; + qDebug() << "ModuleProxy: destroyed"; } bool ModuleProxy::saveToken(const QString& from_module_name, const QString& token) @@ -203,23 +27,20 @@ bool ModuleProxy::saveToken(const QString& from_module_name, const QString& toke qWarning() << "ModuleProxy: Cannot save token with empty module name"; return false; } - if (token.isEmpty()) { qWarning() << "ModuleProxy: Cannot save empty token for module:" << from_module_name; return false; } - qDebug() << "ModuleProxy: Saving token for module:" << from_module_name; m_tokens[from_module_name] = token; - - qDebug() << "ModuleProxy: Token saved successfully. Total tokens stored:" << m_tokens.size(); + qDebug() << "ModuleProxy: Token saved for module:" << from_module_name; return true; } QVariant ModuleProxy::callRemoteMethod(const QString& authToken, const QString& methodName, const QVariantList& args) { - if (!m_module) { - qWarning() << "ModuleProxy: Cannot call method on null module:" << methodName; + if (!m_provider) { + qWarning() << "ModuleProxy: Cannot call method on null provider:" << methodName; return QVariant(); } @@ -228,259 +49,32 @@ 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; - - - PluginInterface* pluginInterface = qobject_cast(m_module); - if (!pluginInterface) { - qWarning() << "ModuleProxy: Module is not a PluginInterface"; - return false; - } - - // now print the name - qDebug() << "ModuleProxy: PluginInterface name:" << pluginInterface->name(); - - // get Logos API - LogosAPI* logosAPI = pluginInterface->logosAPI; - if (!logosAPI) { - qWarning() << "ModuleProxy: LogosAPI not available"; - return false; - } - - // get TokenManager - TokenManager* tokenManager = logosAPI->getTokenManager(); - if (!tokenManager) { - qWarning() << "ModuleProxy: TokenManager not available"; - return false; - } - - // print keys vand values for debug purposes - QList keys = tokenManager->getTokenKeys(); - for (const QString& key : keys) { - qDebug() << "ModuleProxy: Token key:" << key; - } - - // check if authToken is valid - if (authToken.isEmpty()) { - qWarning() << "ModuleProxy: Auth token is empty"; - return QVariant(); - } - - // check if authToken is stored in tokenManager - if (!tokenManager->getToken(authToken).isEmpty()) { - qDebug() << "ERROR: ===================== getToken(authToken) is INVALID ====================="; - qWarning() << "ModuleProxy: Auth token not found in stored tokens"; - qDebug() << "ERROR: ===================== getToken(authToken) is INVALID ====================="; - - return QVariant(); - } else { - qDebug() << "VALID: ===================== getToken(authToken) is VALID ====================="; - } - - // Each createArgument() call now generates its own unique GUID - - // Find the method to get its return type - const QMetaObject* metaObject = m_module->metaObject(); - int methodIndex = -1; - - qDebug() << "ModuleProxy: Looking for method" << methodName << "with" << args.size() << "arguments"; - qDebug() << "ModuleProxy: Available methods in" << metaObject->className() << ":"; - - // Debug: List all available methods - for (int i = 0; i < metaObject->methodCount(); ++i) { - QMetaMethod method = metaObject->method(i); - qDebug() << " Method" << i << ":" << method.name() << "with" << method.parameterCount() << "parameters, return type:" << method.returnMetaType().name(); - } - - // Find the method with matching name and argument count - for (int i = 0; i < metaObject->methodCount(); ++i) { - QMetaMethod method = metaObject->method(i); - if (method.name() == methodName && method.parameterCount() == args.size()) { - methodIndex = i; - qDebug() << "ModuleProxy: Found matching method at index" << i; - break; - } - } - - if (methodIndex == -1) { - qWarning() << "ModuleProxy: Method not found:" << methodName << "with" << args.size() << "arguments"; - return QVariant(); - } - - QMetaMethod method = metaObject->method(methodIndex); - QMetaType returnType = method.returnMetaType(); - - qDebug() << "ModuleProxy: Method signature:" << method.methodSignature(); - qDebug() << "ModuleProxy: Parameter types:"; - for (int i = 0; i < method.parameterCount(); ++i) { - qDebug() << " Param" << i << ":" << method.parameterMetaType(i).name(); - } - - // Handle different return types - bool success = false; - QVariant result; - - if (returnType == QMetaType::fromType()) { - // Void method - no return value expected - success = invokeMethodByArgCount(m_module, methodName, args, nullptr, nullptr); - if (success) { - result = QVariant(true); // Return true to indicate success - } - } else if (returnType == QMetaType::fromType()) { - // Bool return type - qDebug() << "ModuleProxy: Invoking bool method" << methodName; - bool boolResult = false; - success = invokeMethodByArgCount(m_module, methodName, args, &boolResult, "bool"); - qDebug() << "ModuleProxy: Bool method invocation result:" << success << "value:" << boolResult; - if (success) { - result = QVariant(boolResult); - } - } else if (returnType == QMetaType::fromType()) { - // Int return type - int intResult = 0; - success = invokeMethodByArgCount(m_module, methodName, args, &intResult, "int"); - if (success) { - result = QVariant(intResult); - } - } else if (returnType == QMetaType::fromType()) { - // QString return type - QString stringResult; - success = invokeMethodByArgCount(m_module, methodName, args, &stringResult, "QString"); - if (success) { - result = QVariant(stringResult); - } - } - else if (returnType == QMetaType::fromType()) { - // LogosResult return type - LogosResult logosResult; - success = invokeMethodByArgCount(m_module, methodName, args, &logosResult, "LogosResult"); - if (success) { - result = QVariant::fromValue(logosResult); - } - } - else if (returnType == QMetaType::fromType()) { - // QVariant return type - QVariant variantResult; - success = invokeMethodByArgCount(m_module, methodName, args, &variantResult, "QVariant"); - if (success) { - result = variantResult; - } - } else if (returnType == QMetaType::fromType()) { - // QJsonArray return type - qDebug() << "ModuleProxy: Invoking QJsonArray method" << methodName; - QJsonArray jsonArrayResult; - success = invokeMethodByArgCount(m_module, methodName, args, &jsonArrayResult, "QJsonArray"); - qDebug() << "ModuleProxy: QJsonArray method invocation result:" << success << "array size:" << jsonArrayResult.size(); - if (success) { - result = QVariant(jsonArrayResult); - } - } else if (returnType == QMetaType::fromType()) { - // QStringList return type - qDebug() << "ModuleProxy: Invoking QStringList method" << methodName; - QStringList stringListResult; - success = invokeMethodByArgCount(m_module, methodName, args, &stringListResult, "QStringList"); - qDebug() << "ModuleProxy: QStringList method invocation result:" << success << "list size:" << stringListResult.size(); - if (success) { - result = QVariant(stringListResult); - } - } else { - qWarning() << "ModuleProxy: Unsupported return type:" << returnType.name() << "for method:" << methodName; - return QVariant(); - } - - if (!success) { - qWarning() << "ModuleProxy: Failed to invoke method" << methodName << "on module" << m_module; - return QVariant(); - } - - // Note: Argument cleanup is now handled automatically by each createArgument() call's unique GUID - qDebug() << "ModuleProxy: Successfully called method" << methodName << "on module" << m_module; - return result; + qDebug() << "ModuleProxy: callRemoteMethod" << methodName << "args:" << args; + return m_provider->callMethod(methodName, args); } bool ModuleProxy::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) { - Q_UNUSED(authToken) // Authentication token validation can be added later + Q_UNUSED(authToken) - // cast m_module to PluginInterface - PluginInterface* pluginInterface = qobject_cast(m_module); - if (!pluginInterface) { - qWarning() << "ModuleProxy: Module is not a PluginInterface"; + if (!m_provider) { + qWarning() << "ModuleProxy: Cannot inform token on null provider"; return false; } - // now print the name - qDebug() << "ModuleProxy: PluginInterface name:" << pluginInterface->name(); - - // get Logos API - LogosAPI* logosAPI = pluginInterface->logosAPI; - if (!logosAPI) { - qWarning() << "ModuleProxy: LogosAPI not available"; - return false; - } - - // get TokenManager - TokenManager* tokenManager = logosAPI->getTokenManager(); - if (!tokenManager) { - qWarning() << "ModuleProxy: TokenManager not available"; - return false; - } - - // save token - qDebug() << "ModuleProxy: Saving token for module:" << moduleName << "with token:" << token; - tokenManager->saveToken(moduleName, token); - qDebug() << "ModuleProxy: Token saved successfully"; - - return true; + return m_provider->informModuleToken(moduleName, token); } QJsonArray ModuleProxy::getPluginMethods() { - QJsonArray methodsArray; + if (!m_provider) return QJsonArray(); - const QMetaObject* metaObject = m_module->metaObject(); - - for (int i = 0; i < metaObject->methodCount(); ++i) { - QMetaMethod method = metaObject->method(i); - - if (method.enclosingMetaObject() != metaObject) { - continue; - } - - QJsonObject methodObj; - methodObj["signature"] = QString::fromUtf8(method.methodSignature()); - methodObj["name"] = QString::fromUtf8(method.name()); - methodObj["returnType"] = QString::fromUtf8(method.typeName()); - methodObj["isInvokable"] = method.isValid() && (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot); - - if (method.parameterCount() > 0) { - QJsonArray params; - for (int p = 0; p < method.parameterCount(); ++p) { - QJsonObject paramObj; - paramObj["type"] = QString::fromUtf8(method.parameterTypeName(p)); - QByteArrayList paramNames = method.parameterNames(); - if (p < paramNames.size() && !paramNames.at(p).isEmpty()) { - paramObj["name"] = QString::fromUtf8(paramNames.at(p)); - } else { - paramObj["name"] = QString("param%1").arg(p); - } - params.append(paramObj); - } - methodObj["parameters"] = params; - } - - methodsArray.append(methodObj); - } - - return methodsArray; + qDebug() << "[LogosProviderObject] ModuleProxy: calling LogosProviderObject::getMethods()"; + return m_provider->getMethods(); } -// Include MOC for template instantiation #include "moc_module_proxy.cpp" diff --git a/cpp/module_proxy.h b/cpp/module_proxy.h index 9b9af65..dcae013 100644 --- a/cpp/module_proxy.h +++ b/cpp/module_proxy.h @@ -4,74 +4,39 @@ #include #include #include -#include #include -#include #include #include +class LogosProviderObject; + /** - * @brief ModuleProxy provides a proxy interface for module interactions + * @brief ModuleProxy wraps a LogosProviderObject and exposes it as a QObject + * so that Qt Remote Objects can publish it. * - * This class serves as a proxy layer for communicating with modules - * in the Logos Core system. + * All method dispatch, introspection, and event forwarding is delegated + * to the underlying LogosProviderObject*. For legacy QObject-based plugins, + * that provider is a QtProviderObject adapter; for new-API plugins it is + * the plugin's own LogosProviderObject subclass. */ class ModuleProxy : public QObject { Q_OBJECT public: - - /** - * @brief Construct a new ModuleProxy with authentication token - * @param module The module object to proxy - * @param authToken Authentication token for the module - * @param parent Parent QObject - */ - explicit ModuleProxy(QObject* module, QObject* parent = nullptr); - - /** - * @brief Destructor - */ + explicit ModuleProxy(LogosProviderObject* provider, QObject* parent = nullptr); ~ModuleProxy(); - /** - * @brief Call a method on the proxied module - * @param authToken Authentication token for the method call - * @param methodName The name of the method to call - * @param args Arguments to pass to the method - * @return QVariant containing the result, or invalid QVariant if failed - */ Q_INVOKABLE QVariant callRemoteMethod(const QString& authToken, const QString& methodName, const QVariantList& args = QVariantList()); - - /** - * @brief Inform module of a token - * @param authToken Authentication token for the operation - * @param moduleName The name of the module - * @param token The token to inform the module about - * @return bool true if successful, false otherwise - */ Q_INVOKABLE bool informModuleToken(const QString& authToken, const QString& moduleName, const QString& token); - - /** - * @brief Save a token from a module - * @param from_module_name The name of the module providing the token - * @param token The token to save - * @return bool true if token was saved successfully, false otherwise - */ bool saveToken(const QString& from_module_name, const QString& token); - - /** - * @brief Get a list of methods for the encapsulated module - * @return QJsonArray of method metadata (name, signature, returnType, parameters) - */ Q_INVOKABLE QJsonArray getPluginMethods(); signals: void eventResponse(const QString& eventName, const QVariantList& data); private: - QObject* m_module; + LogosProviderObject* m_provider; QHash m_tokens; }; diff --git a/cpp/qt_provider_object.cpp b/cpp/qt_provider_object.cpp new file mode 100644 index 0000000..37f7b70 --- /dev/null +++ b/cpp/qt_provider_object.cpp @@ -0,0 +1,387 @@ +#include "qt_provider_object.h" +#include "../core/interface.h" +#include "logos_api.h" +#include "token_manager.h" +#include "logos_types.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// ── QMetaObject dispatch helpers (moved from module_proxy.cpp) ────────────── + +#define INVOKE_METHOD_WITH_RETURN(returnType, castType) \ + do { \ + castType* result = static_cast(returnValue); \ + switch (args.size()) { \ + case 0: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result)); \ + case 1: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg); \ + case 2: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg); \ + case 3: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); \ + case 4: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); \ + case 5: \ + return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); \ + default: \ + qWarning() << "QtProviderObject: Currently supports 0-5 arguments. Got:" << args.size(); \ + return false; \ + } \ + } while(0) + +namespace { + class ScopedQArg { + public: + ScopedQArg(QMetaMethodArgument a, std::function d) + : arg(a), deleter(std::move(d)) {} + + ~ScopedQArg() { + if (deleter) { + deleter(arg.data); + } + } + + ScopedQArg(ScopedQArg&& other) + : arg(std::move(other.arg)), deleter(std::move(other.deleter)) { + other.deleter = nullptr; + } + ScopedQArg& operator=(ScopedQArg&&) = delete; + ScopedQArg(const ScopedQArg&) = delete; + ScopedQArg& operator=(const ScopedQArg&) = delete; + + QMetaMethodArgument arg; + + private: + std::function deleter; + }; + + auto toScopedQArgs(const QVariantList& args) + { + auto scopedArgs = std::vector{}; + for (const auto& arg : args) { + switch (arg.typeId()) { + case QMetaType::Int: { + auto value = new int{arg.toInt()}; + scopedArgs.emplace_back( + Q_ARG(int, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + case QMetaType::QStringList: { + auto value = new QStringList{arg.toStringList()}; + scopedArgs.emplace_back( + Q_ARG(QStringList, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + case QMetaType::QByteArray: { + auto value = new QByteArray{arg.toByteArray()}; + scopedArgs.emplace_back( + Q_ARG(QByteArray, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + case QMetaType::QUrl: { + auto value = new QUrl{arg.toUrl()}; + scopedArgs.emplace_back( + Q_ARG(QUrl, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + case QMetaType::Bool: { + auto value = new bool{arg.toBool()}; + scopedArgs.emplace_back( + Q_ARG(bool, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + case QMetaType::QString: + default: { + auto value = new QString{arg.toString()}; + scopedArgs.emplace_back( + Q_ARG(QString, *value), + [](const void* data) { delete static_cast(data); } + ); + break; + } + } + } + return scopedArgs; + } + + bool invokeMethodByArgCount(QObject *module, const QString& methodName, const QVariantList& args, void* returnValue, const char* returnTypeName) + { + QByteArray methodNameBytes = methodName.toUtf8(); + const char* methodNameCStr = methodNameBytes.constData(); + + auto scopedArgs = toScopedQArgs(args); + + if (returnValue == nullptr) { + switch (args.size()) { + case 0: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection); + case 1: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg); + case 2: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg); + case 3: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); + case 4: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); + case 5: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); + default: + qWarning() << "QtProviderObject: Currently supports 0-5 arguments. Got:" << args.size(); + return false; + } + } else if (strcmp(returnTypeName, "bool") == 0) { + INVOKE_METHOD_WITH_RETURN(bool, bool); + } else if (strcmp(returnTypeName, "int") == 0) { + INVOKE_METHOD_WITH_RETURN(int, int); + } else if (strcmp(returnTypeName, "QString") == 0) { + INVOKE_METHOD_WITH_RETURN(QString, QString); + } else if (strcmp(returnTypeName, "LogosResult") == 0) { + INVOKE_METHOD_WITH_RETURN(LogosResult, LogosResult); + } else if (strcmp(returnTypeName, "QVariant") == 0) { + INVOKE_METHOD_WITH_RETURN(QVariant, QVariant); + } else if (strcmp(returnTypeName, "QJsonArray") == 0) { + INVOKE_METHOD_WITH_RETURN(QJsonArray, QJsonArray); + } else if (strcmp(returnTypeName, "QStringList") == 0) { + INVOKE_METHOD_WITH_RETURN(QStringList, QStringList); + } else { + qWarning() << "QtProviderObject: Unsupported return type:" << returnTypeName; + return false; + } + } +} + +// ── QtProviderObject implementation ───────────────────────────────────────── + +QtProviderObject::QtProviderObject(QObject* module, QObject* parent) + : QObject(parent) + , m_module(module) +{ + if (m_module) { + connect(m_module, SIGNAL(eventResponse(QString, QVariantList)), + this, SLOT(onWrappedEventResponse(QString, QVariantList))); + qDebug() << "[LogosProviderObject] QtProviderObject: connected to QObject eventResponse signal"; + } +} + +QtProviderObject::~QtProviderObject() +{ + qDebug() << "[LogosProviderObject] QtProviderObject: destroyed"; +} + +void QtProviderObject::onWrappedEventResponse(const QString& eventName, const QVariantList& data) +{ + if (m_eventCallback) { + m_eventCallback(eventName, data); + } +} + +void QtProviderObject::init(void* apiInstance) +{ + if (!m_module) return; + + LogosAPI* api = static_cast(apiInstance); + + int methodIndex = m_module->metaObject()->indexOfMethod("initLogos(LogosAPI*)"); + if (methodIndex != -1) { + qDebug() << "[LogosProviderObject] QtProviderObject: calling initLogos on wrapped QObject"; + QMetaObject::invokeMethod(m_module, "initLogos", + Qt::DirectConnection, + Q_ARG(LogosAPI*, api)); + } else { + qDebug() << "[LogosProviderObject] QtProviderObject: wrapped QObject has no initLogos, skipping"; + } +} + +QString QtProviderObject::providerName() const +{ + PluginInterface* pi = qobject_cast(m_module); + return pi ? pi->name() : QString(); +} + +QString QtProviderObject::providerVersion() const +{ + PluginInterface* pi = qobject_cast(m_module); + return pi ? pi->version() : QString(); +} + +void QtProviderObject::setEventListener(EventCallback callback) +{ + m_eventCallback = std::move(callback); +} + +QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantList& args) +{ + if (!m_module) { + qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: null module"; + return QVariant(); + } + + if (methodName.isEmpty()) { + qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: empty method name"; + return QVariant(); + } + + // Special-case getPluginMethods (framework-level, not on the wrapped plugin) + if (methodName == "getPluginMethods" && args.isEmpty()) { + return QVariant(getMethods()); + } + + // Auth-token validation (mirrors the old ModuleProxy logic) + PluginInterface* pluginInterface = qobject_cast(m_module); + if (!pluginInterface) { + qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: module is not a PluginInterface"; + return QVariant(); + } + + LogosAPI* api = pluginInterface->logosAPI; + if (!api) { + qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: LogosAPI not available"; + return QVariant(); + } + + // Find method via QMetaObject + const QMetaObject* metaObject = m_module->metaObject(); + int methodIndex = -1; + for (int i = 0; i < metaObject->methodCount(); ++i) { + QMetaMethod method = metaObject->method(i); + if (method.name() == methodName && method.parameterCount() == args.size()) { + methodIndex = i; + break; + } + } + + if (methodIndex == -1) { + qWarning() << "[LogosProviderObject] QtProviderObject: method not found:" << methodName + << "with" << args.size() << "arguments"; + return QVariant(); + } + + QMetaMethod method = metaObject->method(methodIndex); + QMetaType returnType = method.returnMetaType(); + + bool success = false; + QVariant result; + + if (returnType == QMetaType::fromType()) { + success = invokeMethodByArgCount(m_module, methodName, args, nullptr, nullptr); + if (success) result = QVariant(true); + } else if (returnType == QMetaType::fromType()) { + bool v = false; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "bool"); + if (success) result = QVariant(v); + } else if (returnType == QMetaType::fromType()) { + int v = 0; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "int"); + if (success) result = QVariant(v); + } else if (returnType == QMetaType::fromType()) { + QString v; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "QString"); + if (success) result = QVariant(v); + } else if (returnType == QMetaType::fromType()) { + LogosResult v; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "LogosResult"); + if (success) result = QVariant::fromValue(v); + } else if (returnType == QMetaType::fromType()) { + QVariant v; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "QVariant"); + if (success) result = v; + } else if (returnType == QMetaType::fromType()) { + QJsonArray v; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "QJsonArray"); + if (success) result = QVariant(v); + } else if (returnType == QMetaType::fromType()) { + QStringList v; + success = invokeMethodByArgCount(m_module, methodName, args, &v, "QStringList"); + if (success) result = QVariant(v); + } else { + qWarning() << "[LogosProviderObject] QtProviderObject: unsupported return type:" + << returnType.name() << "for method:" << methodName; + return QVariant(); + } + + if (!success) { + qWarning() << "[LogosProviderObject] QtProviderObject: failed to invoke" << methodName; + } + return result; +} + +bool QtProviderObject::informModuleToken(const QString& moduleName, const QString& token) +{ + PluginInterface* pluginInterface = qobject_cast(m_module); + if (!pluginInterface) { + qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: not a PluginInterface"; + return false; + } + + LogosAPI* api = pluginInterface->logosAPI; + if (!api) { + qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: LogosAPI not available"; + return false; + } + + TokenManager* tokenManager = api->getTokenManager(); + if (!tokenManager) { + qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: TokenManager not available"; + return false; + } + + qDebug() << "[LogosProviderObject] QtProviderObject: saving token for module:" << moduleName; + tokenManager->saveToken(moduleName, token); + return true; +} + +QJsonArray QtProviderObject::getMethods() +{ + if (!m_module) return QJsonArray(); + + QJsonArray methodsArray; + const QMetaObject* metaObject = m_module->metaObject(); + + for (int i = 0; i < metaObject->methodCount(); ++i) { + QMetaMethod method = metaObject->method(i); + + if (method.enclosingMetaObject() != metaObject) { + continue; + } + + QJsonObject methodObj; + methodObj["signature"] = QString::fromUtf8(method.methodSignature()); + methodObj["name"] = QString::fromUtf8(method.name()); + methodObj["returnType"] = QString::fromUtf8(method.typeName()); + methodObj["isInvokable"] = method.isValid() && + (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot); + + if (method.parameterCount() > 0) { + QJsonArray params; + for (int p = 0; p < method.parameterCount(); ++p) { + QJsonObject paramObj; + paramObj["type"] = QString::fromUtf8(method.parameterTypeName(p)); + QByteArrayList paramNames = method.parameterNames(); + if (p < paramNames.size() && !paramNames.at(p).isEmpty()) { + paramObj["name"] = QString::fromUtf8(paramNames.at(p)); + } else { + paramObj["name"] = QString("param%1").arg(p); + } + params.append(paramObj); + } + methodObj["parameters"] = params; + } + + methodsArray.append(methodObj); + } + + return methodsArray; +} + +#include "moc_qt_provider_object.cpp" diff --git a/cpp/qt_provider_object.h b/cpp/qt_provider_object.h new file mode 100644 index 0000000..ab0574a --- /dev/null +++ b/cpp/qt_provider_object.h @@ -0,0 +1,40 @@ +#ifndef QT_PROVIDER_OBJECT_H +#define QT_PROVIDER_OBJECT_H + +#include "logos_provider_object.h" +#include + +class PluginInterface; + +/** + * @brief Adapter that wraps an existing QObject-based plugin as a LogosProviderObject. + * + * This allows legacy plugins (using Q_INVOKABLE / Qt signals) to work through + * the new LogosProviderObject interface without any changes to the plugin code. + * All dispatch goes through QMetaObject — the same path that ModuleProxy used + * to handle directly. + */ +class QtProviderObject : public QObject, public LogosProviderObject { + Q_OBJECT + +public: + explicit QtProviderObject(QObject* module, QObject* parent = nullptr); + ~QtProviderObject() override; + + QVariant callMethod(const QString& methodName, const QVariantList& args) override; + bool informModuleToken(const QString& moduleName, const QString& token) override; + QJsonArray getMethods() override; + void setEventListener(EventCallback callback) override; + void init(void* apiInstance) override; + QString providerName() const override; + QString providerVersion() const override; + +private slots: + void onWrappedEventResponse(const QString& eventName, const QVariantList& data); + +private: + QObject* m_module; + EventCallback m_eventCallback; +}; + +#endif // QT_PROVIDER_OBJECT_H diff --git a/nix/include.nix b/nix/include.nix index a03638d..91dd285 100644 --- a/nix/include.nix +++ b/nix/include.nix @@ -32,6 +32,8 @@ pkgs.stdenv.mkDerivation { 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 logos_mode.h \ logos_instance.h logos_object.h \ + logos_provider_object.h logos_provider_object.cpp \ + qt_provider_object.h qt_provider_object.cpp \ logos_transport.h logos_transport_factory.h logos_transport_factory.cpp \ logos_registry.h logos_registry_factory.h logos_registry_factory.cpp \ plugin_registry.h; do