From 65aa4a1b24692802e618b3ef0d7c4e320f5f0d99 Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Tue, 30 Sep 2025 14:56:47 -0400 Subject: [PATCH] feat: move logos-cpp-sdk to its own repo --- .gitignore | 60 +++ core/interface.h | 29 ++ cpp-generator/CMakeLists.txt | 27 ++ cpp-generator/compile.sh | 58 +++ cpp-generator/main.cpp | 822 +++++++++++++++++++++++++++++++++++ cpp/CMakeLists.txt | 42 ++ cpp/compile.sh | 200 +++++++++ cpp/example_usage.cpp | 105 +++++ cpp/logos_api.cpp | 49 +++ cpp/logos_api.h | 60 +++ cpp/logos_api_client.cpp | 179 ++++++++ cpp/logos_api_client.h | 211 +++++++++ cpp/logos_api_consumer.cpp | 321 ++++++++++++++ cpp/logos_api_consumer.h | 140 ++++++ cpp/logos_api_provider.cpp | 117 +++++ cpp/logos_api_provider.h | 79 ++++ cpp/module_proxy.cpp | 429 ++++++++++++++++++ cpp/module_proxy.h | 78 ++++ cpp/simple_example.cpp | 33 ++ cpp/token_manager.cpp | 66 +++ cpp/token_manager.h | 116 +++++ 21 files changed, 3221 insertions(+) create mode 100644 .gitignore create mode 100644 core/interface.h create mode 100644 cpp-generator/CMakeLists.txt create mode 100755 cpp-generator/compile.sh create mode 100644 cpp-generator/main.cpp create mode 100644 cpp/CMakeLists.txt create mode 100755 cpp/compile.sh create mode 100644 cpp/example_usage.cpp create mode 100644 cpp/logos_api.cpp create mode 100644 cpp/logos_api.h create mode 100644 cpp/logos_api_client.cpp create mode 100644 cpp/logos_api_client.h create mode 100644 cpp/logos_api_consumer.cpp create mode 100644 cpp/logos_api_consumer.h create mode 100644 cpp/logos_api_provider.cpp create mode 100644 cpp/logos_api_provider.h create mode 100644 cpp/module_proxy.cpp create mode 100644 cpp/module_proxy.h create mode 100644 cpp/simple_example.cpp create mode 100644 cpp/token_manager.cpp create mode 100644 cpp/token_manager.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb03146 --- /dev/null +++ b/.gitignore @@ -0,0 +1,60 @@ +# Build directories +build/ +bin/ +lib/ +out/ + +# CMake artifacts +CMakeCache.txt +CMakeFiles/ +CMakeScripts/ +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps/ + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Generated files (comment out if these should be committed) +cpp/generated/ + +# IDE specific files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Nix +result +result-* + +# Logs +*.log + diff --git a/core/interface.h b/core/interface.h new file mode 100644 index 0000000..8b30e27 --- /dev/null +++ b/core/interface.h @@ -0,0 +1,29 @@ +#ifndef PLUGIN_INTERFACE_H +#define PLUGIN_INTERFACE_H + +#include +#include +#include "../cpp/logos_api.h" + +// Define the common base interface for all modules +class PluginInterface +{ +public: + virtual ~PluginInterface() {} + + // Common plugin methods + virtual QString name() const = 0; + virtual QString version() const = 0; + + // TODO: this should be defined here and removed from the modules, but needs some work + // Q_INVOKABLE void initLogos(LogosAPI* logosAPIInstance); + + LogosAPI* logosAPI = nullptr; +}; + +// Define the interface ID used by Qt's plugin system +#define PluginInterface_iid "com.example.PluginInterface" + +Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid) + +#endif // PLUGIN_INTERFACE_H diff --git a/cpp-generator/CMakeLists.txt b/cpp-generator/CMakeLists.txt new file mode 100644 index 0000000..0bac737 --- /dev/null +++ b/cpp-generator/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.14) +project(LogosCppGenerator) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +# Find Qt Core (QCoreApplication, QPluginLoader, QJson*) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +add_executable(logos-cpp-generator + main.cpp +) + +target_link_libraries(logos-cpp-generator PRIVATE Qt${QT_VERSION_MAJOR}::Core) + +target_include_directories(logos-cpp-generator PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Output directories +set_target_properties(logos-cpp-generator PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + + diff --git a/cpp-generator/compile.sh b/cpp-generator/compile.sh new file mode 100755 index 0000000..efc1fef --- /dev/null +++ b/cpp-generator/compile.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +BUILD_DIR="$REPO_ROOT/build/cpp-generator" +SRC_DIR="$REPO_ROOT/logos-cpp-sdk/cpp-generator" + +BUILD_TYPE="Release" +CMAKE_PREFIX_ARG="" + +for arg in "$@"; do + case "$arg" in + clean) + echo "Cleaning build directory: $BUILD_DIR" + if [ -d "$BUILD_DIR" ]; then + rm -rf "$BUILD_DIR" + fi + echo "Clean complete." + exit 0 + ;; + --debug) + BUILD_TYPE="Debug" + ;; + --release) + BUILD_TYPE="Release" + ;; + --prefix) + shift || true + if [ "${1-}" != "" ]; then + CMAKE_PREFIX_ARG="-DCMAKE_PREFIX_PATH=$1" + shift || true + fi + ;; + *) + ;; + esac +done + +if [ -n "${QT_DIR-}" ]; then + CMAKE_PREFIX_ARG="-DCMAKE_PREFIX_PATH=$QT_DIR" + echo "Using QT_DIR as CMAKE_PREFIX_PATH: $QT_DIR" +fi + +echo "Configuring (type=$BUILD_TYPE) ..." +cmake -S "$SRC_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ${CMAKE_PREFIX_ARG} + +echo "Building logos-cpp-generator ..." +cmake --build "$BUILD_DIR" --target logos-cpp-generator -j + +BIN_PATH="$BUILD_DIR/bin/logos-cpp-generator" +if [ -f "$BIN_PATH" ]; then + echo "Build succeeded: $BIN_PATH" +else + echo "Build finished, but binary not found at expected path: $BIN_PATH" + exit 1 +fi diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp new file mode 100644 index 0000000..8b604cb --- /dev/null +++ b/cpp-generator/main.cpp @@ -0,0 +1,822 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QJsonArray enumerateMethods(QObject* moduleInstance) +{ + QJsonArray methodsArray; + + if (!moduleInstance) { + return methodsArray; + } + + const QMetaObject* metaObject = moduleInstance->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()); + bool isInvokable = method.isValid() && (method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot); + methodObj["isInvokable"] = isInvokable; + + 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; +} + +static QString toPascalCase(const QString& name) +{ + QString out; + bool cap = true; + for (QChar c : name) { + if (!c.isLetterOrNumber()) { cap = true; continue; } + if (cap) { out.append(c.toUpper()); cap = false; } + else { out.append(c.toLower()); } + } + if (out.isEmpty()) return QString("Module"); + return out; +} + +static QString normalizeType(QString t) +{ + t = t.trimmed(); + if (t.startsWith("const ")) t = t.mid(6); + t = t.trimmed(); + // Drop reference and pointer qualifiers + if (t.endsWith('&') || t.endsWith('*')) t.chop(1); + t = t.trimmed(); + return t; +} + +static QString mapParamType(const QString& qtType) +{ + const QString base = normalizeType(qtType); + static const QSet known = { + "void","bool","int","double","float","QString","QStringList","QJsonArray","QVariant" + }; + if (known.contains(base)) return base; + // Fallback to QVariant for unknown types + return QString("QVariant"); +} + +static QString mapReturnType(const QString& qtType) +{ + const QString base = normalizeType(qtType); + if (base.isEmpty() || base == "void") return QString("void"); + static const QSet known = { + "bool","int","double","float","QString","QStringList","QJsonArray","QVariant" + }; + if (known.contains(base)) return base; + return QString("QVariant"); +} + +static QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods) +{ + QString h; + QTextStream s(&h); + s << "#pragma once\n"; + s << "#include \n"; + 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 << "class " << className << " {\n"; + s << "public:\n"; + s << " explicit " << className << "(LogosAPI* api);\n\n"; + s << " using RawEventCallback = std::function;\n"; + 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 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 << " template\n"; + s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n"; + s << " trigger(eventName, source, packVariantList(std::forward(args)...));\n"; + s << " }\n\n"; + // Methods + for (const QJsonValue& v : methods) { + const QJsonObject o = v.toObject(); + const bool invokable = o.value("isInvokable").toBool(); + if (!invokable) continue; + const QString name = o.value("name").toString(); + const QString ret = mapReturnType(o.value("returnType").toString()); + s << " " << ret << " " << name << "("; + QJsonArray params = o.value("parameters").toArray(); + for (int i = 0; i < params.size(); ++i) { + QJsonObject p = params.at(i).toObject(); + QString pt = mapParamType(p.value("type").toString()); + QString pn = p.value("name").toString(); + if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") { + s << "const " << pt << "& " << pn; + } else { + s << pt << " " << pn; + } + if (i + 1 < params.size()) s << ", "; + } + s << ");\n"; + } + s << "\nprivate:\n"; + s << " QObject* ensureReplica();\n"; + s << " template\n"; + s << " static QVariantList packVariantList(Args&&... args) {\n"; + s << " QVariantList list;\n"; + s << " list.reserve(sizeof...(Args));\n"; + s << " using Expander = int[];\n"; + s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward(args))), 0)...};\n"; + s << " return list;\n"; + s << " }\n"; + 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 << "};\n"; + return h; +} + +static QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods) +{ + QString c; + QTextStream s(&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 << " if (!m_eventReplica) {\n"; + s << " QObject* 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 << "}\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 << " if (!origin) {\n"; + s << " return false;\n"; + s << " }\n"; + s << " m_client->onEvent(origin, nullptr, eventName, callback);\n"; + s << " return true;\n"; + s << "}\n\n"; + s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n"; + s << " if (!callback) {\n"; + s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n"; + s << " return false;\n"; + s << " }\n"; + s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n"; + s << " callback(data);\n"; + s << " });\n"; + s << "}\n\n"; + s << "void " << className << "::setEventSource(QObject* source) {\n"; + s << " m_eventSource = source;\n"; + s << "}\n\n"; + s << "QObject* " << className << "::eventSource() const {\n"; + s << " return m_eventSource.data();\n"; + s << "}\n\n"; + s << "void " << className << "::trigger(const QString& eventName) {\n"; + s << " trigger(eventName, QVariantList{});\n"; + s << "}\n\n"; + s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n"; + s << " if (!m_eventSource) {\n"; + 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 << "}\n\n"; + s << "void " << className << "::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n"; + s << " if (!source) {\n"; + s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n"; + s << " return;\n"; + s << " }\n"; + s << " m_client->onEventResponse(source, eventName, data);\n"; + s << "}\n\n"; + for (const QJsonValue& v : methods) { + const QJsonObject o = v.toObject(); + const bool invokable = o.value("isInvokable").toBool(); + if (!invokable) continue; + const QString name = o.value("name").toString(); + const QString ret = mapReturnType(o.value("returnType").toString()); + QJsonArray params = o.value("parameters").toArray(); + // Signature + s << ret << " " << className << "::" << name << "("; + for (int i = 0; i < params.size(); ++i) { + QJsonObject p = params.at(i).toObject(); + QString pt = mapParamType(p.value("type").toString()); + QString pn = p.value("name").toString(); + if (pt == "QString" || pt == "QStringList" || pt == "QJsonArray") { + s << "const " << pt << "& " << pn; + } else { + s << pt << " " << pn; + } + if (i + 1 < params.size()) s << ", "; + } + s << ") {\n"; + // Body: perform call + if (ret != "void") { + s << " QVariant _result = "; + } else { + s << " "; + } + if (params.size() == 0) { + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\");\n"; + } else if (params.size() == 1) { + QJsonObject p = params.at(0).toObject(); + QString pn = p.value("name").toString(); + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << pn << ");\n"; + } else if (params.size() == 2) { + QString p0 = params.at(0).toObject().value("name").toString(); + QString p1 = params.at(1).toObject().value("name").toString(); + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ");\n"; + } else if (params.size() == 3) { + QString p0 = params.at(0).toObject().value("name").toString(); + QString p1 = params.at(1).toObject().value("name").toString(); + QString p2 = params.at(2).toObject().value("name").toString(); + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ");\n"; + } else if (params.size() == 4) { + QString p0 = params.at(0).toObject().value("name").toString(); + QString p1 = params.at(1).toObject().value("name").toString(); + QString p2 = params.at(2).toObject().value("name").toString(); + QString p3 = params.at(3).toObject().value("name").toString(); + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ");\n"; + } else if (params.size() == 5) { + QString p0 = params.at(0).toObject().value("name").toString(); + QString p1 = params.at(1).toObject().value("name").toString(); + QString p2 = params.at(2).toObject().value("name").toString(); + QString p3 = params.at(3).toObject().value("name").toString(); + QString p4 = params.at(4).toObject().value("name").toString(); + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", " << p0 << ", " << p1 << ", " << p2 << ", " << p3 << ", " << p4 << ");\n"; + } else { + s << "m_client->invokeRemoteMethod(\"" << moduleName << "\", \"" << name << "\", QVariantList{"; + for (int i = 0; i < params.size(); ++i) { + QString pn = params.at(i).toObject().value("name").toString(); + s << pn; + if (i + 1 < params.size()) s << ", "; + } + s << "});\n"; + } + // Return conversion + if (ret == "void") { + // nothing + } else if (ret == "bool") { + s << " return _result.toBool();\n"; + } else if (ret == "int") { + s << " return _result.toInt();\n"; + } else if (ret == "double") { + s << " return _result.toDouble();\n"; + } else if (ret == "float") { + s << " return _result.toFloat();\n"; + } else if (ret == "QString") { + s << " return _result.toString();\n"; + } else if (ret == "QStringList") { + s << " return _result.toStringList();\n"; + } else if (ret == "QJsonArray") { + s << " return qvariant_cast(_result);\n"; + } else { // QVariant + s << " return _result;\n"; + } + s << "}\n\n"; + } + return c; +} + +static QString makeCoreManagerHeader() +{ + QString h; + QTextStream s(&h); + s << "#pragma once\n"; + s << "#include \n"; + 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 << "class CoreManager {\n"; + s << "public:\n"; + s << " explicit CoreManager(LogosAPI* api);\n\n"; + s << " using RawEventCallback = std::function;\n"; + 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 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 << " template\n"; + s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n"; + s << " trigger(eventName, source, packVariantList(std::forward(args)...));\n"; + s << " }\n\n"; + s << " void initialize(int argc, char* argv[]);\n"; + s << " void setPluginsDirectory(const QString& directory);\n"; + s << " void start();\n"; + s << " void cleanup();\n"; + s << " QStringList getLoadedPlugins();\n"; + s << " QJsonArray getKnownPlugins();\n"; + s << " QJsonArray getPluginMethods(const QString& pluginName);\n"; + s << " void helloWorld();\n"; + s << " bool loadPlugin(const QString& pluginName);\n"; + s << " bool unloadPlugin(const QString& pluginName);\n"; + s << " QString processPlugin(const QString& filePath);\n\n"; + s << "private:\n"; + s << " QObject* ensureReplica();\n"; + s << " template\n"; + s << " static QVariantList packVariantList(Args&&... args) {\n"; + s << " QVariantList list;\n"; + s << " list.reserve(sizeof...(Args));\n"; + s << " using Expander = int[];\n"; + s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward(args))), 0)...};\n"; + s << " return list;\n"; + s << " }\n"; + 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 << "};\n"; + return h; +} + +static QString makeCoreManagerSource(const QString& headerBaseName) +{ + QString c; + QTextStream s(&c); + s << "#include \"" << headerBaseName << "\"\n\n"; + 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 << " if (!m_eventReplica) {\n"; + s << " QObject* 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 << "}\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 << " if (!origin) {\n"; + s << " return false;\n"; + s << " }\n"; + s << " m_client->onEvent(origin, nullptr, eventName, callback);\n"; + s << " return true;\n"; + s << "}\n\n"; + s << "bool CoreManager::on(const QString& eventName, EventCallback callback) {\n"; + s << " if (!callback) {\n"; + s << " qWarning() << \"CoreManager: ignoring empty event callback for\" << eventName;\n"; + s << " return false;\n"; + s << " }\n"; + s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n"; + s << " callback(data);\n"; + s << " });\n"; + s << "}\n\n"; + s << "void CoreManager::setEventSource(QObject* source) {\n"; + s << " m_eventSource = source;\n"; + s << "}\n\n"; + s << "QObject* CoreManager::eventSource() const {\n"; + s << " return m_eventSource.data();\n"; + s << "}\n\n"; + s << "void CoreManager::trigger(const QString& eventName) {\n"; + s << " trigger(eventName, QVariantList{});\n"; + s << "}\n\n"; + s << "void CoreManager::trigger(const QString& eventName, const QVariantList& data) {\n"; + s << " if (!m_eventSource) {\n"; + 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 << "}\n\n"; + s << "void CoreManager::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n"; + s << " if (!source) {\n"; + s << " qWarning() << \"CoreManager: cannot trigger\" << eventName << \"with null source\";\n"; + s << " return;\n"; + s << " }\n"; + s << " m_client->onEventResponse(source, eventName, data);\n"; + s << "}\n\n"; + s << "void CoreManager::initialize(int argc, char* argv[]) {\n"; + s << " QStringList args;\n"; + s << " if (argv) {\n"; + s << " for (int i = 0; i < argc; ++i) {\n"; + s << " args << QString::fromUtf8(argv[i] ? argv[i] : \"\");\n"; + s << " }\n"; + s << " }\n"; + s << " m_client->invokeRemoteMethod(\"core_manager\", \"initialize\", argc, args);\n"; + s << "}\n\n"; + s << "void CoreManager::setPluginsDirectory(const QString& directory) {\n"; + s << " m_client->invokeRemoteMethod(\"core_manager\", \"setPluginsDirectory\", directory);\n"; + s << "}\n\n"; + s << "void CoreManager::start() {\n"; + s << " m_client->invokeRemoteMethod(\"core_manager\", \"start\");\n"; + s << "}\n\n"; + s << "void CoreManager::cleanup() {\n"; + s << " m_client->invokeRemoteMethod(\"core_manager\", \"cleanup\");\n"; + s << "}\n\n"; + s << "QStringList CoreManager::getLoadedPlugins() {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"getLoadedPlugins\");\n"; + s << " return _result.toStringList();\n"; + s << "}\n\n"; + s << "QJsonArray CoreManager::getKnownPlugins() {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"getKnownPlugins\");\n"; + s << " return qvariant_cast(_result);\n"; + s << "}\n\n"; + s << "QJsonArray CoreManager::getPluginMethods(const QString& pluginName) {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"getPluginMethods\", pluginName);\n"; + s << " return qvariant_cast(_result);\n"; + s << "}\n\n"; + s << "void CoreManager::helloWorld() {\n"; + s << " m_client->invokeRemoteMethod(\"core_manager\", \"helloWorld\");\n"; + s << "}\n\n"; + s << "bool CoreManager::loadPlugin(const QString& pluginName) {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"loadPlugin\", pluginName);\n"; + s << " return _result.toBool();\n"; + s << "}\n\n"; + s << "bool CoreManager::unloadPlugin(const QString& pluginName) {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"unloadPlugin\", pluginName);\n"; + s << " return _result.toBool();\n"; + s << "}\n\n"; + s << "QString CoreManager::processPlugin(const QString& filePath) {\n"; + s << " QVariant _result = m_client->invokeRemoteMethod(\"core_manager\", \"processPlugin\", filePath);\n"; + s << " return _result.toString();\n"; + s << "}\n\n"; + return c; +} + +static bool ensureCoreManagerWrapper(const QString& genDirPath, QTextStream& err) +{ + const QString headerRel = QStringLiteral("core_manager_api.h"); + const QString sourceRel = QStringLiteral("core_manager_api.cpp"); + const QString headerAbs = QDir(genDirPath).filePath(headerRel); + const QString sourceAbs = QDir(genDirPath).filePath(sourceRel); + + QString header = makeCoreManagerHeader(); + QString source = makeCoreManagerSource(headerRel); + + QFile headerFile(headerAbs); + if (!headerFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write core manager header: " << headerAbs << "\n"; + return false; + } + headerFile.write(header.toUtf8()); + headerFile.close(); + + QFile sourceFile(sourceAbs); + if (!sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write core manager source: " << sourceAbs << "\n"; + return false; + } + sourceFile.write(source.toUtf8()); + sourceFile.close(); + + return true; +} + +static bool writeUmbrellaHeader(const QString& genDirPath, QTextStream& err) +{ + // Generate logos-cpp-sdk/cpp/generated/logos_sdk.h that includes all *_api.h in this dir + QDir genDir(genDirPath); + QStringList headers = genDir.entryList(QStringList() << "*_api.h", QDir::Files | QDir::Readable); + QString content; + QTextStream s(&content); + s << "#pragma once\n"; + s << "#include \"logos_api.h\"\n"; + s << "#include \"logos_api_client.h\"\n\n"; + // Includes + for (const QString& h : headers) { + s << "#include \"" << h << "\"\n"; + } + s << "\n"; + // Convenience aggregator exposing module wrappers + s << "struct LogosModules {\n"; + s << " explicit LogosModules(LogosAPI* api) : api(api)"; + for (const QString& h : headers) { + QString base = h; + base.chop(QString("_api.h").size()); + QString className = toPascalCase(base); + s << ", \n " << base << "(api)"; + } + s << " {}\n"; + s << " LogosAPI* api;\n"; + for (const QString& h : headers) { + QString base = h; + base.chop(QString("_api.h").size()); + QString className = toPascalCase(base); + s << " " << className << " " << base << ";\n"; + } + s << "};\n"; + + QFile outFile(genDir.filePath("logos_sdk.h")); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write umbrella header: " << outFile.fileName() << "\n"; + return false; + } + outFile.write(content.toUtf8()); + outFile.close(); + return true; +} + +static bool writeUmbrellaSource(const QString& genDirPath, QTextStream& err) +{ + // Generate logos-cpp-sdk/cpp/generated/logos_sdk.cpp that includes all *_api.cpp in this dir + QDir genDir(genDirPath); + QStringList sources = genDir.entryList(QStringList() << "*_api.cpp", QDir::Files | QDir::Readable); + QString content; + QTextStream s(&content); + s << "#include \"logos_sdk.h\"\n\n"; + for (const QString& c : sources) { + s << "#include \"" << c << "\"\n"; + } + s << "\n"; + + QFile outFile(genDir.filePath("logos_sdk.cpp")); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write umbrella source: " << outFile.fileName() << "\n"; + return false; + } + outFile.write(content.toUtf8()); + outFile.close(); + return true; +} + +static int generateFromPlugin(const QString& pluginInputPath, QTextStream& out, QTextStream& err) +{ + QFileInfo fi(pluginInputPath); + if (!fi.exists()) { + err << "Plugin file does not exist: " << pluginInputPath << "\n"; + return 2; + } + + QString resolvedPath = fi.canonicalFilePath(); + if (resolvedPath.isEmpty()) { + resolvedPath = fi.absoluteFilePath(); + } + + QString genDirPath = QDir::current().filePath("logos-cpp-sdk/cpp/generated"); + QDir().mkpath(genDirPath); + if (!ensureCoreManagerWrapper(genDirPath, err)) { + return 9; + } + + QPluginLoader loader(resolvedPath); + if (!loader.load()) { + err << "Failed to load plugin at " << resolvedPath << ": " << loader.errorString() << "\n"; + return 3; + } + QObject* instance = loader.instance(); + if (!instance) { + err << "Plugin loaded but no instance could be created for " << resolvedPath << "\n"; + loader.unload(); + return 4; + } + + QString moduleName; + { + QJsonObject md = loader.metaData(); + QJsonObject meta = md.value("MetaData").toObject(); + moduleName = meta.value("name").toString(); + if (moduleName.isEmpty()) { + moduleName = QFileInfo(resolvedPath).baseName(); + } + } + + QJsonArray methods = enumerateMethods(instance); + + QString className = toPascalCase(moduleName); + QString headerRel = QString("%1_api.h").arg(moduleName); + QString sourceRel = QString("%1_api.cpp").arg(moduleName); + QString headerAbs = QDir(genDirPath).filePath(headerRel); + QString sourceAbs = QDir(genDirPath).filePath(sourceRel); + + QString header = makeHeader(moduleName, className, methods); + QString source = makeSource(moduleName, className, headerRel, methods); + + { + QFile f(headerAbs); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write header: " << headerAbs << "\n"; + loader.unload(); + return 5; + } + f.write(header.toUtf8()); + f.close(); + } + { + QFile f(sourceAbs); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write source: " << sourceAbs << "\n"; + loader.unload(); + return 6; + } + f.write(source.toUtf8()); + f.close(); + } + + if (!writeUmbrellaHeader(genDirPath, err)) { + loader.unload(); + return 7; + } + if (!writeUmbrellaSource(genDirPath, err)) { + loader.unload(); + return 8; + } + + QJsonDocument doc(methods); + // out << doc.toJson(QJsonDocument::Indented) << "\n"; + out << "Generated: logos-cpp-sdk/cpp/generated/" << headerRel << " and logos-cpp-sdk/cpp/generated/" << sourceRel << "\n"; + out.flush(); + + loader.unload(); + return 0; +} + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + + QTextStream err(stderr); + QTextStream out(stdout); + + // Support: extract dependencies from a metadata.json file + { + const QStringList args = app.arguments(); + const int metaIdx = args.indexOf("--metadata"); + if (metaIdx != -1) { + if (metaIdx + 1 >= args.size()) { + err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json\n"; + return 1; + } + QString metaPathArg = args.at(metaIdx + 1); + if (metaPathArg.startsWith('@')) { + metaPathArg.remove(0, 1); + } + QFileInfo mfi(metaPathArg); + if (!mfi.exists()) { + err << "Metadata file does not exist: " << metaPathArg << "\n"; + return 2; + } + QString metaResolvedPath = mfi.canonicalFilePath(); + if (metaResolvedPath.isEmpty()) { + metaResolvedPath = mfi.absoluteFilePath(); + } + QFile mf(metaResolvedPath); + if (!mf.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << "Failed to open metadata file: " << metaResolvedPath << "\n"; + return 3; + } + const QByteArray jsonData = mf.readAll(); + mf.close(); + QJsonParseError parseError; + const QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) { + err << "Invalid metadata JSON in " << metaResolvedPath << ": " << parseError.errorString() << "\n"; + return 4; + } + const QJsonObject obj = doc.object(); + const QJsonArray deps = obj.value("dependencies").toArray(); + + // If --module-dir provided, generate for each dependency; else print deps + const int modDirIdx = args.indexOf("--module-dir"); + if (modDirIdx != -1) { + if (modDirIdx + 1 >= args.size()) { + err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /path/to/metadata.json --module-dir /path/to/modules_dir\n"; + return 1; + } + QString moduleDirArg = args.at(modDirIdx + 1); + if (moduleDirArg.startsWith('@')) { + moduleDirArg.remove(0, 1); + } + QDir moduleDir(moduleDirArg); + if (!moduleDir.exists()) { + err << "Module directory does not exist: " << moduleDirArg << "\n"; + return 2; + } + + QString genDirPath = QDir::current().filePath("logos-cpp-sdk/cpp/generated"); + QDir().mkpath(genDirPath); + if (!ensureCoreManagerWrapper(genDirPath, err)) { + return 9; + } + + QString suffix; +#if defined(Q_OS_MACOS) + suffix = ".dylib"; +#elif defined(Q_OS_LINUX) + suffix = ".so"; +#elif defined(Q_OS_WIN) + suffix = ".dll"; +#else + suffix = ""; +#endif + + int overallStatus = 0; + for (const QJsonValue& v : deps) { + if (!v.isString()) continue; + const QString depName = v.toString(); + const QString pluginFileName = depName + "_plugin" + suffix; + const QString pluginPath = moduleDir.filePath(pluginFileName); + if (!QFileInfo::exists(pluginPath)) { + err << "Skipping: plugin not found for dependency '" << depName << "' at " << pluginPath << "\n"; + continue; + } + out << "Running generator for dependency plugin: " << pluginPath << "\n"; + const int st = generateFromPlugin(pluginPath, out, err); + if (st != 0) { + overallStatus = st; // remember last non-zero + } + } + if (overallStatus == 0) { + if (!writeUmbrellaHeader(genDirPath, err)) { + overallStatus = 7; + } else if (!writeUmbrellaSource(genDirPath, err)) { + overallStatus = 8; + } + } + return overallStatus; + } else { + for (const QJsonValue& v : deps) { + if (v.isString()) { + out << v.toString() << "\n"; + } + } + out.flush(); + return 0; + } + } + } + + if (app.arguments().size() < 2) { + err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " /absolute/path/to/plugin\n"; + err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json\n"; + return 1; + } + + QString argPath = app.arguments().at(1); + return generateFromPlugin(argPath, out, err); +} diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..38189b8 --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.14) +project(LogosSDK) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +# Find Qt packages +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core RemoteObjects) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core RemoteObjects) + +# SDK sources +set(SDK_SOURCES + 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 + module_proxy.cpp + module_proxy.h + token_manager.cpp + token_manager.h +) + +# Create the SDK library as STATIC instead of SHARED +add_library(logos_sdk STATIC ${SDK_SOURCES}) + +# Link Qt libraries +target_link_libraries(logos_sdk PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::RemoteObjects) + +# Include directories +target_include_directories(logos_sdk PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +# Set output directories for static library +set_target_properties(logos_sdk PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" +) \ No newline at end of file diff --git a/cpp/compile.sh b/cpp/compile.sh new file mode 100755 index 0000000..e7e5a72 --- /dev/null +++ b/cpp/compile.sh @@ -0,0 +1,200 @@ +#!/bin/bash + +# Simple compilation test for LogosAPI +# This script compiles the LogosAPI files to check for syntax and compilation errors + +echo "Testing LogosAPI compilation..." + +# Find Qt installation +if [ -n "$QT_DIR" ]; then + QT_PATH="$QT_DIR" + echo "Using QT_DIR: $QT_PATH" +elif command -v qmake >/dev/null 2>&1; then + QT_PATH=$(qmake -query QT_INSTALL_PREFIX) + echo "Found Qt via qmake at: $QT_PATH" +else + echo "Error: QT_DIR not set and qmake not found. Please set QT_DIR environment variable or ensure Qt is installed and in PATH." + exit 1 +fi + +# Find MOC binary +if [ -f "$QT_PATH/bin/moc" ]; then + MOC_BIN="$QT_PATH/bin/moc" +elif [ -f "$QT_PATH/libexec/moc" ]; then + MOC_BIN="$QT_PATH/libexec/moc" +elif command -v moc >/dev/null 2>&1; then + MOC_BIN="moc" +else + echo "Error: MOC (Meta-Object Compiler) not found. Please ensure Qt development tools are installed." + exit 1 +fi + +echo "Using MOC: $MOC_BIN" + +# Set Qt include paths - handle both Qt5 and Qt6 on different platforms +if [ -d "$QT_PATH/lib" ]; then + # Qt6 style with lib directory (common on macOS) + # Add framework headers and the lib directory itself for framework-style includes + QT_INCLUDES="-F$QT_PATH/lib" + QT_INCLUDES="$QT_INCLUDES -I$QT_PATH/lib/QtCore.framework/Headers" + QT_INCLUDES="$QT_INCLUDES -I$QT_PATH/lib/QtRemoteObjects.framework/Headers" + # Also add the general include paths as fallback + QT_INCLUDES="$QT_INCLUDES -I$QT_PATH/include -I$QT_PATH/include/QtCore -I$QT_PATH/include/QtRemoteObjects" +else + # Standard include directory structure + QT_INCLUDES="-I$QT_PATH/include -I$QT_PATH/include/QtCore -I$QT_PATH/include/QtRemoteObjects" +fi + +echo "Using Qt includes: $QT_INCLUDES" + +# Compiler flags +CXXFLAGS="-std=c++17 -fPIC" + +# Generate MOC files for headers with Q_OBJECT +echo "Generating MOC files..." + +# List of headers that need MOC processing (contain Q_OBJECT) +MOC_HEADERS=( + "logos_api.h" + "logos_api_client.h" + "logos_api_provider.h" + "logos_api_consumer.h" + "module_proxy.h" + "token_manager.h" +) + +for header in "${MOC_HEADERS[@]}"; do + if [ -f "$header" ]; then + echo "Generating MOC for $header..." + $MOC_BIN $header -o "moc_${header%.h}.cpp" + if [ $? -ne 0 ]; then + echo "❌ MOC generation failed for $header" + exit 1 + fi + fi +done + +# Try to compile the headers (syntax check) +echo "Checking header syntax..." + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header logos_api.h -o /tmp/logos_api.h.gch +if [ $? -eq 0 ]; then + echo "✅ LogosAPI header syntax OK" + rm -f /tmp/logos_api.h.gch +else + echo "❌ LogosAPI header has syntax errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header logos_api_client.h -o /tmp/logos_api_client.h.gch +if [ $? -eq 0 ]; then + echo "✅ Client header syntax OK" + rm -f /tmp/logos_api_client.h.gch +else + echo "❌ Client header has syntax errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header logos_api_provider.h -o /tmp/logos_api_provider.h.gch +if [ $? -eq 0 ]; then + echo "✅ Provider header syntax OK" + rm -f /tmp/logos_api_provider.h.gch +else + echo "❌ Provider header has syntax errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header logos_api_consumer.h -o /tmp/logos_api_consumer.h.gch +if [ $? -eq 0 ]; then + echo "✅ Consumer header syntax OK" + rm -f /tmp/logos_api_consumer.h.gch +else + echo "❌ Consumer header has syntax errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header module_proxy.h -o /tmp/module_proxy.h.gch +if [ $? -eq 0 ]; then + echo "✅ Module proxy header syntax OK" + rm -f /tmp/module_proxy.h.gch +else + echo "❌ Module proxy header has syntax errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c -x c++-header token_manager.h -o /tmp/token_manager.h.gch +if [ $? -eq 0 ]; then + echo "✅ Token manager header syntax OK" + rm -f /tmp/token_manager.h.gch +else + echo "❌ Token manager header has syntax errors" + exit 1 +fi + +# Try to compile the implementations (without linking) +echo "Checking implementation syntax..." + +g++ $CXXFLAGS $QT_INCLUDES -c logos_api.cpp -o /tmp/logos_api.o +if [ $? -eq 0 ]; then + echo "✅ LogosAPI implementation compiles OK" + rm -f /tmp/logos_api.o +else + echo "❌ LogosAPI implementation has compilation errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c logos_api_client.cpp -o /tmp/logos_api_client.o +if [ $? -eq 0 ]; then + echo "✅ Client implementation compiles OK" + rm -f /tmp/logos_api_client.o +else + echo "❌ Client implementation has compilation errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c logos_api_provider.cpp -o /tmp/logos_api_provider.o +if [ $? -eq 0 ]; then + echo "✅ Provider implementation compiles OK" + rm -f /tmp/logos_api_provider.o +else + echo "❌ Provider implementation has compilation errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c logos_api_consumer.cpp -o /tmp/logos_api_consumer.o +if [ $? -eq 0 ]; then + echo "✅ Consumer implementation compiles OK" + rm -f /tmp/logos_api_consumer.o +else + echo "❌ Consumer implementation has compilation errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c module_proxy.cpp -o /tmp/module_proxy.o +if [ $? -eq 0 ]; then + echo "✅ Module proxy implementation compiles OK" + rm -f /tmp/module_proxy.o +else + echo "❌ Module proxy implementation has compilation errors" + exit 1 +fi + +g++ $CXXFLAGS $QT_INCLUDES -c token_manager.cpp -o /tmp/token_manager.o +if [ $? -eq 0 ]; then + echo "✅ Token manager implementation compiles OK" + rm -f /tmp/token_manager.o +else + echo "❌ Token manager implementation has compilation errors" + exit 1 +fi + +# Clean up generated MOC files +echo "Cleaning up generated MOC files..." +for header in "${MOC_HEADERS[@]}"; do + moc_file="moc_${header%.h}.cpp" + if [ -f "$moc_file" ]; then + rm -f "$moc_file" + fi +done + +echo "🎉 LogosAPI compilation test passed for all components!" \ No newline at end of file diff --git a/cpp/example_usage.cpp b/cpp/example_usage.cpp new file mode 100644 index 0000000..cf3d205 --- /dev/null +++ b/cpp/example_usage.cpp @@ -0,0 +1,105 @@ +/** + * @file example_usage.cpp + * @brief Example showing how to use the LogosAPI SDK + * + * This example demonstrates how to use the LogosAPI to connect + * to the Logos Core registry and request remote objects. + */ + +#include "logos_api_client.h" +#include "token_manager.h" +#include +#include +#include + +void exampleUsage() +{ + // Create a client instance (uses default registry URL) + LogosAPIClient client("core_manager", "example"); + + // Check if connected + if (!client.isConnected()) { + qWarning() << "Failed to connect to Logos Core registry"; + return; + } + + // Request the Core Manager object + QRemoteObjectReplica* coreManager = client.requestObject("Core Manager"); + if (!coreManager) { + qWarning() << "Failed to acquire Core Manager replica"; + return; + } + + // Use the replica to call methods + QString pluginName; + bool success = QMetaObject::invokeMethod( + coreManager, + "processPlugin", + Qt::DirectConnection, + Q_RETURN_ARG(QString, pluginName), + Q_ARG(QString, "/path/to/plugin.dylib") + ); + + if (success && !pluginName.isEmpty()) { + qDebug() << "Successfully processed plugin:" << pluginName; + } else { + qWarning() << "Failed to process plugin"; + } + + // Clean up the replica when done + delete coreManager; +} + +// Alternative usage with custom registry URL and timeout +void exampleCustomUsage() +{ + // Create client with custom registry URL + LogosAPI client("custom_registry"); + + if (!client.isConnected()) { + // Try to reconnect + if (!client.reconnect()) { + qWarning() << "Failed to connect to custom registry"; + return; + } + } + + // Request object with custom timeout (10 seconds) + QRemoteObjectReplica* someObject = client.requestObject("Some Object", 10000); + if (someObject) { + // Use the object... + + // Clean up + delete someObject; + } + + // Example TokenManager usage + TokenManager& tokenManager = TokenManager::instance(); + + // Save some tokens + tokenManager.saveToken("auth_token", "abc123xyz"); + tokenManager.saveToken("refresh_token", "def456uvw"); + tokenManager.saveToken("session_token", "ghi789rst"); + + // Retrieve tokens + QString authToken = tokenManager.getToken("auth_token"); + qDebug() << "Auth token:" << authToken; + + // Check if token exists + if (tokenManager.hasToken("refresh_token")) { + qDebug() << "Refresh token exists"; + } + + // Get all token keys + QList keys = tokenManager.getTokenKeys(); + qDebug() << "Token keys:" << keys; + qDebug() << "Total tokens:" << tokenManager.tokenCount(); + + // Remove a token + if (tokenManager.removeToken("session_token")) { + qDebug() << "Session token removed"; + } + + // Clear all tokens when done + // tokenManager.clearAllTokens(); +} \ No newline at end of file diff --git a/cpp/logos_api.cpp b/cpp/logos_api.cpp new file mode 100644 index 0000000..aae10d7 --- /dev/null +++ b/cpp/logos_api.cpp @@ -0,0 +1,49 @@ +#include "logos_api.h" +#include "logos_api_client.h" +#include "logos_api_provider.h" +#include "token_manager.h" + +LogosAPI::LogosAPI(const QString& module_name, QObject *parent) + : QObject(parent) + , m_module_name(module_name) + , m_provider(nullptr) + , m_token_manager(nullptr) +{ + // Initialize provider + m_provider = new LogosAPIProvider(m_module_name, this); + + // Get token manager instance + m_token_manager = &TokenManager::instance(); +} + +LogosAPI::~LogosAPI() +{ + // Provider and client will be automatically deleted as child objects + // Token manager is a singleton, so we don't delete it +} + +LogosAPIProvider* LogosAPI::getProvider() const +{ + return m_provider; +} + +LogosAPIClient* LogosAPI::getClient(const QString& target_module) const +{ + // Check if we already have a client for this target module + if (m_clients.contains(target_module)) { + return m_clients.value(target_module); + } + + // Create a new client for this target module + LogosAPIClient* client = new LogosAPIClient(target_module, m_module_name, m_token_manager, const_cast(this)); + + // Cache it for future use + m_clients.insert(target_module, client); + + return client; +} + +TokenManager* LogosAPI::getTokenManager() const +{ + return m_token_manager; +} diff --git a/cpp/logos_api.h b/cpp/logos_api.h new file mode 100644 index 0000000..3df2229 --- /dev/null +++ b/cpp/logos_api.h @@ -0,0 +1,60 @@ +#ifndef LOGOS_API_H +#define LOGOS_API_H + +#include +#include +#include + +class LogosAPIClient; +class LogosAPIProvider; +class TokenManager; + +/** + * @brief LogosAPI provides a unified interface to the Logos SDK + * + * This class initializes and keeps instances of the client provider and token manager. + */ +class LogosAPI : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Construct a new LogosAPI instance + * @param module_name The name of this module + * @param parent Parent QObject + */ + explicit LogosAPI(const QString& module_name, QObject *parent = nullptr); + + /** + * @brief Destructor + */ + ~LogosAPI(); + + /** + * @brief Get the client provider instance + * @return LogosAPIProvider* Pointer to the provider + */ + LogosAPIProvider* getProvider() const; + + /** + * @brief Get the client instance for communicating with a module + * @param target_module The module to communicate with + * @return LogosAPIClient* Pointer to the client + */ + LogosAPIClient* getClient(const QString& target_module) const; + + /** + * @brief Get the token manager instance + * @return TokenManager* Pointer to the token manager + */ + TokenManager* getTokenManager() const; + +private: + QString m_module_name; + LogosAPIProvider* m_provider; + mutable QHash m_clients; // Cache of clients per target module + TokenManager* m_token_manager; +}; + +#endif // LOGOS_API_H \ No newline at end of file diff --git a/cpp/logos_api_client.cpp b/cpp/logos_api_client.cpp new file mode 100644 index 0000000..d0d06c1 --- /dev/null +++ b/cpp/logos_api_client.cpp @@ -0,0 +1,179 @@ +#include "logos_api_client.h" +#include "logos_api_consumer.h" +#include "token_manager.h" + +LogosAPIClient::LogosAPIClient(const QString& module_to_talk_to, const QString& origin_module, TokenManager* token_manager, QObject *parent) + : QObject(parent) + , m_consumer(new LogosAPIConsumer(module_to_talk_to, origin_module, token_manager, this)) + , m_token_manager(token_manager) + , m_origin_module(origin_module) +{ +} + +LogosAPIClient::~LogosAPIClient() +{ + // m_consumer will be deleted automatically as it's a child object +} + +QObject* LogosAPIClient::requestObject(const QString& objectName, int timeoutMs) +{ + return m_consumer->requestObject(objectName, timeoutMs); +} + +bool LogosAPIClient::isConnected() const +{ + return m_consumer->isConnected(); +} + +QString LogosAPIClient::registryUrl() const +{ + return m_consumer->registryUrl(); +} + +bool LogosAPIClient::reconnect() +{ + return m_consumer->reconnect(); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariantList& args, int timeoutMs) +{ + qDebug() << "LogosAPIClient: invoking remote method" << objectName << methodName << args; + + // Get the token for the module + QString token = getToken(objectName); + + if (token.isEmpty() && objectName != "capability_module") { + qDebug() << "LogosAPIClient: calling requestModule for" << objectName; + 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, timeoutMs); + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "LogosAPIClient: requestModule result for" << objectName << ":" << result.toString(); + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + qDebug() << "================================================"; + + token = result.toString(); + } + + return m_consumer->invokeRemoteMethod(token, objectName, methodName, args, timeoutMs); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg, int timeoutMs) +{ + return invokeRemoteMethod(objectName, methodName, QVariantList() << arg, timeoutMs); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, int timeoutMs) +{ + return invokeRemoteMethod(objectName, methodName, QVariantList() << arg1 << arg2, timeoutMs); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, int timeoutMs) +{ + return invokeRemoteMethod(objectName, methodName, QVariantList() << arg1 << arg2 << arg3, timeoutMs); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, int timeoutMs) +{ + return invokeRemoteMethod(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4, timeoutMs); +} + +QVariant LogosAPIClient::invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, + const QVariant& arg4, const QVariant& arg5, int timeoutMs) +{ + return invokeRemoteMethod(objectName, methodName, QVariantList() << arg1 << arg2 << arg3 << arg4 << arg5, timeoutMs); +} + +void LogosAPIClient::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName, std::function callback) +{ + m_consumer->onEvent(originObject, destinationObject, eventName, callback); +} + +void LogosAPIClient::onEvent(QObject* originObject, QObject* destinationObject, const QString& eventName) +{ + 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; + + if (eventName.isEmpty()) { + qWarning() << "LogosAPIClient: Event name cannot be empty"; + return; + } + + // qDebug() << "LogosAPIClient: Emitting event:" << eventName << "with data:" << data; + qDebug() << "LogosAPIClient: Emitting event:" << eventName; + + // emit the eventResponse signal of replica + QMetaObject::invokeMethod(replica, "eventResponse", Qt::QueuedConnection, Q_ARG(QString, eventName), Q_ARG(QVariantList, data)); +} + +bool LogosAPIClient::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) +{ + return m_consumer->informModuleToken(authToken, moduleName, token); +} + +bool LogosAPIClient::informModuleToken_module(const QString& authToken, const QString& originModule, const QString& moduleName, const QString& token) +{ + return m_consumer->informModuleToken_module(authToken, originModule, moduleName, token); +} + +TokenManager* LogosAPIClient::getTokenManager() const +{ + return m_token_manager; +} + +QString LogosAPIClient::getToken(const QString& module_name) +{ + qDebug() << "getoken: -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"; + qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"; + // if (m_token_manager) { + qDebug() << "LogosAPIClient: printing keys"; + QList keys = m_token_manager->getTokenKeys(); + for (const QString& key : keys) { + qDebug() << "LogosAPIClient: Token key:" << key << "value:" << m_token_manager->getToken(key); + } + + 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 found for module:" << module_name; + } + // } else { + // qDebug() << "LogosAPIClient: No token manager found - using default AUTH_TOKEN"; + // } + + qDebug() << "LogosAPIClient: No stored token for module:" << module_name; + qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"; + + // TODO: this is breaking here for core_manager + // return AUTH_TOKEN; + return ""; +} \ No newline at end of file diff --git a/cpp/logos_api_client.h b/cpp/logos_api_client.h new file mode 100644 index 0000000..b218cdf --- /dev/null +++ b/cpp/logos_api_client.h @@ -0,0 +1,211 @@ +#ifndef LOGOS_API_CLIENT_H +#define LOGOS_API_CLIENT_H + +#include +#include +#include +#include +#include +#include + +class LogosAPIConsumer; +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. + */ +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 timeoutMs Timeout in milliseconds to wait for the replica to be ready + * @return QObject* pointer to the replica, or nullptr if failed + */ + QObject* requestObject(const QString& objectName, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @return QVariant containing the result, or invalid QVariant if failed + */ + QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariantList& args = QVariantList(), int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @return QVariant containing the result, or invalid QVariant if failed + */ + QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @return QVariant containing the result, or invalid QVariant if failed + */ + QVariant invokeRemoteMethod(const QString& objectName, const QString& methodName, + const QVariant& arg1, const QVariant& arg2, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @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, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @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, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @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, const QVariant& arg5, int timeoutMs = 20000); + + /** + * @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 + * @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, + 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 + * @param eventName The name of the event + * @param data The event data + */ + void onEventResponse(QObject* replica, 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); + + /** + * @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; + TokenManager* m_token_manager; + QString m_origin_module; +}; + +#endif // LOGOS_API_CLIENT_H \ No newline at end of file diff --git a/cpp/logos_api_consumer.cpp b/cpp/logos_api_consumer.cpp new file mode 100644 index 0000000..e1b6bcd --- /dev/null +++ b/cpp/logos_api_consumer.cpp @@ -0,0 +1,321 @@ +#include "logos_api_consumer.h" +#include "module_proxy.h" +#include "logos_api_client.h" +#include "token_manager.h" +#include +#include +#include +#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(QString("local:logos_%1").arg(module_to_talk_to)) + , m_connected(false) + , m_token_manager(token_manager) +{ + m_node = new QRemoteObjectNode(this); + connectToRegistry(); +} + +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, int timeoutMs) +{ + qDebug() << "LogosAPIConsumer: Requesting object:" << objectName << "at" << QTime::currentTime().toString("hh:mm:ss.zzz"); + if (!m_connected) { + qWarning() << "LogosAPIConsumer: Not connected to registry. Cannot request object:" << objectName; + return nullptr; + } + + if (objectName.isEmpty()) { + qWarning() << "LogosAPIConsumer: Object name cannot be empty"; + return nullptr; + } + + 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; + } + + // Wait for the replica to be initialized + if (!replica->waitForSource(timeoutMs)) { + 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; +} + +bool LogosAPIConsumer::isConnected() const +{ + return m_connected; +} + +QString LogosAPIConsumer::registryUrl() const +{ + return m_registryUrl; +} + +bool LogosAPIConsumer::reconnect() +{ + 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(); +} + +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, int timeoutMs) +{ + qDebug() << "LogosAPIConsumer: Calling invokeRemoteMethod with params:" << authToken << objectName << methodName << args << timeoutMs; + + // This method handles both ModuleProxy-wrapped modules (template_module, package_manager) + // and direct remote object calls for other modules + QObject* replica = requestObject(objectName, timeoutMs); + if (!replica) { + qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << objectName; + return QVariant(); + } + + // Try to cast to ModuleProxy first (in case the replica is a wrapped module) + ModuleProxy* moduleProxy = qobject_cast(replica); + if (moduleProxy) { + QVariant result = moduleProxy->callRemoteMethod(authToken, methodName, args); + delete replica; + return result; + } + + // Fallback: use QMetaObject::invokeMethod directly + // Note: Remote objects' callRemoteMethod returns QRemoteObjectPendingCall, not QVariant + QRemoteObjectPendingCall pendingCall; + bool success = QMetaObject::invokeMethod( + replica, + "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 replica; + return QVariant(); + } + + // Wait for the result + pendingCall.waitForFinished(timeoutMs); + delete replica; + + if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { + qWarning() << "LogosAPIConsumer: Remote callRemoteMethod failed or timed out:" << pendingCall.error(); + return QVariant(); + } + + return pendingCall.returnValue(); +} + +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); +} + +bool LogosAPIConsumer::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) +{ + qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token; + + // Request the ModuleProxy object + QObject* replica = requestObject("capability_module", 20000); + if (!replica) { + qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << "capability_module"; + return false; + } + + // Use QRemoteObjectPendingCall similar to invokeRemoteMethod + QRemoteObjectPendingCall pendingCall; + bool success = QMetaObject::invokeMethod( + replica, + "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 replica; + return false; + } + + // Wait for the result + pendingCall.waitForFinished(20000); + delete replica; + + if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { + qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); + return false; + } + + QVariant result = pendingCall.returnValue(); + qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; + + return result.toBool(); +} + +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; + + // Request the ModuleProxy object + QObject* replica = requestObject(originModule, 20000); + if (!replica) { + qWarning() << "LogosAPIConsumer: Failed to acquire replica for object:" << "capability_module"; + return false; + } + + // Use QRemoteObjectPendingCall similar to invokeRemoteMethod + QRemoteObjectPendingCall pendingCall; + bool success = QMetaObject::invokeMethod( + replica, + "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 replica; + return false; + } + + // Wait for the result + pendingCall.waitForFinished(20000); + delete replica; + + if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) { + qWarning() << "LogosAPIConsumer: Remote informModuleToken failed or timed out:" << pendingCall.error(); + return false; + } + + QVariant result = pendingCall.returnValue(); + qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result; + + return result.toBool(); +} diff --git a/cpp/logos_api_consumer.h b/cpp/logos_api_consumer.h new file mode 100644 index 0000000..ae2d787 --- /dev/null +++ b/cpp/logos_api_consumer.h @@ -0,0 +1,140 @@ +#ifndef LOGOS_API_CONSUMER_H +#define LOGOS_API_CONSUMER_H + +#include +#include +#include +#include +#include +#include +#include + +class QRemoteObjectNode; +class TokenManager; + +/** + * @brief LogosAPIConsumer handles connecting to remote 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 + */ +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 timeoutMs Timeout in milliseconds to wait for the replica to be ready + * @return QObject* pointer to the replica, or nullptr if failed + */ + QObject* requestObject(const QString& objectName, int timeoutMs = 20000); + + /** + * @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 timeoutMs Timeout in milliseconds to wait for the result + * @return QVariant containing the result, or invalid QVariant if failed + */ + QVariant invokeRemoteMethod(const QString& authToken, const QString& objectName, const QString& methodName, + const QVariantList& args = QVariantList(), int timeoutMs = 20000); + + /** + * @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 + * @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, + 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; + 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 diff --git a/cpp/logos_api_provider.cpp b/cpp/logos_api_provider.cpp new file mode 100644 index 0000000..a268864 --- /dev/null +++ b/cpp/logos_api_provider.cpp @@ -0,0 +1,117 @@ +#include "logos_api_provider.h" +#include "module_proxy.h" +#include "logos_api.h" +#include +#include +#include +#include + +LogosAPIProvider::LogosAPIProvider(const QString& module_name, QObject *parent) + : QObject(parent) + , m_registryHost(nullptr) + , m_registryUrl(QString("local:logos_%1").arg(module_name)) + , m_moduleProxy(nullptr) +{ +} + +LogosAPIProvider::~LogosAPIProvider() +{ + // QRemoteObjectRegistryHost will be deleted automatically as it's a child object + // ModuleProxy will be deleted automatically as it's a child object +} + +bool LogosAPIProvider::registerObject(const QString& name, QObject* object) +{ + if (!object) { + qWarning() << "LogosAPIProvider: Cannot register null object"; + return false; + } + + if (name.isEmpty()) { + qWarning() << "LogosAPIProvider: Cannot register object with empty name"; + 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"; + } + } else { + qDebug() << "LogosAPIProvider: Object does not have initLogos method, skipping"; + } + + m_moduleProxy = new ModuleProxy(object, this); + object = m_moduleProxy; + + if (!m_registryHost) { + m_registryHost = new QRemoteObjectRegistryHost(QUrl(m_registryUrl)); + if (!m_registryHost) { + qCritical() << "LogosAPIProvider: Failed to create registry host"; + return false; + } + qDebug() << "LogosAPIProvider: Created registry host with URL:" << m_registryUrl; + } + + bool success = m_registryHost->enableRemoting(object, name); + if (success) { + qDebug() << "LogosAPIProvider: Successfully registered object with name:" << name; + } else { + qCritical() << "LogosAPIProvider: Failed to register object with name:" << name; + } + + return success; +} + +QString LogosAPIProvider::registryUrl() const +{ + return m_registryUrl; +} + +bool LogosAPIProvider::saveToken(const QString& from_module_name, const QString& token) +{ + if (!m_moduleProxy) { + qWarning() << "LogosAPIProvider: Cannot save token - no module proxy available"; + return false; + } + + qDebug() << "LogosAPIProvider: Delegating saveToken call to module proxy for module:" << from_module_name; + return m_moduleProxy->saveToken(from_module_name, token); +} + +void LogosAPIProvider::onEventResponse(QObject* replica, const QString& eventName, const QVariantList& data) +{ + // qDebug() << "LogosAPIProvider: Received event:" << eventName << "with data:" << data; + qDebug() << "LogosAPIProvider: Received event:" << eventName; + + if (eventName.isEmpty()) { + qWarning() << "LogosAPIProvider: Event name cannot be empty"; + 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)); +} + diff --git a/cpp/logos_api_provider.h b/cpp/logos_api_provider.h new file mode 100644 index 0000000..c116417 --- /dev/null +++ b/cpp/logos_api_provider.h @@ -0,0 +1,79 @@ +#ifndef LOGOS_API_PROVIDER_H +#define LOGOS_API_PROVIDER_H + +#include +#include +#include +#include +#include + +class QRemoteObjectRegistryHost; +class ModuleProxy; + +/** + * @brief LogosAPIProvider handles registering objects for remote access + * + * This class is responsible for the provider/server side functionality: + * - Creating registry hosts + * - Registering objects for remote access + * - Handling event responses + */ +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 + */ + bool registerObject(const QString& name, QObject* object); + + /** + * @brief Get the registry URL for this provider + * @return QString containing the registry URL + */ + QString registryUrl() const; + + /** + * @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 + */ + 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); + +private: + QRemoteObjectRegistryHost* m_registryHost; + QString m_registryUrl; + QMap m_tokens; + ModuleProxy* m_moduleProxy; + + +}; + +#endif // LOGOS_API_PROVIDER_H \ No newline at end of file diff --git a/cpp/module_proxy.cpp b/cpp/module_proxy.cpp new file mode 100644 index 0000000..55ac5bb --- /dev/null +++ b/cpp/module_proxy.cpp @@ -0,0 +1,429 @@ +#include "module_proxy.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::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, "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) + : QObject(parent) + , m_module(module) +{ + // 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"; + } +} + +ModuleProxy::~ModuleProxy() +{ + qDebug() << "ModuleProxy: Destroyed for module:" << m_module; +} + +bool ModuleProxy::saveToken(const QString& from_module_name, const QString& token) +{ + if (from_module_name.isEmpty()) { + 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(); + 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; + return QVariant(); + } + + if (methodName.isEmpty()) { + qWarning() << "ModuleProxy: Method name cannot be empty"; + return QVariant(); + } + + 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 << "value:" << tokenManager->getToken(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()) { + // 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; +} + +bool ModuleProxy::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token) +{ + Q_UNUSED(authToken) // Authentication token validation can be added later + + // cast m_module to PluginInterface + 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; + } + + // save token + qDebug() << "ModuleProxy: Saving token for module:" << moduleName << "with token:" << token; + tokenManager->saveToken(moduleName, token); + qDebug() << "ModuleProxy: Token saved successfully"; + + return true; +} + +QJsonArray ModuleProxy::getPluginMethods() +{ + 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 for template instantiation +#include "moc_module_proxy.cpp" diff --git a/cpp/module_proxy.h b/cpp/module_proxy.h new file mode 100644 index 0000000..9b9af65 --- /dev/null +++ b/cpp/module_proxy.h @@ -0,0 +1,78 @@ +#ifndef MODULE_PROXY_H +#define MODULE_PROXY_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief ModuleProxy provides a proxy interface for module interactions + * + * This class serves as a proxy layer for communicating with modules + * in the Logos Core system. + */ +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 + */ + ~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; + QHash m_tokens; +}; + +#endif // MODULE_PROXY_H diff --git a/cpp/simple_example.cpp b/cpp/simple_example.cpp new file mode 100644 index 0000000..e039760 --- /dev/null +++ b/cpp/simple_example.cpp @@ -0,0 +1,33 @@ +/** + * @file simple_example.cpp + * @brief Simple example showing how to use the LogosAPI class + */ + +#include "logos_api.h" +#include "logos_api_client.h" +#include "logos_api_provider.h" +#include "token_manager.h" +#include + +void simpleExample() +{ + // Create a LogosAPI instance for our module + LogosAPI api("core"); + + // Get the provider and register an object + LogosAPIProvider* provider = api.getProvider(); + QObject* myService = new QObject(); + provider->registerObject("my_service", myService); + + // Get the client to communicate with other modules + LogosAPIClient* client = api.getClient("core_manager"); + // Use client to call remote methods... + + // Get the token manager and save tokens + TokenManager* tokenManager = api.getTokenManager(); + tokenManager->saveToken("auth_token", "abc123"); + + qDebug() << "LogosAPI initialized successfully!"; + + delete myService; +} \ No newline at end of file diff --git a/cpp/token_manager.cpp b/cpp/token_manager.cpp new file mode 100644 index 0000000..c4c8c14 --- /dev/null +++ b/cpp/token_manager.cpp @@ -0,0 +1,66 @@ +#include "token_manager.h" +#include + +TokenManager& TokenManager::instance() +{ + static TokenManager instance; + return instance; +} + +TokenManager::TokenManager(QObject *parent) + : QObject(parent) +{ +} + +TokenManager::~TokenManager() +{ +} + +void TokenManager::saveToken(const QString& key, const QString& token) +{ + QMutexLocker locker(&m_mutex); + m_tokens[key] = token; + emit tokenSaved(key); +} + +QString TokenManager::getToken(const QString& key) const +{ + QMutexLocker locker(&m_mutex); + return m_tokens.value(key, QString()); +} + +bool TokenManager::hasToken(const QString& key) const +{ + QMutexLocker locker(&m_mutex); + return m_tokens.contains(key); +} + +bool TokenManager::removeToken(const QString& key) +{ + QMutexLocker locker(&m_mutex); + if (m_tokens.contains(key)) { + m_tokens.remove(key); + emit tokenRemoved(key); + return true; + } + return false; +} + +void TokenManager::clearAllTokens() +{ + QMutexLocker locker(&m_mutex); + m_tokens.clear(); + emit allTokensCleared(); +} + +QList TokenManager::getTokenKeys() const +{ + QMutexLocker locker(&m_mutex); + return m_tokens.keys(); +} + +int TokenManager::tokenCount() const +{ + QMutexLocker locker(&m_mutex); + return m_tokens.size(); +} \ No newline at end of file diff --git a/cpp/token_manager.h b/cpp/token_manager.h new file mode 100644 index 0000000..cc720ba --- /dev/null +++ b/cpp/token_manager.h @@ -0,0 +1,116 @@ +#ifndef TOKEN_MANAGER_H +#define TOKEN_MANAGER_H + +#include +#include +#include +#include + +/** + * @brief TokenManager provides a singleton interface for managing authentication tokens + * + * This class manages a collection of tokens identified by keys, providing thread-safe + * access to store, retrieve, and manage tokens throughout the application lifecycle. + */ +class TokenManager : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Get the singleton instance of TokenManager + * @return TokenManager& Reference to the singleton instance + */ + static TokenManager& instance(); + + /** + * @brief Save a token with the given key + * @param key The identifier for the token + * @param token The token value to store + */ + void saveToken(const QString& key, const QString& token); + + /** + * @brief Retrieve a token by key + * @param key The identifier for the token + * @return QString The token value, or empty string if not found + */ + QString getToken(const QString& key) const; + + /** + * @brief Check if a token exists for the given key + * @param key The identifier to check + * @return bool True if token exists, false otherwise + */ + bool hasToken(const QString& key) const; + + /** + * @brief Remove a token by key + * @param key The identifier for the token to remove + * @return bool True if token was removed, false if it didn't exist + */ + bool removeToken(const QString& key); + + /** + * @brief Clear all tokens + */ + void clearAllTokens(); + + /** + * @brief Get all token keys + * @return QList List of all token keys + */ + QList getTokenKeys() const; + + /** + * @brief Get the number of stored tokens + * @return int Number of tokens stored + */ + int tokenCount() const; + +signals: + /** + * @brief Emitted when a token is saved + * @param key The key of the saved token + */ + void tokenSaved(const QString& key); + + /** + * @brief Emitted when a token is removed + * @param key The key of the removed token + */ + void tokenRemoved(const QString& key); + + /** + * @brief Emitted when all tokens are cleared + */ + void allTokensCleared(); + +private: + /** + * @brief Private constructor for singleton pattern + * @param parent Parent QObject + */ + explicit TokenManager(QObject *parent = nullptr); + + /** + * @brief Private destructor + */ + ~TokenManager(); + + // Delete copy constructor and assignment operator to enforce singleton + TokenManager(const TokenManager&) = delete; + TokenManager& operator=(const TokenManager&) = delete; + + /** + * @brief Hash map storing tokens by key + */ + QHash m_tokens; + + /** + * @brief Mutex for thread-safe access to tokens + */ + mutable QMutex m_mutex; +}; + +#endif // TOKEN_MANAGER_H \ No newline at end of file