From 4197ee183041c33e90d8f90e80d810e5b7ed04bf Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Mon, 23 Mar 2026 11:45:25 -0400 Subject: [PATCH] Finish Abstraction & Refactor (Ongoing) - part 1 (#25) * refactor: abstract connection/transport; and clearly separate qt remote obj and qt local into separate implementations * abstract qt remote registry * add mock implementation; these serves to further test the abstraction but also useful for testing modules later * use LogosObject instead of QObject * abstract provider side * updates to use new api * re-add async api back --------- Co-authored-by: Logos Workspace --- cpp-generator/CMakeLists.txt | 1 + cpp-generator/main.cpp | 327 +++++++++++-- cpp/CMakeLists.txt | 56 ++- cpp/implementations/mock/logos_mock.h | 106 ++++ cpp/implementations/mock/mock_registry.h | 17 + cpp/implementations/mock/mock_store.cpp | 125 +++++ cpp/implementations/mock/mock_store.h | 109 +++++ cpp/implementations/mock/mock_transport.cpp | 39 ++ cpp/implementations/mock/mock_transport.h | 79 +++ .../qt_local/local_transport.cpp | 171 +++++++ .../qt_local/local_transport.h | 23 + .../qt_remote/qt_remote_registry.cpp | 28 ++ .../qt_remote/qt_remote_registry.h | 27 ++ .../qt_remote/remote_transport.cpp | 311 ++++++++++++ .../qt_remote/remote_transport.h | 42 ++ cpp/logos_api_client.cpp | 135 +++--- cpp/logos_api_client.h | 226 ++------- cpp/logos_api_consumer.cpp | 392 ++------------- cpp/logos_api_consumer.h | 114 +---- cpp/logos_api_provider.cpp | 129 ++--- cpp/logos_api_provider.h | 64 +-- cpp/logos_mode.h | 17 +- cpp/logos_object.h | 99 ++++ cpp/logos_provider_object.cpp | 39 ++ cpp/logos_provider_object.h | 96 ++++ cpp/logos_registry.h | 31 ++ cpp/logos_registry_factory.cpp | 35 ++ cpp/logos_registry_factory.h | 27 ++ cpp/logos_transport.h | 77 +++ cpp/logos_transport_factory.cpp | 32 ++ cpp/logos_transport_factory.h | 28 ++ cpp/module_proxy.cpp | 452 +----------------- cpp/module_proxy.h | 55 +-- cpp/qt_provider_object.cpp | 387 +++++++++++++++ cpp/qt_provider_object.h | 40 ++ docs/docs.md | 2 +- nix/include.nix | 31 +- 37 files changed, 2652 insertions(+), 1317 deletions(-) create mode 100644 cpp/implementations/mock/logos_mock.h create mode 100644 cpp/implementations/mock/mock_registry.h create mode 100644 cpp/implementations/mock/mock_store.cpp create mode 100644 cpp/implementations/mock/mock_store.h create mode 100644 cpp/implementations/mock/mock_transport.cpp create mode 100644 cpp/implementations/mock/mock_transport.h create mode 100644 cpp/implementations/qt_local/local_transport.cpp create mode 100644 cpp/implementations/qt_local/local_transport.h create mode 100644 cpp/implementations/qt_remote/qt_remote_registry.cpp create mode 100644 cpp/implementations/qt_remote/qt_remote_registry.h create mode 100644 cpp/implementations/qt_remote/remote_transport.cpp create mode 100644 cpp/implementations/qt_remote/remote_transport.h create mode 100644 cpp/logos_object.h create mode 100644 cpp/logos_provider_object.cpp create mode 100644 cpp/logos_provider_object.h create mode 100644 cpp/logos_registry.h create mode 100644 cpp/logos_registry_factory.cpp create mode 100644 cpp/logos_registry_factory.h create mode 100644 cpp/logos_transport.h create mode 100644 cpp/logos_transport_factory.cpp create mode 100644 cpp/logos_transport_factory.h create mode 100644 cpp/qt_provider_object.cpp create mode 100644 cpp/qt_provider_object.h diff --git a/cpp-generator/CMakeLists.txt b/cpp-generator/CMakeLists.txt index 0bac737..c80c650 100644 --- a/cpp-generator/CMakeLists.txt +++ b/cpp-generator/CMakeLists.txt @@ -17,6 +17,7 @@ target_link_libraries(logos-cpp-generator PRIVATE Qt${QT_VERSION_MAJOR}::Core) target_include_directories(logos-cpp-generator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../cpp ) # Output directories diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp index 3e3b105..7738ec3 100644 --- a/cpp-generator/main.cpp +++ b/cpp-generator/main.cpp @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include "logos_provider_object.h" static QJsonArray enumerateMethods(QObject* moduleInstance) { @@ -115,13 +117,12 @@ static QString makeHeader(const QString& moduleName, const QString& className, c s << "#include \n"; s << "#include \n"; s << "#include \n"; - s << "#include \n"; - s << "#include \n"; s << "#include \n"; s << "#include \n"; s << "#include \"logos_types.h\"\n"; s << "#include \"logos_api.h\"\n"; - s << "#include \"logos_api_client.h\"\n\n"; + s << "#include \"logos_api_client.h\"\n"; + s << "#include \"logos_object.h\"\n\n"; s << "class " << className << " {\n"; s << "public:\n"; s << " explicit " << className << "(LogosAPI* api);\n\n"; @@ -129,17 +130,17 @@ static QString makeHeader(const QString& moduleName, const QString& className, c s << " using EventCallback = std::function;\n\n"; s << " bool on(const QString& eventName, RawEventCallback callback);\n"; s << " bool on(const QString& eventName, EventCallback callback);\n"; - s << " void setEventSource(QObject* source);\n"; - s << " QObject* eventSource() const;\n"; + s << " void setEventSource(LogosObject* source);\n"; + s << " LogosObject* eventSource() const;\n"; s << " void trigger(const QString& eventName);\n"; s << " void trigger(const QString& eventName, const QVariantList& data);\n"; s << " template\n"; s << " void trigger(const QString& eventName, Args&&... args) {\n"; s << " trigger(eventName, packVariantList(std::forward(args)...));\n"; s << " }\n"; - s << " void trigger(const QString& eventName, QObject* source, const QVariantList& data);\n"; + s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n"; s << " template\n"; - s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n"; + s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n"; s << " trigger(eventName, source, packVariantList(std::forward(args)...));\n"; s << " }\n\n"; // Methods @@ -181,7 +182,7 @@ static QString makeHeader(const QString& moduleName, const QString& className, c s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n"; } s << "\nprivate:\n"; - s << " QObject* ensureReplica();\n"; + s << " LogosObject* ensureReplica();\n"; s << " template\n"; s << " static QVariantList packVariantList(Args&&... args) {\n"; s << " QVariantList list;\n"; @@ -193,8 +194,8 @@ static QString makeHeader(const QString& moduleName, const QString& className, c s << " LogosAPI* m_api;\n"; s << " LogosAPIClient* m_client;\n"; s << " QString m_moduleName;\n"; - s << " QPointer m_eventReplica;\n"; - s << " QPointer m_eventSource;\n"; + s << " LogosObject* m_eventReplica = nullptr;\n"; + s << " LogosObject* m_eventSource = nullptr;\n"; s << "};\n"; return h; } @@ -206,27 +207,27 @@ static QString makeSource(const QString& moduleName, const QString& className, c s << "#include \"" << headerBaseName << "\"\n\n"; s << "#include \n\n"; s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n"; - s << "QObject* " << className << "::ensureReplica() {\n"; + s << "LogosObject* " << className << "::ensureReplica() {\n"; s << " if (!m_eventReplica) {\n"; - s << " QObject* replica = m_client->requestObject(m_moduleName);\n"; + s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n"; s << " if (!replica) {\n"; s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n"; s << " return nullptr;\n"; s << " }\n"; s << " m_eventReplica = replica;\n"; s << " }\n"; - s << " return m_eventReplica.data();\n"; + s << " return m_eventReplica;\n"; s << "}\n\n"; s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n"; s << " if (!callback) {\n"; s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n"; s << " return false;\n"; s << " }\n"; - s << " QObject* origin = ensureReplica();\n"; + s << " LogosObject* origin = ensureReplica();\n"; s << " if (!origin) {\n"; s << " return false;\n"; s << " }\n"; - s << " m_client->onEvent(origin, nullptr, eventName, callback);\n"; + s << " m_client->onEvent(origin, eventName, callback);\n"; s << " return true;\n"; s << "}\n\n"; s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n"; @@ -238,11 +239,11 @@ static QString makeSource(const QString& moduleName, const QString& className, c s << " callback(data);\n"; s << " });\n"; s << "}\n\n"; - s << "void " << className << "::setEventSource(QObject* source) {\n"; + s << "void " << className << "::setEventSource(LogosObject* source) {\n"; s << " m_eventSource = source;\n"; s << "}\n\n"; - s << "QObject* " << className << "::eventSource() const {\n"; - s << " return m_eventSource.data();\n"; + s << "LogosObject* " << className << "::eventSource() const {\n"; + s << " return m_eventSource;\n"; s << "}\n\n"; s << "void " << className << "::trigger(const QString& eventName) {\n"; s << " trigger(eventName, QVariantList{});\n"; @@ -252,9 +253,9 @@ static QString makeSource(const QString& moduleName, const QString& className, c s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n"; s << " return;\n"; s << " }\n"; - s << " m_client->onEventResponse(m_eventSource.data(), eventName, data);\n"; + s << " m_client->onEventResponse(m_eventSource, eventName, data);\n"; s << "}\n\n"; - s << "void " << className << "::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n"; + s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n"; s << " if (!source) {\n"; s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n"; s << " return;\n"; @@ -409,12 +410,11 @@ static QString makeCoreManagerHeader() s << "#include \n"; s << "#include \n"; s << "#include \n"; - s << "#include \n"; - s << "#include \n"; s << "#include \n"; s << "#include \n"; s << "#include \"logos_api.h\"\n"; - s << "#include \"logos_api_client.h\"\n\n"; + s << "#include \"logos_api_client.h\"\n"; + s << "#include \"logos_object.h\"\n\n"; s << "class CoreManager {\n"; s << "public:\n"; s << " explicit CoreManager(LogosAPI* api);\n\n"; @@ -422,17 +422,17 @@ static QString makeCoreManagerHeader() s << " using EventCallback = std::function;\n\n"; s << " bool on(const QString& eventName, RawEventCallback callback);\n"; s << " bool on(const QString& eventName, EventCallback callback);\n"; - s << " void setEventSource(QObject* source);\n"; - s << " QObject* eventSource() const;\n"; + s << " void setEventSource(LogosObject* source);\n"; + s << " LogosObject* eventSource() const;\n"; s << " void trigger(const QString& eventName);\n"; s << " void trigger(const QString& eventName, const QVariantList& data);\n"; s << " template\n"; s << " void trigger(const QString& eventName, Args&&... args) {\n"; s << " trigger(eventName, packVariantList(std::forward(args)...));\n"; s << " }\n"; - s << " void trigger(const QString& eventName, QObject* source, const QVariantList& data);\n"; + s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n"; s << " template\n"; - s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n"; + s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n"; s << " trigger(eventName, source, packVariantList(std::forward(args)...));\n"; s << " }\n\n"; s << " void initialize(int argc, char* argv[]);\n"; @@ -447,7 +447,7 @@ static QString makeCoreManagerHeader() s << " bool unloadPlugin(const QString& pluginName);\n"; s << " QString processPlugin(const QString& filePath);\n\n"; s << "private:\n"; - s << " QObject* ensureReplica();\n"; + s << " LogosObject* ensureReplica();\n"; s << " template\n"; s << " static QVariantList packVariantList(Args&&... args) {\n"; s << " QVariantList list;\n"; @@ -459,8 +459,8 @@ static QString makeCoreManagerHeader() s << " LogosAPI* m_api;\n"; s << " LogosAPIClient* m_client;\n"; s << " QString m_moduleName;\n"; - s << " QPointer m_eventReplica;\n"; - s << " QPointer m_eventSource;\n"; + s << " LogosObject* m_eventReplica = nullptr;\n"; + s << " LogosObject* m_eventSource = nullptr;\n"; s << "};\n"; return h; } @@ -473,27 +473,27 @@ static QString makeCoreManagerSource(const QString& headerBaseName) s << "#include \n"; s << "#include \n\n"; s << "CoreManager::CoreManager(LogosAPI* api) : m_api(api), m_client(api->getClient(\"core_manager\")), m_moduleName(QStringLiteral(\"core_manager\")) {}\n\n"; - s << "QObject* CoreManager::ensureReplica() {\n"; + s << "LogosObject* CoreManager::ensureReplica() {\n"; s << " if (!m_eventReplica) {\n"; - s << " QObject* replica = m_client->requestObject(m_moduleName);\n"; + s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n"; s << " if (!replica) {\n"; s << " qWarning() << \"CoreManager: failed to acquire remote object for events on\" << m_moduleName;\n"; s << " return nullptr;\n"; s << " }\n"; s << " m_eventReplica = replica;\n"; s << " }\n"; - s << " return m_eventReplica.data();\n"; + s << " return m_eventReplica;\n"; s << "}\n\n"; s << "bool CoreManager::on(const QString& eventName, RawEventCallback callback) {\n"; s << " if (!callback) {\n"; s << " qWarning() << \"CoreManager: ignoring empty event callback for\" << eventName;\n"; s << " return false;\n"; s << " }\n"; - s << " QObject* origin = ensureReplica();\n"; + s << " LogosObject* origin = ensureReplica();\n"; s << " if (!origin) {\n"; s << " return false;\n"; s << " }\n"; - s << " m_client->onEvent(origin, nullptr, eventName, callback);\n"; + s << " m_client->onEvent(origin, eventName, callback);\n"; s << " return true;\n"; s << "}\n\n"; s << "bool CoreManager::on(const QString& eventName, EventCallback callback) {\n"; @@ -505,11 +505,11 @@ static QString makeCoreManagerSource(const QString& headerBaseName) s << " callback(data);\n"; s << " });\n"; s << "}\n\n"; - s << "void CoreManager::setEventSource(QObject* source) {\n"; + s << "void CoreManager::setEventSource(LogosObject* source) {\n"; s << " m_eventSource = source;\n"; s << "}\n\n"; - s << "QObject* CoreManager::eventSource() const {\n"; - s << " return m_eventSource.data();\n"; + s << "LogosObject* CoreManager::eventSource() const {\n"; + s << " return m_eventSource;\n"; s << "}\n\n"; s << "void CoreManager::trigger(const QString& eventName) {\n"; s << " trigger(eventName, QVariantList{});\n"; @@ -519,9 +519,9 @@ static QString makeCoreManagerSource(const QString& headerBaseName) s << " qWarning() << \"CoreManager: no event source set for trigger\" << eventName;\n"; s << " return;\n"; s << " }\n"; - s << " m_client->onEventResponse(m_eventSource.data(), eventName, data);\n"; + s << " m_client->onEventResponse(m_eventSource, eventName, data);\n"; s << "}\n\n"; - s << "void CoreManager::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n"; + s << "void CoreManager::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n"; s << " if (!source) {\n"; s << " qWarning() << \"CoreManager: cannot trigger\" << eventName << \"with null source\";\n"; s << " return;\n"; @@ -743,6 +743,222 @@ 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 eqIdx = trimmed.indexOf('='); + if (eqIdx > 0) trimmed = trimmed.left(eqIdx).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() — group by name to support overloaded methods + QMap> methodsByName; + for (const ParsedMethod& m : methods) { + methodsByName[m.name].append(&m); + } + + s << "QVariant " << className << "::callMethod(const QString& methodName, const QVariantList& args)\n"; + s << "{\n"; + for (auto it = methodsByName.constBegin(); it != methodsByName.constEnd(); ++it) { + const QString& name = it.key(); + const QVector& overloads = it.value(); + s << " if (methodName == \"" << name << "\") {\n"; + bool needArgsSizeCheck = overloads.size() > 1; + for (const ParsedMethod* m : overloads) { + if (needArgsSizeCheck) { + s << " if (args.size() == " << m->params.size() << ") {\n"; + s << " "; + } + 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"; + if (needArgsSizeCheck) s << " "; + 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"; + } + if (needArgsSizeCheck) { + 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); @@ -784,7 +1000,21 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out } } - QJsonArray methods = enumerateMethods(instance); + QJsonArray methods; + LogosProviderPlugin* providerPlugin = qobject_cast(instance); + if (providerPlugin) { + LogosProviderObject* provider = providerPlugin->createProviderObject(); + if (provider) { + methods = provider->getMethods(); + out << "Detected new-API plugin (LogosProviderPlugin), using getMethods() — " + << methods.size() << " methods\n"; + delete provider; + } else { + err << "LogosProviderPlugin::createProviderObject() returned null\n"; + } + } else { + methods = enumerateMethods(instance); + } QString className = toPascalCase(moduleName); QString headerRel = QString("%1_api.h").arg(moduleName); @@ -991,10 +1221,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 e96396d..8d1bd90 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -26,7 +26,31 @@ set(SDK_SOURCES token_manager.cpp token_manager.h logos_mode.h + 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 + logos_registry.h + logos_registry_factory.h + logos_registry_factory.cpp + implementations/qt_local/local_transport.cpp + implementations/qt_local/local_transport.h + implementations/qt_remote/remote_transport.cpp + implementations/qt_remote/remote_transport.h + implementations/qt_remote/qt_remote_registry.h + implementations/qt_remote/qt_remote_registry.cpp + implementations/mock/mock_store.cpp + implementations/mock/mock_store.h + implementations/mock/mock_transport.cpp + implementations/mock/mock_transport.h + implementations/mock/mock_registry.h + implementations/mock/logos_mock.h ) # Create the SDK library as STATIC instead of SHARED @@ -38,6 +62,9 @@ target_link_libraries(logos_sdk PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSIO # Include directories target_include_directories(logos_sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/implementations/qt_local + ${CMAKE_CURRENT_SOURCE_DIR}/implementations/qt_remote + ${CMAKE_CURRENT_SOURCE_DIR}/implementations/mock ) # Set output directories for static library @@ -62,6 +89,33 @@ install(FILES module_proxy.h token_manager.h logos_mode.h + 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 + logos_registry_factory.h DESTINATION include -) \ No newline at end of file +) + +install(FILES + implementations/qt_local/local_transport.h + DESTINATION include/implementations/qt_local +) + +install(FILES + implementations/qt_remote/remote_transport.h + implementations/qt_remote/qt_remote_registry.h + DESTINATION include/implementations/qt_remote +) + +install(FILES + implementations/mock/mock_store.h + implementations/mock/mock_transport.h + implementations/mock/mock_registry.h + implementations/mock/logos_mock.h + DESTINATION include/implementations/mock +) diff --git a/cpp/implementations/mock/logos_mock.h b/cpp/implementations/mock/logos_mock.h new file mode 100644 index 0000000..3207051 --- /dev/null +++ b/cpp/implementations/mock/logos_mock.h @@ -0,0 +1,106 @@ +#ifndef LOGOS_MOCK_H +#define LOGOS_MOCK_H + +/** + * @file logos_mock.h + * @brief Convenience header for unit tests that use the mock transport. + * + * Include this file in test source files. It pulls in everything needed to + * set up mock expectations and verify calls: + * + * #include "logos_mock.h" + * + * LOGOS_TEST(my_test) { + * LogosMockSetup mock; + * mock.when("other_module", "someMethod").thenReturn(QVariant(42)); + * + * // ... exercise code under test ... + * + * LOGOS_ASSERT(mock.wasCalled("other_module", "someMethod")); + * } + */ + +#include "../../logos_mode.h" +#include "../../token_manager.h" +#include "mock_store.h" +#include +#include +#include + +/** + * @brief RAII guard that activates mock mode for the duration of a test. + * + * Construction: + * - Switches the SDK to LogosMode::Mock. + * - Resets MockStore (clears all expectations and call records). + * - Clears the TokenManager singleton so that no stale tokens from + * previous tests influence token lookup in LogosAPIClient. + * + * Destruction: + * - Restores the previous LogosMode. + * - Resets MockStore again (belt-and-suspenders cleanup). + * + * Token pre-seeding: + * when() automatically registers a non-empty dummy token for the target + * module so that LogosAPIClient::invokeRemoteMethod() does not attempt to + * call capability_module.requestModule() before invoking the mock. + */ +class LogosMockSetup { +public: + LogosMockSetup() + : m_previousMode(LogosModeConfig::getMode()) + { + LogosModeConfig::setMode(LogosMode::Mock); + MockStore::instance().reset(); + TokenManager::instance().clearAllTokens(); + } + + ~LogosMockSetup() + { + MockStore::instance().reset(); + LogosModeConfig::setMode(m_previousMode); + } + + /** + * @brief Register a mock expectation for module::method. + * + * Also seeds a dummy token for the module so LogosAPIClient does not + * try to call capability_module.requestModule(). + * + * @return A fluent builder to configure arguments and return value. + */ + MockStore::ExpectationBuilder when(const QString& module, const QString& method) + { + // Pre-seed a token so LogosAPIClient skips the capability_module lookup + TokenManager::instance().saveToken(module, "mock-token-" + module); + return MockStore::instance().when(module, method); + } + + // ── Verification helpers (delegates to MockStore) ──────────────────────── + + bool wasCalled(const QString& module, const QString& method) const + { + return MockStore::instance().wasCalled(module, method); + } + + bool wasCalledWith(const QString& module, const QString& method, + const QVariantList& args) const + { + return MockStore::instance().wasCalledWith(module, method, args); + } + + int callCount(const QString& module, const QString& method) const + { + return MockStore::instance().callCount(module, method); + } + + QVariantList lastArgs(const QString& module, const QString& method) const + { + return MockStore::instance().lastArgs(module, method); + } + +private: + LogosMode m_previousMode; +}; + +#endif // LOGOS_MOCK_H diff --git a/cpp/implementations/mock/mock_registry.h b/cpp/implementations/mock/mock_registry.h new file mode 100644 index 0000000..9269fe0 --- /dev/null +++ b/cpp/implementations/mock/mock_registry.h @@ -0,0 +1,17 @@ +#ifndef MOCK_REGISTRY_H +#define MOCK_REGISTRY_H + +#include "../../logos_registry.h" + +/** + * @brief Trivial LogosRegistry implementation for mock mode. + * + * No real registry endpoint is needed in mock mode; this implementation + * simply reports itself as initialized so that callers do not stall. + */ +class MockRegistry : public LogosRegistry { +public: + bool isInitialized() const override { return true; } +}; + +#endif // MOCK_REGISTRY_H diff --git a/cpp/implementations/mock/mock_store.cpp b/cpp/implementations/mock/mock_store.cpp new file mode 100644 index 0000000..03af7aa --- /dev/null +++ b/cpp/implementations/mock/mock_store.cpp @@ -0,0 +1,125 @@ +#include "mock_store.h" +#include +#include + +MockStore& MockStore::instance() +{ + static MockStore s; + return s; +} + +void MockStore::reset() +{ + QMutexLocker lock(&m_mutex); + m_expectations.clear(); + m_calls.clear(); +} + +// ── ExpectationBuilder ─────────────────────────────────────────────────────── + +MockStore::ExpectationBuilder::ExpectationBuilder(MockStore& store, + const QString& module, + const QString& method) + : m_store(store) +{ + QMutexLocker lock(&store.m_mutex); + MockExpectation exp; + exp.module = module; + exp.method = method; + exp.matchAnyArgs = true; + store.m_expectations.append(exp); + m_index = store.m_expectations.size() - 1; +} + +MockStore::ExpectationBuilder& MockStore::ExpectationBuilder::withArgs(const QVariantList& args) +{ + QMutexLocker lock(&m_store.m_mutex); + m_store.m_expectations[m_index].expectedArgs = args; + m_store.m_expectations[m_index].matchAnyArgs = false; + return *this; +} + +MockStore::ExpectationBuilder& MockStore::ExpectationBuilder::thenReturn(const QVariant& value) +{ + QMutexLocker lock(&m_store.m_mutex); + m_store.m_expectations[m_index].returnValue = value; + return *this; +} + +// ── MockStore ──────────────────────────────────────────────────────────────── + +MockStore::ExpectationBuilder MockStore::when(const QString& module, const QString& method) +{ + return ExpectationBuilder(*this, module, method); +} + +QVariant MockStore::recordAndReturn(const QString& module, const QString& method, + const QVariantList& args) +{ + QMutexLocker lock(&m_mutex); + + MockCallRecord record; + record.module = module; + record.method = method; + record.args = args; + m_calls.append(record); + + // Search expectations in reverse (last registered wins) + for (int i = m_expectations.size() - 1; i >= 0; --i) { + const MockExpectation& exp = m_expectations.at(i); + if (exp.module != module || exp.method != method) continue; + if (!exp.matchAnyArgs && exp.expectedArgs != args) continue; + qDebug() << "MockStore: matched expectation for" << module << "::" << method + << "-> returning" << exp.returnValue; + return exp.returnValue; + } + + qWarning() << "MockStore: no expectation registered for" << module << "::" << method + << "- returning invalid QVariant"; + return QVariant(); +} + +bool MockStore::wasCalled(const QString& module, const QString& method) const +{ + QMutexLocker lock(&m_mutex); + for (const MockCallRecord& r : m_calls) { + if (r.module == module && r.method == method) return true; + } + return false; +} + +bool MockStore::wasCalledWith(const QString& module, const QString& method, + const QVariantList& args) const +{ + QMutexLocker lock(&m_mutex); + for (const MockCallRecord& r : m_calls) { + if (r.module == module && r.method == method && r.args == args) return true; + } + return false; +} + +int MockStore::callCount(const QString& module, const QString& method) const +{ + QMutexLocker lock(&m_mutex); + int count = 0; + for (const MockCallRecord& r : m_calls) { + if (r.module == module && r.method == method) ++count; + } + return count; +} + +QVariantList MockStore::lastArgs(const QString& module, const QString& method) const +{ + QMutexLocker lock(&m_mutex); + for (int i = m_calls.size() - 1; i >= 0; --i) { + const MockCallRecord& r = m_calls.at(i); + if (r.module == module && r.method == method) return r.args; + } + return QVariantList(); +} + +QList MockStore::allCalls() const +{ + QMutexLocker lock(&m_mutex); + return m_calls; +} diff --git a/cpp/implementations/mock/mock_store.h b/cpp/implementations/mock/mock_store.h new file mode 100644 index 0000000..d043e87 --- /dev/null +++ b/cpp/implementations/mock/mock_store.h @@ -0,0 +1,109 @@ +#ifndef MOCK_STORE_H +#define MOCK_STORE_H + +#include +#include +#include +#include +#include + +/** + * @brief Records a single intercepted call to a mocked module method. + */ +struct MockCallRecord { + QString module; + QString method; + QVariantList args; +}; + +/** + * @brief Stores a single configured expectation (module + method -> return value). + * + * If matchAnyArgs is true the expectation matches regardless of arguments. + * Otherwise it only matches when args equal expectedArgs exactly. + */ +struct MockExpectation { + QString module; + QString method; + QVariantList expectedArgs; + QVariant returnValue; + bool matchAnyArgs = true; +}; + +/** + * @brief Singleton store that holds mock expectations and records calls. + * + * MockStore is the central registry used by MockTransportConnection. + * Tests set up expectations via when() and verify them via wasCalled() etc. + * Call reset() at the start of every test to clear state from previous runs. + */ +class MockStore { +public: + static MockStore& instance(); + + /** + * @brief Remove all expectations and call records. + */ + void reset(); + + // ── Fluent expectation builder ─────────────────────────────────────────── + + class ExpectationBuilder { + public: + ExpectationBuilder(MockStore& store, const QString& module, const QString& method); + + /** + * @brief Restrict this expectation to calls with exactly these arguments. + */ + ExpectationBuilder& withArgs(const QVariantList& args); + + /** + * @brief Set the value returned when the expectation is matched. + */ + ExpectationBuilder& thenReturn(const QVariant& value); + + private: + MockStore& m_store; + int m_index; // index into m_expectations + }; + + /** + * @brief Begin configuring an expectation for module::method. + * + * Multiple calls to when() for the same module/method are allowed; the + * last matching expectation wins (LIFO order). + */ + ExpectationBuilder when(const QString& module, const QString& method); + + // ── Called by MockTransportConnection ─────────────────────────────────── + + /** + * @brief Record a call and return the configured return value. + * + * If no expectation matches an invalid QVariant() is returned. + */ + QVariant recordAndReturn(const QString& module, const QString& method, + const QVariantList& args); + + // ── Verification helpers ───────────────────────────────────────────────── + + bool wasCalled(const QString& module, const QString& method) const; + bool wasCalledWith(const QString& module, const QString& method, + const QVariantList& args) const; + int callCount(const QString& module, const QString& method) const; + QVariantList lastArgs(const QString& module, const QString& method) const; + QList allCalls() const; + +private: + MockStore() = default; + MockStore(const MockStore&) = delete; + MockStore& operator=(const MockStore&) = delete; + + mutable QMutex m_mutex; + QList m_expectations; + QList m_calls; + + friend class ExpectationBuilder; +}; + +#endif // MOCK_STORE_H diff --git a/cpp/implementations/mock/mock_transport.cpp b/cpp/implementations/mock/mock_transport.cpp new file mode 100644 index 0000000..1379d2c --- /dev/null +++ b/cpp/implementations/mock/mock_transport.cpp @@ -0,0 +1,39 @@ +#include "mock_transport.h" +#include + +// ── MockTransportHost ──────────────────────────────────────────────────────── + +bool MockTransportHost::publishObject(const QString& name, QObject* /*object*/) +{ + qDebug() << "MockTransportHost: publishObject (no-op)" << name; + return true; +} + +void MockTransportHost::unpublishObject(const QString& name) +{ + qDebug() << "MockTransportHost: unpublishObject (no-op)" << name; +} + +// ── MockTransportConnection ────────────────────────────────────────────────── + +bool MockTransportConnection::connectToHost() +{ + qDebug() << "MockTransportConnection: connectToHost (no-op, always connected)"; + return true; +} + +bool MockTransportConnection::isConnected() const +{ + return true; +} + +bool MockTransportConnection::reconnect() +{ + return true; +} + +LogosObject* MockTransportConnection::requestObject(const QString& objectName, int /*timeoutMs*/) +{ + qDebug() << "MockTransportConnection: requestObject" << objectName; + return new MockLogosObject(objectName); +} diff --git a/cpp/implementations/mock/mock_transport.h b/cpp/implementations/mock/mock_transport.h new file mode 100644 index 0000000..6b92dab --- /dev/null +++ b/cpp/implementations/mock/mock_transport.h @@ -0,0 +1,79 @@ +#ifndef MOCK_TRANSPORT_H +#define MOCK_TRANSPORT_H + +#include "../../logos_transport.h" +#include "../../logos_object.h" +#include "mock_store.h" +#include +#include + +/** + * @brief LogosObject implementation for mock mode. + * + * Stores the module name and delegates callMethod to MockStore. + * Event operations are no-ops in mock mode. + */ +class MockLogosObject : public LogosObject { +public: + explicit MockLogosObject(const QString& moduleName) + : m_moduleName(moduleName) {} + + const QString& moduleName() const { return m_moduleName; } + + QVariant callMethod(const QString& /*authToken*/, + const QString& methodName, + const QVariantList& args, + int /*timeoutMs*/) override + { + return MockStore::instance().recordAndReturn(m_moduleName, methodName, args); + } + + bool informModuleToken(const QString& /*authToken*/, + const QString& moduleName, + const QString& /*token*/, + int /*timeoutMs*/) override + { + Q_UNUSED(moduleName) + return true; + } + + void onEvent(const QString& /*eventName*/, EventCallback /*callback*/) override {} + void disconnectEvents() override {} + void emitEvent(const QString& /*eventName*/, const QVariantList& /*data*/) override {} + + QJsonArray getMethods() override { return QJsonArray(); } + + void release() override { delete this; } + + quintptr id() const override { return reinterpret_cast(this); } + +private: + QString m_moduleName; +}; + +/** + * @brief No-op provider-side transport for mock mode. + * + * publishObject / unpublishObject succeed silently; there is no real + * IPC endpoint and no object needs to be made available. + */ +class MockTransportHost : public LogosTransportHost { +public: + bool publishObject(const QString& name, QObject* object) override; + void unpublishObject(const QString& name) override; +}; + +/** + * @brief Consumer-side transport for mock mode. + * + * requestObject returns a MockLogosObject tagged with the module name. + */ +class MockTransportConnection : public LogosTransportConnection { +public: + bool connectToHost() override; + bool isConnected() const override; + bool reconnect() override; + LogosObject* requestObject(const QString& objectName, int timeoutMs) override; +}; + +#endif // MOCK_TRANSPORT_H diff --git a/cpp/implementations/qt_local/local_transport.cpp b/cpp/implementations/qt_local/local_transport.cpp new file mode 100644 index 0000000..a257e1c --- /dev/null +++ b/cpp/implementations/qt_local/local_transport.cpp @@ -0,0 +1,171 @@ +#include "local_transport.h" +#include "../../plugin_registry.h" +#include "../../module_proxy.h" +#include +#include + +// ── LocalLogosObject ───────────────────────────────────────────────────────── + +namespace { + +class EventHelper : public QObject { + Q_OBJECT +public: + explicit EventHelper(QObject* parent = nullptr) : QObject(parent) {} + + void addCallback(const QString& eventName, LogosObject::EventCallback cb) { + m_callbacks[eventName].append(std::move(cb)); + } + +public slots: + void onEventResponse(const QString& eventName, const QVariantList& data) { + auto cbs = m_callbacks.value(eventName); + if (!cbs.isEmpty()) { + qDebug() << "[LogosObject] Local EventHelper: dispatching event" << eventName << "to" << cbs.size() << "callback(s)"; + } + for (const auto& cb : cbs) { + try { cb(eventName, data); } catch (...) {} + } + } + +private: + QHash> m_callbacks; +}; + +} // anonymous namespace + +class LocalLogosObject : public LogosObject { +public: + explicit LocalLogosObject(ModuleProxy* proxy) + : m_proxy(proxy), m_helper(nullptr) + { + qDebug() << "[LogosObject] Created LocalLogosObject wrapping ModuleProxy" << reinterpret_cast(proxy); + } + + ~LocalLogosObject() override { + qDebug() << "[LogosObject] Destroying LocalLogosObject" << reinterpret_cast(m_proxy); + delete m_helper; + } + + QVariant callMethod(const QString& authToken, + const QString& methodName, + const QVariantList& args, + int /*timeoutMs*/) override + { + if (!m_proxy) return QVariant(); + qDebug() << "[LogosObject] LocalLogosObject::callMethod" << methodName << "args:" << args.size(); + return m_proxy->callRemoteMethod(authToken, methodName, args); + } + + bool informModuleToken(const QString& authToken, + const QString& moduleName, + const QString& token, + int /*timeoutMs*/) override + { + if (!m_proxy) return false; + return m_proxy->informModuleToken(authToken, moduleName, token); + } + + void onEvent(const QString& eventName, EventCallback callback) override + { + if (!m_proxy) return; + + qDebug() << "[LogosObject] LocalLogosObject::onEvent subscribing to event:" << eventName; + if (!m_helper) { + m_helper = new EventHelper(); + QObject::connect(m_proxy, SIGNAL(eventResponse(QString,QVariantList)), + m_helper, SLOT(onEventResponse(QString,QVariantList))); + qDebug() << "[LogosObject] LocalLogosObject: connected EventHelper to ModuleProxy signals"; + } + m_helper->addCallback(eventName, std::move(callback)); + } + + void disconnectEvents() override + { + delete m_helper; + m_helper = nullptr; + } + + void emitEvent(const QString& eventName, const QVariantList& data) override + { + if (!m_proxy) return; + qDebug() << "[LogosObject] LocalLogosObject::emitEvent" << eventName << "data:" << data.size() << "items"; + QMetaObject::invokeMethod(m_proxy, "eventResponse", + Qt::QueuedConnection, + Q_ARG(QString, eventName), + Q_ARG(QVariantList, data)); + } + + QJsonArray getMethods() override + { + if (!m_proxy) return QJsonArray(); + return m_proxy->getPluginMethods(); + } + + void release() override + { + // Local mode: we don't own the ModuleProxy, just stop using it + disconnectEvents(); + } + + quintptr id() const override { return reinterpret_cast(m_proxy); } + +private: + ModuleProxy* m_proxy; + EventHelper* m_helper; +}; + +// ── LocalTransportHost ─────────────────────────────────────────────────────── + +bool LocalTransportHost::publishObject(const QString& name, QObject* object) +{ + PluginRegistry::registerPlugin(object, name); + qDebug() << "LocalTransportHost: Published object:" << name; + return true; +} + +void LocalTransportHost::unpublishObject(const QString& name) +{ + if (!name.isEmpty()) { + PluginRegistry::unregisterPlugin(name); + qDebug() << "LocalTransportHost: Unpublished object:" << name; + } +} + +// ── LocalTransportConnection ───────────────────────────────────────────────── + +bool LocalTransportConnection::connectToHost() +{ + qDebug() << "LocalTransportConnection: Local mode - no connection needed"; + return true; +} + +bool LocalTransportConnection::isConnected() const +{ + return true; +} + +bool LocalTransportConnection::reconnect() +{ + return true; +} + +LogosObject* LocalTransportConnection::requestObject(const QString& objectName, int /*timeoutMs*/) +{ + QObject* plugin = PluginRegistry::getPlugin(objectName); + if (!plugin) { + qWarning() << "LocalTransportConnection: Plugin not found in registry:" << objectName; + return nullptr; + } + + ModuleProxy* proxy = qobject_cast(plugin); + if (!proxy) { + qWarning() << "LocalTransportConnection: Plugin is not a ModuleProxy:" << objectName; + return nullptr; + } + + qDebug() << "[LogosObject] LocalTransportConnection: returning LocalLogosObject for:" << objectName; + return new LocalLogosObject(proxy); +} + +#include "local_transport.moc" diff --git a/cpp/implementations/qt_local/local_transport.h b/cpp/implementations/qt_local/local_transport.h new file mode 100644 index 0000000..51ee171 --- /dev/null +++ b/cpp/implementations/qt_local/local_transport.h @@ -0,0 +1,23 @@ +#ifndef LOCAL_TRANSPORT_H +#define LOCAL_TRANSPORT_H + +#include "../../logos_transport.h" +#include "../../logos_object.h" + +class ModuleProxy; + +class LocalTransportHost : public LogosTransportHost { +public: + bool publishObject(const QString& name, QObject* object) override; + void unpublishObject(const QString& name) override; +}; + +class LocalTransportConnection : public LogosTransportConnection { +public: + bool connectToHost() override; + bool isConnected() const override; + bool reconnect() override; + LogosObject* requestObject(const QString& objectName, int timeoutMs) override; +}; + +#endif // LOCAL_TRANSPORT_H diff --git a/cpp/implementations/qt_remote/qt_remote_registry.cpp b/cpp/implementations/qt_remote/qt_remote_registry.cpp new file mode 100644 index 0000000..89bbfbd --- /dev/null +++ b/cpp/implementations/qt_remote/qt_remote_registry.cpp @@ -0,0 +1,28 @@ +#include "qt_remote_registry.h" +#include +#include +#include + +QtRemoteRegistry::QtRemoteRegistry(const QString& url) + : m_registryHost(nullptr) +{ + m_registryHost = new QRemoteObjectRegistryHost(QUrl(url)); + + if (m_registryHost) { + qDebug() << "QtRemoteRegistry: Registry host created at:" << url; + } else { + qCritical() << "QtRemoteRegistry: Failed to create registry host at:" << url; + } +} + +QtRemoteRegistry::~QtRemoteRegistry() +{ + delete m_registryHost; + m_registryHost = nullptr; + qDebug() << "QtRemoteRegistry: Registry host destroyed"; +} + +bool QtRemoteRegistry::isInitialized() const +{ + return m_registryHost != nullptr; +} diff --git a/cpp/implementations/qt_remote/qt_remote_registry.h b/cpp/implementations/qt_remote/qt_remote_registry.h new file mode 100644 index 0000000..f877c25 --- /dev/null +++ b/cpp/implementations/qt_remote/qt_remote_registry.h @@ -0,0 +1,27 @@ +#ifndef QT_REMOTE_REGISTRY_H +#define QT_REMOTE_REGISTRY_H + +#include "../../logos_registry.h" +#include + +class QRemoteObjectRegistryHost; + +/** + * @brief LogosRegistry implementation backed by QRemoteObjectRegistryHost. + * + * Used in Remote (multi-process) mode. The registry host is created in the + * constructor and torn down in the destructor, so the lifetime of this object + * directly controls the lifetime of the IPC rendezvous point. + */ +class QtRemoteRegistry : public LogosRegistry { +public: + explicit QtRemoteRegistry(const QString& url); + ~QtRemoteRegistry() override; + + bool isInitialized() const override; + +private: + QRemoteObjectRegistryHost* m_registryHost; +}; + +#endif // QT_REMOTE_REGISTRY_H diff --git a/cpp/implementations/qt_remote/remote_transport.cpp b/cpp/implementations/qt_remote/remote_transport.cpp new file mode 100644 index 0000000..9f22da5 --- /dev/null +++ b/cpp/implementations/qt_remote/remote_transport.cpp @@ -0,0 +1,311 @@ +#include "remote_transport.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ── RemoteLogosObject ──────────────────────────────────────────────────────── + +namespace { + +class RemoteEventHelper : public QObject { + Q_OBJECT +public: + explicit RemoteEventHelper(QObject* parent = nullptr) : QObject(parent) {} + + void addCallback(const QString& eventName, LogosObject::EventCallback cb) { + m_callbacks[eventName].append(std::move(cb)); + } + +public slots: + void onEventResponse(const QString& eventName, const QVariantList& data) { + auto cbs = m_callbacks.value(eventName); + if (!cbs.isEmpty()) { + qDebug() << "[LogosObject] Remote EventHelper: dispatching event" << eventName << "to" << cbs.size() << "callback(s) (via IPC)"; + } + for (const auto& cb : cbs) { + try { cb(eventName, data); } catch (...) {} + } + } + +private: + QHash> m_callbacks; +}; + +} // anonymous namespace + +class RemoteLogosObject : public LogosObject { +public: + explicit RemoteLogosObject(QObject* replica) + : m_replica(replica), m_helper(nullptr) + { + qDebug() << "[LogosObject] Created RemoteLogosObject wrapping QRemoteObjectReplica" << reinterpret_cast(replica); + } + + ~RemoteLogosObject() override { + qDebug() << "[LogosObject] Destroying RemoteLogosObject" << reinterpret_cast(m_replica); + delete m_helper; + } + + QVariant callMethod(const QString& authToken, + const QString& methodName, + const QVariantList& args, + int timeoutMs) override + { + if (!m_replica) { + qWarning() << "RemoteLogosObject: Cannot call method on null replica"; + return QVariant(); + } + qDebug() << "[LogosObject] RemoteLogosObject::callMethod" << methodName << "args:" << args.size(); + + QRemoteObjectPendingCall pendingCall; + bool success = QMetaObject::invokeMethod( + m_replica, + "callRemoteMethod", + Qt::DirectConnection, + Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), + Q_ARG(QString, authToken), + Q_ARG(QString, methodName), + Q_ARG(QVariantList, args) + ); + + if (!success) { + qWarning() << "RemoteLogosObject: Failed to invoke callRemoteMethod on replica"; + return QVariant(); + } + + pendingCall.waitForFinished(timeoutMs); + + if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { + qWarning() << "RemoteLogosObject: callRemoteMethod failed or timed out:" << pendingCall.error(); + return QVariant(); + } + + return pendingCall.returnValue(); + } + + bool informModuleToken(const QString& authToken, + const QString& moduleName, + const QString& token, + int timeoutMs) override + { + if (!m_replica) { + qWarning() << "RemoteLogosObject: Cannot call informModuleToken on null replica"; + return false; + } + + QRemoteObjectPendingCall pendingCall; + bool success = QMetaObject::invokeMethod( + m_replica, + "informModuleToken", + Qt::DirectConnection, + Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), + Q_ARG(QString, authToken), + Q_ARG(QString, moduleName), + Q_ARG(QString, token) + ); + + if (!success) { + qWarning() << "RemoteLogosObject: Failed to invoke informModuleToken on replica"; + return false; + } + + pendingCall.waitForFinished(timeoutMs); + + if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { + qWarning() << "RemoteLogosObject: informModuleToken failed or timed out:" << pendingCall.error(); + return false; + } + + return pendingCall.returnValue().toBool(); + } + + void onEvent(const QString& eventName, EventCallback callback) override + { + if (!m_replica) return; + + qDebug() << "[LogosObject] RemoteLogosObject::onEvent subscribing to event:" << eventName; + if (!m_helper) { + m_helper = new RemoteEventHelper(); + QObject::connect(m_replica, SIGNAL(eventResponse(QString,QVariantList)), + m_helper, SLOT(onEventResponse(QString,QVariantList))); + qDebug() << "[LogosObject] RemoteLogosObject: connected EventHelper to QRemoteObjectReplica signals (IPC)"; + } + m_helper->addCallback(eventName, std::move(callback)); + } + + void disconnectEvents() override + { + delete m_helper; + m_helper = nullptr; + } + + void emitEvent(const QString& eventName, const QVariantList& data) override + { + if (!m_replica) return; + qDebug() << "[LogosObject] RemoteLogosObject::emitEvent" << eventName << "data:" << data.size() << "items (via IPC)"; + QMetaObject::invokeMethod(m_replica, "eventResponse", + Qt::QueuedConnection, + Q_ARG(QString, eventName), + Q_ARG(QVariantList, data)); + } + + QJsonArray getMethods() override + { + // Remote introspection not implemented — callers should use + // the local module inspection tools (lm) instead. + return QJsonArray(); + } + + void release() override + { + disconnectEvents(); + delete m_replica; + m_replica = nullptr; + delete this; + } + + quintptr id() const override { return reinterpret_cast(m_replica); } + +private: + QObject* m_replica; + RemoteEventHelper* m_helper; +}; + +// ── RemoteTransportHost ────────────────────────────────────────────────────── + +RemoteTransportHost::RemoteTransportHost(const QString& registryUrl) + : m_registryHost(nullptr) + , m_registryUrl(registryUrl) +{ +} + +RemoteTransportHost::~RemoteTransportHost() +{ + delete m_registryHost; +} + +bool RemoteTransportHost::publishObject(const QString& name, QObject* object) +{ + if (!m_registryHost) { + m_registryHost = new QRemoteObjectRegistryHost(QUrl(m_registryUrl)); + if (!m_registryHost) { + qCritical() << "RemoteTransportHost: Failed to create registry host"; + return false; + } + qDebug() << "RemoteTransportHost: Created registry host with URL:" << m_registryUrl; + } + + bool success = m_registryHost->enableRemoting(object, name); + if (success) { + qDebug() << "RemoteTransportHost: Published object:" << name; + } else { + qCritical() << "RemoteTransportHost: Failed to publish object:" << name; + } + return success; +} + +void RemoteTransportHost::unpublishObject(const QString& /*name*/) +{ +} + +// ── RemoteTransportConnection ──────────────────────────────────────────────── + +RemoteTransportConnection::RemoteTransportConnection(const QString& registryUrl) + : m_node(new QRemoteObjectNode()) + , m_registryUrl(registryUrl) + , m_connected(false) +{ +} + +RemoteTransportConnection::~RemoteTransportConnection() +{ + delete m_node; +} + +bool RemoteTransportConnection::connectToHost() +{ + return connectToRegistry(); +} + +bool RemoteTransportConnection::isConnected() const +{ + return m_connected; +} + +bool RemoteTransportConnection::reconnect() +{ + qDebug() << "RemoteTransportConnection: Attempting to reconnect to registry:" << m_registryUrl; + + if (m_connected) { + delete m_node; + m_node = new QRemoteObjectNode(); + m_connected = false; + } + + return connectToRegistry(); +} + +bool RemoteTransportConnection::connectToRegistry() +{ + if (!m_node) { + qWarning() << "RemoteTransportConnection: Remote object node is null"; + return false; + } + + if (m_registryUrl.isEmpty()) { + qWarning() << "RemoteTransportConnection: Registry URL is empty"; + return false; + } + + qDebug() << "RemoteTransportConnection: Connecting to registry:" << m_registryUrl + << "at" << QTime::currentTime().toString("hh:mm:ss.zzz"); + + QUrl url(m_registryUrl); + bool success = m_node->connectToNode(url); + + if (success) { + m_connected = true; + qDebug() << "RemoteTransportConnection: Successfully connected to registry:" << m_registryUrl; + } else { + m_connected = false; + qWarning() << "RemoteTransportConnection: Failed to connect to registry:" << m_registryUrl; + } + qDebug() << "RemoteTransportConnection: Connected to registry at" + << QTime::currentTime().toString("hh:mm:ss.zzz"); + + return m_connected; +} + +LogosObject* RemoteTransportConnection::requestObject(const QString& objectName, int timeoutMs) +{ + if (!m_connected) { + qWarning() << "RemoteTransportConnection: Not connected. Cannot request object:" << objectName; + return nullptr; + } + + qDebug() << "RemoteTransportConnection: Requesting object:" << objectName + << "at" << QTime::currentTime().toString("hh:mm:ss.zzz"); + + QRemoteObjectReplica* replica = m_node->acquireDynamic(objectName); + if (!replica) { + qWarning() << "RemoteTransportConnection: Failed to acquire replica for:" << objectName; + return nullptr; + } + + if (!replica->waitForSource(timeoutMs)) { + qWarning() << "RemoteTransportConnection: Timeout waiting for replica:" << objectName; + delete replica; + return nullptr; + } + + qDebug() << "[LogosObject] RemoteTransportConnection: returning RemoteLogosObject for:" << objectName; + return new RemoteLogosObject(replica); +} + +#include "remote_transport.moc" diff --git a/cpp/implementations/qt_remote/remote_transport.h b/cpp/implementations/qt_remote/remote_transport.h new file mode 100644 index 0000000..6ce826e --- /dev/null +++ b/cpp/implementations/qt_remote/remote_transport.h @@ -0,0 +1,42 @@ +#ifndef REMOTE_TRANSPORT_H +#define REMOTE_TRANSPORT_H + +#include "../../logos_transport.h" +#include "../../logos_object.h" +#include + +class QRemoteObjectRegistryHost; +class QRemoteObjectNode; + +class RemoteTransportHost : public LogosTransportHost { +public: + explicit RemoteTransportHost(const QString& registryUrl); + ~RemoteTransportHost() override; + + bool publishObject(const QString& name, QObject* object) override; + void unpublishObject(const QString& name) override; + +private: + QRemoteObjectRegistryHost* m_registryHost; + QString m_registryUrl; +}; + +class RemoteTransportConnection : public LogosTransportConnection { +public: + explicit RemoteTransportConnection(const QString& registryUrl); + ~RemoteTransportConnection() override; + + bool connectToHost() override; + bool isConnected() const override; + bool reconnect() override; + LogosObject* requestObject(const QString& objectName, int timeoutMs) override; + +private: + bool connectToRegistry(); + + QRemoteObjectNode* m_node; + QString m_registryUrl; + bool m_connected; +}; + +#endif // REMOTE_TRANSPORT_H diff --git a/cpp/logos_api_client.cpp b/cpp/logos_api_client.cpp index d9df72d..b39bbc3 100644 --- a/cpp/logos_api_client.cpp +++ b/cpp/logos_api_client.cpp @@ -1,6 +1,8 @@ #include "logos_api_client.h" #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) @@ -12,10 +14,9 @@ LogosAPIClient::LogosAPIClient(const QString& module_to_talk_to, const QString& LogosAPIClient::~LogosAPIClient() { - // m_consumer will be deleted automatically as it's a child object } -QObject* LogosAPIClient::requestObject(const QString& objectName, Timeout timeout) +LogosObject* LogosAPIClient::requestObject(const QString& objectName, Timeout timeout) { return m_consumer->requestObject(objectName, timeout); } @@ -40,7 +41,6 @@ QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QSt { qDebug() << "LogosAPIClient: invoking remote method" << objectName << methodName << "args_count:" << args.size(); - // Get the token for the module QString token = getToken(objectName); if (token.isEmpty() && objectName != "capability_module") { @@ -48,19 +48,7 @@ QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QSt LogosAPIConsumer* packageManagerConsumer = new LogosAPIConsumer("capability_module", m_origin_module, m_token_manager, this); QString capabilityToken = getToken("capability_module"); QVariant result = packageManagerConsumer->invokeRemoteMethod(capabilityToken, "capability_module", "requestModule", QVariantList() << m_origin_module << objectName, timeout); - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; qDebug() << "LogosAPIClient: requestModule result for" << objectName << ":" << result.toString(); - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - qDebug() << "================================================"; - token = result.toString(); } @@ -100,90 +88,100 @@ QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QSt } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariantList& args, - std::function callback, - Timeout timeout) + const QVariantList& args, AsyncResultCallback callback, + Timeout timeout) { - if (!callback) { - return; - } + if (!callback) return; + QString token = getToken(objectName); + if (token.isEmpty() && objectName != "capability_module") { - LogosAPIConsumer* apiConsumer = new LogosAPIConsumer("capability_module", m_origin_module, m_token_manager, this); + LogosAPIConsumer* packageManagerConsumer = new LogosAPIConsumer("capability_module", m_origin_module, m_token_manager, this); QString capabilityToken = getToken("capability_module"); - QVariant result = apiConsumer->invokeRemoteMethod(capabilityToken, "capability_module", "requestModule", QVariantList() << m_origin_module << objectName, timeout); + QVariant result = packageManagerConsumer->invokeRemoteMethod(capabilityToken, "capability_module", "requestModule", QVariantList() << m_origin_module << objectName, timeout); token = result.toString(); } - m_consumer->invokeRemoteMethodAsync(token, objectName, methodName, args, callback, timeout); + + m_consumer->invokeRemoteMethodAsync(token, objectName, methodName, args, std::move(callback), timeout); } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg, std::function callback, Timeout timeout) + const QVariant& arg, AsyncResultCallback callback, + Timeout timeout) { - invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg, callback, timeout); + invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg, std::move(callback), timeout); } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, std::function callback, Timeout timeout) + const QVariant& arg1, const QVariant& arg2, + AsyncResultCallback callback, Timeout timeout) { - invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2, callback, timeout); + invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2, std::move(callback), timeout); } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - std::function callback, Timeout timeout) + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + AsyncResultCallback callback, Timeout timeout) { - invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3, callback, timeout); + invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3, std::move(callback), timeout); } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - const QVariant& arg4, - std::function callback, Timeout timeout) + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, AsyncResultCallback callback, + Timeout timeout) { - invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4, callback, timeout); + invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4, std::move(callback), timeout); } void LogosAPIClient::invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - const QVariant& arg4, const QVariant& arg5, - std::function callback, Timeout timeout) + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, const QVariant& arg5, + AsyncResultCallback callback, Timeout timeout) { - invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4 << arg5, callback, timeout); + invokeRemoteMethodAsync(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4 << arg5, std::move(callback), timeout); } -void LogosAPIClient::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName, std::function callback) +void LogosAPIClient::onEvent(LogosObject* originObject, const QString& eventName, std::function callback) { - m_consumer->onEvent(originObject, destinationObject, eventName, callback); + m_consumer->onEvent(originObject, eventName, std::move(callback)); } -void LogosAPIClient::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName) +void LogosAPIClient::onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data) { - m_consumer->onEvent(originObject, destinationObject, eventName); -} - - - -void LogosAPIClient::invokeCallback(const QString& eventName, const QVariantList& data) -{ - m_consumer->invokeCallback(eventName, data); -} - -void LogosAPIClient::onEventResponse(QObject* replica, const QString& eventName, const QVariantList& data) -{ - // qDebug() << "LogosAPIClient: Received event:" << eventName << "with data:" << data; - qDebug() << "LogosAPIClient: Received event:" << eventName; + qDebug() << "[LogosObject] LogosAPIClient::onEventResponse" << eventName << "-> LogosObject::emitEvent"; if (eventName.isEmpty()) { qWarning() << "LogosAPIClient: Event name cannot be empty"; return; } - // qDebug() << "LogosAPIClient: Emitting event:" << eventName << "with data:" << data; - qDebug() << "LogosAPIClient: Emitting event:" << eventName; + if (!object) { + qWarning() << "LogosAPIClient: Cannot emit event on null object"; + return; + } - // emit the eventResponse signal of replica - QMetaObject::invokeMethod(replica, "eventResponse", Qt::QueuedConnection, Q_ARG(QString, eventName), Q_ARG(QVariantList, data)); + 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) @@ -205,19 +203,12 @@ QString LogosAPIClient::getToken(const QString& module_name) { qDebug() << "LogosAPIClient: getToken for module:" << module_name; - // if (m_token_manager) { - QString token = m_token_manager->getToken(module_name); - if (!token.isEmpty()) { - qDebug() << "LogosAPIClient: Found token for module:" << module_name; - return token; - } - // } else { - // qDebug() << "LogosAPIClient: No token manager found - using default AUTH_TOKEN"; - // } + QString token = m_token_manager->getToken(module_name); + if (!token.isEmpty()) { + qDebug() << "LogosAPIClient: Found token for module:" << module_name; + return token; + } qDebug() << "LogosAPIClient: No token found for module:" << module_name; - - // TODO: this is breaking here for core_manager - // return AUTH_TOKEN; return ""; } diff --git a/cpp/logos_api_client.h b/cpp/logos_api_client.h index 13219e3..489cb8a 100644 --- a/cpp/logos_api_client.h +++ b/cpp/logos_api_client.h @@ -11,246 +11,112 @@ #include "logos_mode.h" class LogosAPIConsumer; +class LogosObject; class TokenManager; /** * @brief LogosAPIClient provides a high-level interface for remote method calls * * This class serves as a facade over LogosAPIConsumer, providing a clean interface - * for applications that need to call remote methods and handle events. It includes - * additional logic like token management and request routing. + * for applications that need to call remote methods and handle events. */ class LogosAPIClient : public QObject { Q_OBJECT public: - /** - * @brief Construct a new LogosAPIClient - * @param module_to_talk_to The name of the module to connect to - * @param origin_module The name of the originating module - * @param token_manager Pointer to the token manager instance - * @param parent Parent QObject - */ explicit LogosAPIClient(const QString& module_to_talk_to, const QString& origin_module, TokenManager* token_manager, QObject *parent = nullptr); - - /** - * @brief Destructor - */ ~LogosAPIClient(); /** - * @brief Request a remote object replica by name - * @param objectName The name of the remote object to acquire - * @param timeout Timeout to wait for the replica to be ready (default 20000ms) - * @return QObject* pointer to the replica, or nullptr if failed + * @brief Request a LogosObject handle by name + * @return LogosObject* handle, or nullptr if failed */ - QObject* requestObject(const QString& objectName, Timeout timeout = Timeout()); + LogosObject* requestObject(const QString& objectName, Timeout timeout = Timeout()); - /** - * @brief Check if the client is connected to the registry - * @return true if connected, false otherwise - */ bool isConnected() const; - - /** - * @brief Get the registry URL this client is connected to - * @return QString containing the registry URL - */ QString registryUrl() const; - - /** - * @brief Reconnect to the registry - * @return true if reconnection successful, false otherwise - */ bool reconnect(); - /** - * @brief Invoke a remote method on a remote object - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param args Arguments to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariantList& args = QVariantList(), Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method on a remote object with a single argument - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param arg Argument to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariant& arg, Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method on a remote object with two arguments - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param arg1 First argument to pass to the method - * @param arg2 Second argument to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariant& arg1, const QVariant& arg2, Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method on a remote object with three arguments - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param arg1 First argument to pass to the method - * @param arg2 Second argument to pass to the method - * @param arg3 Third argument to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method on a remote object with four arguments - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param arg1 First argument to pass to the method - * @param arg2 Second argument to pass to the method - * @param arg3 Third argument to pass to the method - * @param arg4 Fourth argument to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, const QVariant& arg4, Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method on a remote object with five arguments - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param arg1 First argument to pass to the method - * @param arg2 Second argument to pass to the method - * @param arg3 Third argument to pass to the method - * @param arg4 Fourth argument to pass to the method - * @param arg5 Fifth argument to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ - QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, const QVariant& arg4, const QVariant& arg5, Timeout timeout = Timeout()); - /** - * @brief Invoke a remote method asynchronously; result or error is delivered via callback - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param args Arguments to pass to the method - * @param callback Called when the call completes (typically on the main thread) - * @param timeout Timeout for replica acquisition and for the remote call (default 20000ms) - */ + using AsyncResultCallback = std::function; + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariantList& args, - std::function callback, - Timeout timeout = Timeout()); + const QVariantList& args, AsyncResultCallback callback, + Timeout timeout = Timeout()); + + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, + const QVariant& arg, AsyncResultCallback callback, + Timeout timeout = Timeout()); + + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, + AsyncResultCallback callback, Timeout timeout = Timeout()); + + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + AsyncResultCallback callback, Timeout timeout = Timeout()); + + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, AsyncResultCallback callback, + Timeout timeout = Timeout()); + + void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, const QVariant& arg5, + AsyncResultCallback callback, Timeout timeout = Timeout()); /** - * @brief Invoke a remote method asynchronously with a single argument - */ - void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg, std::function callback, Timeout timeout = Timeout()); - - /** - * @brief Invoke a remote method asynchronously with two arguments - */ - void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, std::function callback, Timeout timeout = Timeout()); - - /** - * @brief Invoke a remote method asynchronously with three arguments - */ - void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - std::function callback, Timeout timeout = Timeout()); - - /** - * @brief Invoke a remote method asynchronously with four arguments - */ - void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - const QVariant& arg4, - std::function callback, Timeout timeout = Timeout()); - - /** - * @brief Invoke a remote method asynchronously with five arguments - */ - void invokeRemoteMethodAsync(const QString& objectName, const QString& methodName, - const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, - const QVariant& arg4, const QVariant& arg5, - std::function callback, Timeout timeout = Timeout()); - - /** - * @brief Register an event listener for the specified event name - * @param originObject The object that will emit the event - * @param destinationObject The object that will receive the event + * @brief Register an event listener via LogosObject's callback mechanism + * @param originObject The LogosObject that will emit the event * @param eventName The name of the event to listen for * @param callback Function to call when the event is triggered */ - void onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName, + void onEvent(LogosObject* originObject, const QString& eventName, std::function callback); - - /** - * @brief Register an event listener without callback (connects to destinationObject's slot) - * @param originObject The object that will emit the event - * @param destinationObject The object that will receive the event - * @param eventName The name of the event to listen for - */ - void onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName); - - /** - * @brief Emit an event response (for plugins that also act as event sources) - * @param replica The replica object that should receive the event + * @brief Emit an event on a LogosObject (for plugins that act as event sources) + * @param object The LogosObject to emit the event on * @param eventName The name of the event * @param data The event data */ - void onEventResponse(QObject* replica, const QString& eventName, const QVariantList& data); + void onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data); /** - * @brief Inform a module about 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 + * @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. */ - bool informModuleToken(const QString& authToken, const QString& moduleName, const QString& token); + 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); - /** - * @brief Get the token manager instance - * @return TokenManager* Pointer to the token manager - */ TokenManager* getTokenManager() const; - - /** - * @brief Get authentication token for a module - * @param module_name The module name to get token for - * @return QString containing the token - */ QString getToken(const QString& module_name); -public slots: - /** - * @brief Helper slot to invoke stored callbacks - * @param eventName The name of the event that was triggered - * @param data The event data to pass to the callback - */ - void invokeCallback(const QString& eventName, const QVariantList& data); - private: LogosAPIConsumer* m_consumer; QMap m_tokens; @@ -258,4 +124,4 @@ private: QString m_origin_module; }; -#endif // LOGOS_API_CLIENT_H \ No newline at end of file +#endif // LOGOS_API_CLIENT_H diff --git a/cpp/logos_api_consumer.cpp b/cpp/logos_api_consumer.cpp index ad94bed..26f480b 100644 --- a/cpp/logos_api_consumer.cpp +++ b/cpp/logos_api_consumer.cpp @@ -1,50 +1,32 @@ #include "logos_api_consumer.h" +#include "logos_object.h" #include "module_proxy.h" #include "logos_api_client.h" #include "token_manager.h" #include "logos_mode.h" #include "logos_instance.h" -#include "plugin_registry.h" -#include -#include -#include -#include -#include -#include +#include "logos_transport.h" +#include "logos_transport_factory.h" #include #include #include +#include #include LogosAPIConsumer::LogosAPIConsumer(const QString& module_to_talk_to, const QString& origin_module, TokenManager* token_manager, QObject *parent) : QObject(parent) - , m_node(nullptr) , m_registryUrl(LogosInstance::id(module_to_talk_to)) - , m_connected(false) , m_token_manager(token_manager) { - if (LogosModeConfig::isLocal()) { - qDebug() << "LogosAPIConsumer: Using Local mode - skipping QRemoteObjectNode"; - m_connected = true; - } else { - m_node = new QRemoteObjectNode(this); - connectToRegistry(); - } + m_transport = LogosTransportFactory::createConnection(m_registryUrl); + m_transport->connectToHost(); } LogosAPIConsumer::~LogosAPIConsumer() { - // Clean up event callbacks and connections - for (auto it = m_connections.begin(); it != m_connections.end(); ++it) { - QObject::disconnect(it.value()); - } - m_eventCallbacks.clear(); - m_connections.clear(); - - // QRemoteObjectNode will be deleted automatically as it's a child object } -QObject* LogosAPIConsumer::requestObject(const QString& objectName, Timeout timeout) +LogosObject* LogosAPIConsumer::requestObject(const QString& objectName, Timeout timeout) { qDebug() << "LogosAPIConsumer: Requesting object:" << objectName << "at" << QTime::currentTime().toString("hh:mm:ss.zzz"); @@ -53,45 +35,21 @@ QObject* LogosAPIConsumer::requestObject(const QString& objectName, Timeout time 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) { + if (!m_transport->isConnected()) { qWarning() << "LogosAPIConsumer: Not connected to registry. Cannot request object:" << objectName; return nullptr; } - qDebug() << "LogosAPIConsumer: Requesting object:" << objectName; - - // Acquire the dynamic replica - QRemoteObjectReplica* replica = m_node->acquireDynamic(objectName); - if (!replica) { - qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << objectName; - return nullptr; + LogosObject* object = m_transport->requestObject(objectName, timeout.ms); + if (object) { + qDebug() << "[LogosObject] LogosAPIConsumer: acquired LogosObject for:" << objectName << "(id:" << object->id() << ")"; } - - // Wait for the replica to be initialized - if (!replica->waitForSource(timeout.ms)) { - qWarning() << "LogosAPIConsumer: Timeout waiting for object replica to be ready:" << objectName; - delete replica; - return nullptr; - } - - qDebug() << "LogosAPIConsumer: Successfully acquired replica for object:" << objectName; - qDebug() << "LogosAPIConsumer: Replica acquired at" << QTime::currentTime().toString("hh:mm:ss.zzz"); - return replica; + return object; } bool LogosAPIConsumer::isConnected() const { - return m_connected; + return m_transport->isConnected(); } QString LogosAPIConsumer::registryUrl() const @@ -101,350 +59,94 @@ 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 - if (m_connected) { - // Note: QRemoteObjectNode doesn't have a direct disconnect method - // We'll create a new node instead - m_node->deleteLater(); - m_node = new QRemoteObjectNode(this); - m_connected = false; - } - - return connectToRegistry(); + return m_transport->reconnect(); } -bool LogosAPIConsumer::connectToRegistry() -{ - if (!m_node) { - qWarning() << "LogosAPIConsumer: Remote object node is null"; - return false; - } - - if (m_registryUrl.isEmpty()) { - qWarning() << "LogosAPIConsumer: Registry URL is empty"; - return false; - } - - qDebug() << "LogosAPIConsumer: Connecting to registry:" << m_registryUrl; - qDebug() << "LogosAPIConsumer: Connecting to registry at" << QTime::currentTime().toString("hh:mm:ss.zzz"); - - // Connect to the registry node - QUrl url(m_registryUrl); - bool success = m_node->connectToNode(url); - - if (success) { - m_connected = true; - qDebug() << "LogosAPIConsumer: Successfully connected to registry:" << m_registryUrl; - } else { - m_connected = false; - qWarning() << "LogosAPIConsumer: Failed to connect to registry:" << m_registryUrl; - } - qDebug() << "LogosAPIConsumer: Connected to registry at" << QTime::currentTime().toString("hh:mm:ss.zzz"); - - return m_connected; -} - - - QVariant LogosAPIConsumer::invokeRemoteMethod(const QString& authToken, const QString& objectName, const QString& methodName, const QVariantList& args, Timeout timeout) { qDebug() << "LogosAPIConsumer: Calling invokeRemoteMethod:" << objectName << methodName << "args_count:" << args.size() << "timeout:" << timeout.ms; - // This method handles both ModuleProxy-wrapped modules (template_module, package_manager) - // and direct remote object calls for other modules - QObject* plugin = requestObject(objectName, timeout); + LogosObject* plugin = m_transport->requestObject(objectName, timeout.ms); if (!plugin) { qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << objectName; return QVariant(); } - ModuleProxy* moduleProxy = qobject_cast(plugin); - if (moduleProxy) { - QVariant result = moduleProxy->callRemoteMethod(authToken, methodName, args); - if (!LogosModeConfig::isLocal()) { - delete plugin; - } - return result; - } - - 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( - plugin, - "callRemoteMethod", - Qt::DirectConnection, - Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), - Q_ARG(QString, authToken), - Q_ARG(QString, methodName), - Q_ARG(QVariantList, args) - ); - - if (!success) { - qWarning() << "LogosAPIConsumer: Failed to invoke callRemoteMethod on replica for object:" << objectName; - delete plugin; - return QVariant(); - } - - // Wait for the result - pendingCall.waitForFinished(timeout.ms); - delete plugin; - - if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { - qWarning() << "LogosAPIConsumer: Remote callRemoteMethod failed or timed out:" << pendingCall.error(); - return QVariant(); - } - - return pendingCall.returnValue(); + qDebug() << "[LogosObject] LogosAPIConsumer: calling via LogosObject::callMethod" << methodName; + QVariant result = plugin->callMethod(authToken, methodName, args, timeout.ms); + plugin->release(); + return result; } void LogosAPIConsumer::invokeRemoteMethodAsync(const QString& authToken, const QString& objectName, const QString& methodName, - const QVariantList& args, - AsyncResultCallback callback, - Timeout timeout) + const QVariantList& args, + AsyncResultCallback callback, + Timeout timeout) { if (!callback) { qWarning() << "LogosAPIConsumer: invokeRemoteMethodAsync called with null callback"; return; } - QObject* plugin = requestObject(objectName, timeout); + LogosObject* plugin = m_transport->requestObject(objectName, timeout.ms); if (!plugin) { qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << objectName; QTimer::singleShot(0, this, [callback]() { callback(QVariant()); }); return; } - ModuleProxy* moduleProxy = qobject_cast(plugin); - if (moduleProxy) { - QVariant result = moduleProxy->callRemoteMethod(authToken, methodName, args); - if (!LogosModeConfig::isLocal()) { - delete plugin; - } - QTimer::singleShot(0, this, [callback, result]() { callback(result); }); + qDebug() << "[LogosObject] LogosAPIConsumer: async calling via LogosObject::callMethod" << methodName; + QVariant result = plugin->callMethod(authToken, methodName, args, timeout.ms); + plugin->release(); + QTimer::singleShot(0, this, [callback, result]() { callback(result); }); +} + +void LogosAPIConsumer::onEvent(LogosObject* originObject, const QString& eventName, std::function callback) +{ + qDebug() << "[LogosObject] LogosAPIConsumer::onEvent registering for:" << eventName << "on LogosObject id:" << originObject; + + if (!originObject) { + qWarning() << "LogosAPIConsumer: Cannot register event on null object"; return; } - if (LogosModeConfig::isLocal()) { - qWarning() << "LogosAPIConsumer: Local mode requires ModuleProxy-wrapped objects"; - QTimer::singleShot(0, this, [callback]() { callback(QVariant()); }); - return; - } + originObject->onEvent(eventName, std::move(callback)); - QRemoteObjectPendingCall pendingCall; - bool success = QMetaObject::invokeMethod( - plugin, - "callRemoteMethod", - Qt::DirectConnection, - Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), - Q_ARG(QString, authToken), - Q_ARG(QString, methodName), - Q_ARG(QVariantList, args) - ); - - if (!success) { - qWarning() << "LogosAPIConsumer: Failed to invoke callRemoteMethod on replica for object:" << objectName; - delete plugin; - QTimer::singleShot(0, this, [callback]() { callback(QVariant()); }); - return; - } - - QRemoteObjectPendingCallWatcher* watcher = new QRemoteObjectPendingCallWatcher(pendingCall, this); - QPointer watcherRef(watcher); - QObject* pluginToDelete = plugin; - // Use QueuedConnection so the callback always runs on the consumer's (e.g. main) thread, - // even if the watcher emits finished() from an IO or worker thread. Otherwise the callback - // may never run or may run in a context where UI/consumer state is inaccessible. - connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, [watcherRef, callback, pluginToDelete]() { - QVariant result; - if (!watcherRef.isNull() && watcherRef->error() == QRemoteObjectPendingCall::NoError) { - result = watcherRef->returnValue(); - } - if (callback) callback(result); - delete pluginToDelete; - if (!watcherRef.isNull()) watcherRef->deleteLater(); - }, Qt::QueuedConnection); - QTimer::singleShot(timeout.ms, this, [watcherRef, callback, pluginToDelete]() { - if (!watcherRef.isNull() && !watcherRef->isFinished()) { - if (callback) callback(QVariant()); - delete pluginToDelete; - watcherRef->deleteLater(); - } - }); -} - -void LogosAPIConsumer::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName, std::function callback) -{ - qDebug() << "LogosAPIConsumer: Registering event listener for event:" << eventName; - - // Store the callback for this event name - m_eventCallbacks[eventName].append(callback); - - // Check if we already have a connection for this origin object - if (!m_connections.contains(originObject)) { - // Create new connection only if it doesn't exist - auto connection = QObject::connect(originObject, SIGNAL(eventResponse(QString, QVariantList)), - this, SLOT(invokeCallback(QString, QVariantList))); - - if (connection) { - m_connections[originObject] = connection; - qDebug() << "LogosAPIConsumer: Created new connection for origin object"; - } else { - qWarning() << "LogosAPIConsumer: Failed to create connection for event:" << eventName; - } - } else { - qDebug() << "LogosAPIConsumer: Reusing existing connection for origin object"; - } - - qDebug() << "LogosAPIConsumer: Registered callback for event:" << eventName; -} - -void LogosAPIConsumer::invokeCallback(const QString& eventName, const QVariantList& data) -{ - // qDebug() << "LogosAPIConsumer: invokeCallback called for event:" << eventName; - - // Call all registered callbacks - // Note: This will call all callbacks for any event. In a more sophisticated implementation, - // you might want to store event names with callbacks to filter them. - for (const auto& callback : m_eventCallbacks[eventName]) { - try { - callback(eventName, data); - } catch (...) { - qWarning() << "LogosAPIConsumer: Exception in callback for event:" << eventName; - } - } - - // qDebug() << "LogosAPIConsumer: Called" << m_eventCallbacks[eventName].size() << "callbacks for event:" << eventName; -} - -void LogosAPIConsumer::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName) -{ - qDebug() << "LogosAPIConsumer: Registering event listener for event:" << eventName << "(connecting to destination slot)"; - - // connect to the eventResponse signal of the destinationObject's slot - QObject::connect(originObject, SIGNAL(eventResponse(QString, QVariantList)), - destinationObject, SLOT(onEventResponse(QString, QVariantList)), Qt::AutoConnection); + qDebug() << "[LogosObject] LogosAPIConsumer: event callback registered for:" << eventName; } bool LogosAPIConsumer::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) { qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token; - QObject* plugin = requestObject("capability_module", Timeout(20000)); + LogosObject* plugin = m_transport->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; - } - - QRemoteObjectPendingCall pendingCall; - bool success = QMetaObject::invokeMethod( - plugin, - "informModuleToken", - Qt::DirectConnection, - Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), - Q_ARG(QString, authToken), - Q_ARG(QString, moduleName), - Q_ARG(QString, token) - ); - - if (!success) { - qWarning() << "LogosAPIConsumer: Failed to invoke informModuleToken on replica"; - delete plugin; - return false; - } - - pendingCall.waitForFinished(20000); - delete plugin; - - if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { - qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); - return false; - } - - QVariant result = pendingCall.returnValue(); + qDebug() << "[LogosObject] LogosAPIConsumer: calling LogosObject::informModuleToken for" << moduleName; + bool result = plugin->informModuleToken(authToken, moduleName, token, 20000); qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; - - return result.toBool(); + plugin->release(); + return result; } bool LogosAPIConsumer::informModuleToken_module(const QString& authToken, const QString& originModule, const QString& moduleName, const QString& token) { qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token; - QObject* plugin = requestObject(originModule, Timeout(20000)); + LogosObject* plugin = m_transport->requestObject(originModule, 20000); if (!plugin) { qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << originModule; 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; - } - QRemoteObjectPendingCall pendingCall; - bool success = QMetaObject::invokeMethod( - plugin, - "informModuleToken", - Qt::DirectConnection, - Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall), - Q_ARG(QString, authToken), - Q_ARG(QString, moduleName), - Q_ARG(QString, token) - ); - - if (!success) { - qWarning() << "LogosAPIConsumer: Failed to invoke informModuleToken on replica"; - delete plugin; - return false; - } - - pendingCall.waitForFinished(20000); - delete plugin; - - if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { - qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); - return false; - } - - QVariant result = pendingCall.returnValue(); + qDebug() << "[LogosObject] LogosAPIConsumer: calling LogosObject::informModuleToken for" << moduleName << "on" << originModule; + bool result = plugin->informModuleToken(authToken, moduleName, token, 20000); qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; - - return result.toBool(); + plugin->release(); + return result; } diff --git a/cpp/logos_api_consumer.h b/cpp/logos_api_consumer.h index 76fe656..33a972a 100644 --- a/cpp/logos_api_consumer.h +++ b/cpp/logos_api_consumer.h @@ -8,89 +8,54 @@ #include #include #include +#include #include "logos_mode.h" -class QRemoteObjectNode; +class LogosTransportConnection; +class LogosObject; class TokenManager; /** - * @brief LogosAPIConsumer handles connecting to remote objects and invoking their methods + * @brief LogosAPIConsumer handles connecting to module objects and invoking their methods * * This class is responsible for the consumer/client side functionality: - * - Connecting to remote object registries - * - Requesting remote object replicas - * - Invoking methods on remote objects - * - Handling events from remote objects + * - Connecting to module registries via the transport layer + * - Requesting LogosObject handles + * - Invoking methods on objects + * - Handling events from objects */ class LogosAPIConsumer : public QObject { Q_OBJECT public: - /** - * @brief Construct a new LogosAPIConsumer - * @param module_to_talk_to The name of the module to connect to - * @param origin_module The name of the originating module - * @param token_manager Pointer to the token manager instance - * @param parent Parent QObject - */ explicit LogosAPIConsumer(const QString& module_to_talk_to, const QString& origin_module, TokenManager* token_manager, QObject *parent = nullptr); - - /** - * @brief Destructor - cleans up connections and resources - */ ~LogosAPIConsumer(); /** - * @brief Request a remote object replica by name - * @param objectName The name of the remote object to acquire - * @param timeout Timeout to wait for the replica to be ready (default 20000ms) - * @return QObject* pointer to the replica, or nullptr if failed + * @brief Request a LogosObject handle by name + * @return LogosObject* handle, or nullptr if failed. Caller must call release() when done. */ - QObject* requestObject(const QString& objectName, Timeout timeout = Timeout()); + LogosObject* requestObject(const QString& objectName, Timeout timeout = Timeout()); - /** - * @brief Check if the consumer is connected to the registry - * @return true if connected, false otherwise - */ bool isConnected() const; - - /** - * @brief Get the registry URL this consumer is connected to - * @return QString containing the registry URL - */ QString registryUrl() const; - - /** - * @brief Reconnect to the registry - * @return true if reconnection successful, false otherwise - */ bool reconnect(); - /** - * @brief Invoke a remote method on a remote object - * @param authToken Authentication token for the operation - * @param objectName The name of the remote object - * @param methodName The name of the method to call - * @param args Arguments to pass to the method - * @param timeout Timeout to wait for the result (default 20000ms) - * @return QVariant containing the result, or invalid QVariant if failed - */ - QVariant invokeRemoteMethod(const QString& authToken, const QString& objectName, const QString& methodName, + QVariant invokeRemoteMethod(const QString& authToken, const QString& objectName, const QString& methodName, const QVariantList& args = QVariantList(), Timeout timeout = Timeout()); - /** Callback type for async remote method results. Receives the result QVariant (invalid on error/timeout). */ using AsyncResultCallback = std::function; /** - * @brief Invoke a remote method asynchronously; result or error is delivered via callback + * @brief Invoke a remote method asynchronously; result is delivered via callback * @param authToken Authentication token for the operation * @param objectName The name of the remote object * @param methodName The name of the method to call * @param args Arguments to pass to the method - * @param callback Called when the call completes (on the same thread as this consumer, typically main) - * @param timeout Timeout for replica acquisition and for the remote call (default 20000ms) + * @param callback Called when the call completes (on the caller's thread via QueuedConnection) + * @param timeout Timeout for replica acquisition and for the remote call */ void invokeRemoteMethodAsync(const QString& authToken, const QString& objectName, const QString& methodName, const QVariantList& args, @@ -98,62 +63,23 @@ public: Timeout timeout = Timeout()); /** - * @brief Register an event listener for the specified event name - * @param originObject The object that will emit the event - * @param destinationObject The object that will receive the event + * @brief Register an event listener via LogosObject's callback mechanism + * @param originObject The LogosObject that will emit the event * @param eventName The name of the event to listen for * @param callback Function to call when the event is triggered */ - void onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName, + void onEvent(LogosObject* originObject, const QString& eventName, std::function callback); - - /** - * @brief Register an event listener without callback (connects to destinationObject's slot) - * @param originObject The object that will emit the event - * @param destinationObject The object that will receive the event - * @param eventName The name of the event to listen for - */ - void onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName); public slots: - /** - * @brief Helper slot to invoke stored callbacks - * @param eventName The name of the event that was triggered - * @param data The event data to pass to the callback - */ - void invokeCallback(const QString& eventName, const QVariantList& data); - - /** - * @brief Inform a module about 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 - */ 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); private: - QRemoteObjectNode* m_node; + std::unique_ptr m_transport; QString m_registryUrl; - bool m_connected; QMap m_tokens; TokenManager* m_token_manager; - - // Store callbacks by event name - QHash>> m_eventCallbacks; - - // Track existing connections by origin object to avoid duplicates - QHash m_connections; - - /** - * @brief Internal method to establish connection to the registry - * @return true if connection successful, false otherwise - */ - bool connectToRegistry(); - - }; -#endif // LOGOS_API_CONSUMER_H \ No newline at end of file +#endif // LOGOS_API_CONSUMER_H diff --git a/cpp/logos_api_provider.cpp b/cpp/logos_api_provider.cpp index 4881911..883133a 100644 --- a/cpp/logos_api_provider.cpp +++ b/cpp/logos_api_provider.cpp @@ -1,32 +1,31 @@ #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_mode.h" #include "logos_instance.h" -#include "plugin_registry.h" -#include +#include "logos_transport.h" +#include "logos_transport_factory.h" #include -#include -#include -#include LogosAPIProvider::LogosAPIProvider(const QString& module_name, QObject *parent) : QObject(parent) - , m_registryHost(nullptr) , m_registryUrl(LogosInstance::id(module_name)) , m_moduleProxy(nullptr) + , m_qtProviderObject(nullptr) { + m_transport = LogosTransportFactory::createHost(m_registryUrl); } LogosAPIProvider::~LogosAPIProvider() { - if (LogosModeConfig::isLocal() && !m_registeredObjectName.isEmpty()) { - PluginRegistry::unregisterPlugin(m_registeredObjectName); + if (!m_registeredObjectName.isEmpty()) { + m_transport->unpublishObject(m_registeredObjectName); } - // QRemoteObjectRegistryHost will be deleted automatically as it's a child object - // ModuleProxy will be deleted automatically as it's a child object } +// QObject* path: auto-detects LogosProviderPlugin; falls back to QtProviderObject wrapper bool LogosAPIProvider::registerObject(const QString& name, QObject* object) { if (!object) { @@ -39,57 +38,66 @@ bool LogosAPIProvider::registerObject(const QString& name, QObject* object) return false; } - // Check if a ModuleProxy was already created - only allow one registration if (m_moduleProxy) { qCritical() << "LogosAPIProvider: Object already registered. Only one registration per provider is allowed"; return false; } - qDebug() << "LogosAPIProvider: Creating ModuleProxy for" << name << "wrapping the provided object"; - - // Before wrapping with ModuleProxy, call initLogos if the method exists - // Check if the object has an initLogos method and call it with the parent (LogosAPI instance) - 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); - object = m_moduleProxy; + // Legacy path: wrap QObject in QtProviderObject adapter + qDebug() << "[LogosProviderObject] LogosAPIProvider: wrapping QObject in QtProviderObject for" << name; - bool success = false; + m_qtProviderObject = new QtProviderObject(object, this); + m_qtProviderObject->init(qobject_cast(parent())); - if (LogosModeConfig::isLocal()) { - PluginRegistry::registerPlugin(object, name); + 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; - success = true; - qDebug() << "LogosAPIProvider: Successfully registered object with name:" << name; + qDebug() << "[LogosProviderObject] LogosAPIProvider: successfully published" << name; } else { - 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; - } + qCritical() << "LogosAPIProvider: Failed to publish" << name; } return success; @@ -107,27 +115,22 @@ 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); } -void LogosAPIProvider::onEventResponse(QObject* replica, const QString& eventName, const QVariantList& data) +void LogosAPIProvider::onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data) { - // qDebug() << "LogosAPIProvider: Received event:" << eventName << "with data:" << data; - qDebug() << "LogosAPIProvider: Received event:" << eventName; + qDebug() << "[LogosObject] LogosAPIProvider::onEventResponse" << eventName << "-> LogosObject::emitEvent"; if (eventName.isEmpty()) { qWarning() << "LogosAPIProvider: Event name cannot be empty"; return; } + if (!object) { + qWarning() << "LogosAPIProvider: Cannot emit event on null object"; + return; + } - // qDebug() << "LogosAPIProvider: Emitting event:" << eventName << "with data:" << data; - qDebug() << "LogosAPIProvider: Emitting event:" << eventName; - - // emit the eventResponse signal of replica - QMetaObject::invokeMethod(replica, "eventResponse", Qt::QueuedConnection, Q_ARG(QString, eventName), Q_ARG(QVariantList, data)); - // QMetaObject::invokeMethod(replica, "eventResponse_another", Qt::QueuedConnection, Q_ARG(QString, eventName), Q_ARG(QVariantList, data)); - // TODO: try queued connection instead - // QMetaObject::invokeMethod(replica, "eventResponse_another", Qt::DirectConnection, Q_ARG(QString, eventName), Q_ARG(QVariantList, data)); + object->emitEvent(eventName, data); } - diff --git a/cpp/logos_api_provider.h b/cpp/logos_api_provider.h index 5319851..c04ce5c 100644 --- a/cpp/logos_api_provider.h +++ b/cpp/logos_api_provider.h @@ -6,77 +6,57 @@ #include #include #include +#include -class QRemoteObjectRegistryHost; +class LogosTransportHost; +class LogosObject; class ModuleProxy; - -#include "logos_mode.h" +class LogosProviderObject; +class QtProviderObject; /** - * @brief LogosAPIProvider handles registering objects for remote access + * @brief LogosAPIProvider handles registering objects for access by consumers * - * This class is responsible for the provider/server side functionality: - * - Creating registry hosts - * - Registering objects for remote access - * - 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 { Q_OBJECT public: - /** - * @brief Construct a new LogosAPIProvider - * @param module_name The name of this module - * @param parent Parent QObject - */ explicit LogosAPIProvider(const QString& module_name, QObject *parent = nullptr); - - /** - * @brief Destructor - cleans up registry host - */ ~LogosAPIProvider(); /** - * @brief Register an object to be available for remote access - * @param name The name to register the object under - * @param object The object to register - * @param authToken Authentication token for the object - * @return true if registration successful, false otherwise + * @brief Register a legacy QObject-based plugin. + * Wraps in QtProviderObject, then ModuleProxy. */ bool registerObject(const QString& name, QObject* object); /** - * @brief Get the registry URL for this provider - * @return QString containing the registry URL + * @brief Register a new-API LogosProviderObject plugin. + * Wraps directly in ModuleProxy. */ - QString registryUrl() const; + bool registerObject(const QString& name, LogosProviderObject* provider); - /** - * @brief Save a token from a module via the proxy - * @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 - */ + QString registryUrl() const; bool saveToken(const QString& from_module_name, const QString& token); public slots: - /** - * @brief Handle event responses from objects - * @param replica The replica object that should receive the event - * @param eventName The name of the event - * @param data The event data - */ - void onEventResponse(QObject* replica, const QString& eventName, const QVariantList& data); + void onEventResponse(LogosObject* object, const QString& eventName, const QVariantList& data); private: - QRemoteObjectRegistryHost* m_registryHost; + 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; - - }; -#endif // LOGOS_API_PROVIDER_H \ No newline at end of file +#endif // LOGOS_API_PROVIDER_H diff --git a/cpp/logos_mode.h b/cpp/logos_mode.h index e3fde1a..22023d4 100644 --- a/cpp/logos_mode.h +++ b/cpp/logos_mode.h @@ -8,10 +8,12 @@ * * - Remote: Uses QRemoteObjects for inter-process communication (desktop apps) * - Local: Uses in-process PluginRegistry (mobile apps, single process) + * - Mock: Uses in-memory mock transport for unit testing */ enum class LogosMode { Remote, // Default: Use QRemoteObjects (IPC) - Local // Use in-process PluginRegistry + Local, // Use in-process PluginRegistry + Mock // Use in-memory mock transport for unit testing }; /** @@ -56,7 +58,10 @@ namespace LogosModeConfig { */ inline void setMode(LogosMode mode) { modeStorage() = mode; - qDebug() << "LogosModeConfig: Mode set to" << (mode == LogosMode::Local ? "Local" : "Remote"); + QString modeName = (mode == LogosMode::Local) ? "Local" + : (mode == LogosMode::Mock) ? "Mock" + : "Remote"; + qDebug() << "LogosModeConfig: Mode set to" << modeName; } /** @@ -83,6 +88,14 @@ namespace LogosModeConfig { return modeStorage() == LogosMode::Remote; } + /** + * @brief Check if the SDK is in Mock mode + * @return true if Mock mode, false otherwise + */ + inline bool isMock() { + return modeStorage() == LogosMode::Mock; + } + } #endif // LOGOS_MODE_H diff --git a/cpp/logos_object.h b/cpp/logos_object.h new file mode 100644 index 0000000..7e1126d --- /dev/null +++ b/cpp/logos_object.h @@ -0,0 +1,99 @@ +#ifndef LOGOS_OBJECT_H +#define LOGOS_OBJECT_H + +#include +#include +#include +#include +#include +#include + +/** + * @brief Abstract interface for a module object handle. + * + * LogosObject decouples callers from the underlying transport mechanism. + * Each transport (local/Qt Remote Objects/mock/JSON-RPC/...) provides its + * own concrete subclass. Callers interact exclusively through this + * interface and never need to know the implementation type. + */ +class LogosObject { +public: + virtual ~LogosObject() = default; + + /** + * @brief Invoke a method on the remote/local module. + * @param authToken Authentication token for the operation + * @param methodName Method to call on the underlying module + * @param args Arguments for the method + * @param timeoutMs Maximum time to wait for the result + * @return The method result, or an invalid QVariant on failure + */ + virtual QVariant callMethod(const QString& authToken, + const QString& methodName, + const QVariantList& args, + int timeoutMs) = 0; + + /** + * @brief Deliver a module token to the underlying module. + * @param authToken Authentication token for the operation + * @param moduleName Target module name + * @param token The token to deliver + * @param timeoutMs Maximum time to wait for the result + * @return true if the token was delivered successfully + */ + virtual bool informModuleToken(const QString& authToken, + const QString& moduleName, + const QString& token, + int timeoutMs) = 0; + + using EventCallback = std::function; + + /** + * @brief Subscribe to events from this object. + * + * Qt-based implementations use QObject::connect internally; + * other implementations may use a different mechanism. + * + * @param eventName The event name to listen for + * @param callback Called when the event fires + */ + virtual void onEvent(const QString& eventName, EventCallback callback) = 0; + + /** + * @brief Remove all event subscriptions made via onEvent(). + */ + virtual void disconnectEvents() = 0; + + /** + * @brief Emit an event on this object. + * + * For Qt-based implementations this triggers the underlying + * QObject signal so that Qt Remote Objects can replicate it. + * + * @param eventName The event name + * @param data Event payload + */ + virtual void emitEvent(const QString& eventName, const QVariantList& data) = 0; + + /** + * @brief Return introspection data for the methods exposed by + * the underlying module. + */ + virtual QJsonArray getMethods() = 0; + + /** + * @brief Release resources associated with this handle. + * + * After calling release() the object must not be used again. + * Implementations that own the underlying resource (e.g. a + * QRemoteObjectReplica) will delete it here. + */ + virtual void release() = 0; + + /** + * @brief Stable identity value suitable for use as a hash key. + */ + virtual quintptr id() const = 0; +}; + +#endif // LOGOS_OBJECT_H 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/logos_registry.h b/cpp/logos_registry.h new file mode 100644 index 0000000..32594df --- /dev/null +++ b/cpp/logos_registry.h @@ -0,0 +1,31 @@ +#ifndef LOGOS_REGISTRY_H +#define LOGOS_REGISTRY_H + +/** + * @brief Abstract interface for a module registry endpoint. + * + * A registry is a well-known rendezvous point that modules advertise + * themselves on (host/provider side) and that consumers discover them + * through (client side). + * + * The interface is intentionally minimal: lifecycle management + * (creation / destruction) is handled entirely by the implementation's + * constructor and destructor. Callers only need to check whether the + * registry is up and running. + * + * Current implementations: + * - QtRemoteRegistry — backed by QRemoteObjectRegistryHost (Remote mode) + * - NullRegistry — no-op used in Local (in-process) mode + */ +class LogosRegistry { +public: + virtual ~LogosRegistry() = default; + + /** + * @brief Returns true if the registry endpoint has been successfully + * created and is ready to accept registrations. + */ + virtual bool isInitialized() const = 0; +}; + +#endif // LOGOS_REGISTRY_H diff --git a/cpp/logos_registry_factory.cpp b/cpp/logos_registry_factory.cpp new file mode 100644 index 0000000..59bcd7f --- /dev/null +++ b/cpp/logos_registry_factory.cpp @@ -0,0 +1,35 @@ +#include "logos_registry_factory.h" +#include "logos_registry.h" +#include "logos_mode.h" +#include "implementations/qt_remote/qt_remote_registry.h" +#include "implementations/mock/mock_registry.h" + +namespace { + +/** + * @brief No-op registry used in Local (in-process) mode. + * + * In Local mode all modules live in the same process and discover each + * other through PluginRegistry, so no IPC rendezvous point is required. + */ +class NullRegistry : public LogosRegistry { +public: + bool isInitialized() const override { return true; } +}; + +} // anonymous namespace + +namespace LogosRegistryFactory { + +std::unique_ptr create(const QString& url) +{ + if (LogosModeConfig::isLocal()) { + return std::make_unique(); + } + if (LogosModeConfig::isMock()) { + return std::make_unique(); + } + return std::make_unique(url); +} + +} // namespace LogosRegistryFactory diff --git a/cpp/logos_registry_factory.h b/cpp/logos_registry_factory.h new file mode 100644 index 0000000..475a13d --- /dev/null +++ b/cpp/logos_registry_factory.h @@ -0,0 +1,27 @@ +#ifndef LOGOS_REGISTRY_FACTORY_H +#define LOGOS_REGISTRY_FACTORY_H + +#include +#include + +class LogosRegistry; + +namespace LogosRegistryFactory { + + /** + * @brief Create a registry appropriate for the current LogosMode. + * + * In Remote mode a QtRemoteRegistry is created, binding a + * QRemoteObjectRegistryHost to the given @p url. + * + * In Local (in-process) mode a NullRegistry is created — no IPC + * endpoint is needed because all modules share the same process. + * + * @param url The address to bind the registry to (ignored in Local mode). + * @return Owning pointer to the new registry. + */ + std::unique_ptr create(const QString& url); + +} + +#endif // LOGOS_REGISTRY_FACTORY_H diff --git a/cpp/logos_transport.h b/cpp/logos_transport.h new file mode 100644 index 0000000..9324bfe --- /dev/null +++ b/cpp/logos_transport.h @@ -0,0 +1,77 @@ +#ifndef LOGOS_TRANSPORT_H +#define LOGOS_TRANSPORT_H + +#include +#include +#include + +class QObject; +class LogosObject; + +/** + * @brief Abstract interface for the provider/server side of module transport. + * + * Implementations handle how a module object is made available to consumers + * (e.g. via in-process registry, Qt Remote Objects, or other mechanisms). + */ +class LogosTransportHost { +public: + virtual ~LogosTransportHost() = default; + + /** + * @brief Publish an object so consumers can discover and invoke it + * @param name The name to publish the object under + * @param object The QObject to publish (provider side remains Qt-based) + * @return true if publishing succeeded + */ + virtual bool publishObject(const QString& name, QObject* object) = 0; + + /** + * @brief Remove a previously published object + * @param name The name the object was published under + */ + virtual void unpublishObject(const QString& name) = 0; +}; + +/** + * @brief Abstract interface for the consumer/client side of module transport. + * + * Implementations handle how a consumer connects to a host and acquires + * LogosObject handles. Method invocation, event subscription, and lifecycle + * management are handled by LogosObject itself. + */ +class LogosTransportConnection { +public: + virtual ~LogosTransportConnection() = default; + + /** + * @brief Establish connection to the host/registry + * @return true if connection succeeded + */ + virtual bool connectToHost() = 0; + + /** + * @brief Check if currently connected + */ + virtual bool isConnected() const = 0; + + /** + * @brief Tear down and re-establish the connection + * @return true if reconnection succeeded + */ + virtual bool reconnect() = 0; + + /** + * @brief Acquire a LogosObject handle to a named object from the host. + * + * The returned LogosObject encapsulates method invocation, event + * handling and lifecycle. Call LogosObject::release() when done. + * + * @param objectName The published name of the object + * @param timeoutMs Maximum time to wait for the object to become available + * @return LogosObject* handle, or nullptr on failure. + */ + virtual LogosObject* requestObject(const QString& objectName, int timeoutMs) = 0; +}; + +#endif // LOGOS_TRANSPORT_H diff --git a/cpp/logos_transport_factory.cpp b/cpp/logos_transport_factory.cpp new file mode 100644 index 0000000..431f498 --- /dev/null +++ b/cpp/logos_transport_factory.cpp @@ -0,0 +1,32 @@ +#include "logos_transport_factory.h" +#include "logos_transport.h" +#include "logos_mode.h" +#include "implementations/qt_local/local_transport.h" +#include "implementations/qt_remote/remote_transport.h" +#include "implementations/mock/mock_transport.h" + +namespace LogosTransportFactory { + +std::unique_ptr createHost(const QString& registryUrl) +{ + if (LogosModeConfig::isLocal()) { + return std::make_unique(); + } + if (LogosModeConfig::isMock()) { + return std::make_unique(); + } + return std::make_unique(registryUrl); +} + +std::unique_ptr createConnection(const QString& registryUrl) +{ + if (LogosModeConfig::isLocal()) { + return std::make_unique(); + } + if (LogosModeConfig::isMock()) { + return std::make_unique(); + } + return std::make_unique(registryUrl); +} + +} diff --git a/cpp/logos_transport_factory.h b/cpp/logos_transport_factory.h new file mode 100644 index 0000000..c31bb2c --- /dev/null +++ b/cpp/logos_transport_factory.h @@ -0,0 +1,28 @@ +#ifndef LOGOS_TRANSPORT_FACTORY_H +#define LOGOS_TRANSPORT_FACTORY_H + +#include +#include + +class LogosTransportHost; +class LogosTransportConnection; + +namespace LogosTransportFactory { + + /** + * @brief Create the appropriate transport host for the current mode + * @param registryUrl The URL used by the remote transport (ignored in local mode) + * @return Owning pointer to the transport host + */ + std::unique_ptr createHost(const QString& registryUrl); + + /** + * @brief Create the appropriate transport connection for the current mode + * @param registryUrl The URL to connect to (ignored in local mode) + * @return Owning pointer to the transport connection + */ + std::unique_ptr createConnection(const QString& registryUrl); + +} + +#endif // LOGOS_TRANSPORT_FACTORY_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/docs/docs.md b/docs/docs.md index f9fbc51..b2694b8 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -295,7 +295,7 @@ Triggering an event: QVariantList data; data << timestamp << nick << message; -logosAPI->getClient("chat")->onEventResponse(this, "chatMessage", data); +emit eventResponse("chatMessage", data); ``` Triggering an event with the generated helpers: diff --git a/nix/include.nix b/nix/include.nix index 842137a..91dd285 100644 --- a/nix/include.nix +++ b/nix/include.nix @@ -18,6 +18,9 @@ pkgs.stdenv.mkDerivation { # Install headers with proper structure mkdir -p $out/include/core mkdir -p $out/include/cpp + mkdir -p $out/include/cpp/implementations/qt_local + mkdir -p $out/include/cpp/implementations/qt_remote + mkdir -p $out/include/cpp/implementations/mock # Install core headers if [ -f core/interface.h ]; then @@ -27,12 +30,37 @@ pkgs.stdenv.mkDerivation { # Install cpp headers and sources for file in logos_types.cpp logos_types.h 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 logos_mode.h logos_instance.h; do + 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 if [ -f cpp/$file ]; then cp cpp/$file $out/include/cpp/ fi done + # Install transport implementation headers and sources + for file in local_transport.h local_transport.cpp; do + if [ -f cpp/implementations/qt_local/$file ]; then + cp cpp/implementations/qt_local/$file $out/include/cpp/implementations/qt_local/ + fi + done + + for file in remote_transport.h remote_transport.cpp qt_remote_registry.h qt_remote_registry.cpp; do + if [ -f cpp/implementations/qt_remote/$file ]; then + cp cpp/implementations/qt_remote/$file $out/include/cpp/implementations/qt_remote/ + fi + done + + for file in mock_store.h mock_store.cpp mock_transport.h mock_transport.cpp mock_registry.h logos_mock.h; do + if [ -f cpp/implementations/mock/$file ]; then + cp cpp/implementations/mock/$file $out/include/cpp/implementations/mock/ + fi + done + if [ -f cpp/logos_mode.h ]; then cp cpp/logos_mode.h $out/include/ fi @@ -40,4 +68,3 @@ pkgs.stdenv.mkDerivation { runHook postInstall ''; } -