mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
Finish Abstraction & Refactor (Ongoing) - part 1 (#25)
* refactor: abstract connection/transport; and clearly separate qt remote obj and qt local into separate implementations * abstract qt remote registry * add mock implementation; these serves to further test the abstraction but also useful for testing modules later * use LogosObject instead of QObject * abstract provider side * updates to use new api * re-add async api back --------- Co-authored-by: Logos Workspace <logos@workspace.local>
This commit is contained in:
co-authored by
Logos Workspace
parent
4b66dac015
commit
4197ee1830
@@ -0,0 +1,106 @@
|
||||
#ifndef LOGOS_MOCK_H
|
||||
#define LOGOS_MOCK_H
|
||||
|
||||
/**
|
||||
* @file logos_mock.h
|
||||
* @brief Convenience header for unit tests that use the mock transport.
|
||||
*
|
||||
* Include this file in test source files. It pulls in everything needed to
|
||||
* set up mock expectations and verify calls:
|
||||
*
|
||||
* #include "logos_mock.h"
|
||||
*
|
||||
* LOGOS_TEST(my_test) {
|
||||
* LogosMockSetup mock;
|
||||
* mock.when("other_module", "someMethod").thenReturn(QVariant(42));
|
||||
*
|
||||
* // ... exercise code under test ...
|
||||
*
|
||||
* LOGOS_ASSERT(mock.wasCalled("other_module", "someMethod"));
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "../../logos_mode.h"
|
||||
#include "../../token_manager.h"
|
||||
#include "mock_store.h"
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
|
||||
/**
|
||||
* @brief RAII guard that activates mock mode for the duration of a test.
|
||||
*
|
||||
* Construction:
|
||||
* - Switches the SDK to LogosMode::Mock.
|
||||
* - Resets MockStore (clears all expectations and call records).
|
||||
* - Clears the TokenManager singleton so that no stale tokens from
|
||||
* previous tests influence token lookup in LogosAPIClient.
|
||||
*
|
||||
* Destruction:
|
||||
* - Restores the previous LogosMode.
|
||||
* - Resets MockStore again (belt-and-suspenders cleanup).
|
||||
*
|
||||
* Token pre-seeding:
|
||||
* when() automatically registers a non-empty dummy token for the target
|
||||
* module so that LogosAPIClient::invokeRemoteMethod() does not attempt to
|
||||
* call capability_module.requestModule() before invoking the mock.
|
||||
*/
|
||||
class LogosMockSetup {
|
||||
public:
|
||||
LogosMockSetup()
|
||||
: m_previousMode(LogosModeConfig::getMode())
|
||||
{
|
||||
LogosModeConfig::setMode(LogosMode::Mock);
|
||||
MockStore::instance().reset();
|
||||
TokenManager::instance().clearAllTokens();
|
||||
}
|
||||
|
||||
~LogosMockSetup()
|
||||
{
|
||||
MockStore::instance().reset();
|
||||
LogosModeConfig::setMode(m_previousMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Register a mock expectation for module::method.
|
||||
*
|
||||
* Also seeds a dummy token for the module so LogosAPIClient does not
|
||||
* try to call capability_module.requestModule().
|
||||
*
|
||||
* @return A fluent builder to configure arguments and return value.
|
||||
*/
|
||||
MockStore::ExpectationBuilder when(const QString& module, const QString& method)
|
||||
{
|
||||
// Pre-seed a token so LogosAPIClient skips the capability_module lookup
|
||||
TokenManager::instance().saveToken(module, "mock-token-" + module);
|
||||
return MockStore::instance().when(module, method);
|
||||
}
|
||||
|
||||
// ── Verification helpers (delegates to MockStore) ────────────────────────
|
||||
|
||||
bool wasCalled(const QString& module, const QString& method) const
|
||||
{
|
||||
return MockStore::instance().wasCalled(module, method);
|
||||
}
|
||||
|
||||
bool wasCalledWith(const QString& module, const QString& method,
|
||||
const QVariantList& args) const
|
||||
{
|
||||
return MockStore::instance().wasCalledWith(module, method, args);
|
||||
}
|
||||
|
||||
int callCount(const QString& module, const QString& method) const
|
||||
{
|
||||
return MockStore::instance().callCount(module, method);
|
||||
}
|
||||
|
||||
QVariantList lastArgs(const QString& module, const QString& method) const
|
||||
{
|
||||
return MockStore::instance().lastArgs(module, method);
|
||||
}
|
||||
|
||||
private:
|
||||
LogosMode m_previousMode;
|
||||
};
|
||||
|
||||
#endif // LOGOS_MOCK_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MOCK_REGISTRY_H
|
||||
#define MOCK_REGISTRY_H
|
||||
|
||||
#include "../../logos_registry.h"
|
||||
|
||||
/**
|
||||
* @brief Trivial LogosRegistry implementation for mock mode.
|
||||
*
|
||||
* No real registry endpoint is needed in mock mode; this implementation
|
||||
* simply reports itself as initialized so that callers do not stall.
|
||||
*/
|
||||
class MockRegistry : public LogosRegistry {
|
||||
public:
|
||||
bool isInitialized() const override { return true; }
|
||||
};
|
||||
|
||||
#endif // MOCK_REGISTRY_H
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "mock_store.h"
|
||||
#include <QMutexLocker>
|
||||
#include <QDebug>
|
||||
|
||||
MockStore& MockStore::instance()
|
||||
{
|
||||
static MockStore s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void MockStore::reset()
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
m_expectations.clear();
|
||||
m_calls.clear();
|
||||
}
|
||||
|
||||
// ── ExpectationBuilder ───────────────────────────────────────────────────────
|
||||
|
||||
MockStore::ExpectationBuilder::ExpectationBuilder(MockStore& store,
|
||||
const QString& module,
|
||||
const QString& method)
|
||||
: m_store(store)
|
||||
{
|
||||
QMutexLocker lock(&store.m_mutex);
|
||||
MockExpectation exp;
|
||||
exp.module = module;
|
||||
exp.method = method;
|
||||
exp.matchAnyArgs = true;
|
||||
store.m_expectations.append(exp);
|
||||
m_index = store.m_expectations.size() - 1;
|
||||
}
|
||||
|
||||
MockStore::ExpectationBuilder& MockStore::ExpectationBuilder::withArgs(const QVariantList& args)
|
||||
{
|
||||
QMutexLocker lock(&m_store.m_mutex);
|
||||
m_store.m_expectations[m_index].expectedArgs = args;
|
||||
m_store.m_expectations[m_index].matchAnyArgs = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MockStore::ExpectationBuilder& MockStore::ExpectationBuilder::thenReturn(const QVariant& value)
|
||||
{
|
||||
QMutexLocker lock(&m_store.m_mutex);
|
||||
m_store.m_expectations[m_index].returnValue = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// ── MockStore ────────────────────────────────────────────────────────────────
|
||||
|
||||
MockStore::ExpectationBuilder MockStore::when(const QString& module, const QString& method)
|
||||
{
|
||||
return ExpectationBuilder(*this, module, method);
|
||||
}
|
||||
|
||||
QVariant MockStore::recordAndReturn(const QString& module, const QString& method,
|
||||
const QVariantList& args)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
MockCallRecord record;
|
||||
record.module = module;
|
||||
record.method = method;
|
||||
record.args = args;
|
||||
m_calls.append(record);
|
||||
|
||||
// Search expectations in reverse (last registered wins)
|
||||
for (int i = m_expectations.size() - 1; i >= 0; --i) {
|
||||
const MockExpectation& exp = m_expectations.at(i);
|
||||
if (exp.module != module || exp.method != method) continue;
|
||||
if (!exp.matchAnyArgs && exp.expectedArgs != args) continue;
|
||||
qDebug() << "MockStore: matched expectation for" << module << "::" << method
|
||||
<< "-> returning" << exp.returnValue;
|
||||
return exp.returnValue;
|
||||
}
|
||||
|
||||
qWarning() << "MockStore: no expectation registered for" << module << "::" << method
|
||||
<< "- returning invalid QVariant";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool MockStore::wasCalled(const QString& module, const QString& method) const
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
for (const MockCallRecord& r : m_calls) {
|
||||
if (r.module == module && r.method == method) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MockStore::wasCalledWith(const QString& module, const QString& method,
|
||||
const QVariantList& args) const
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
for (const MockCallRecord& r : m_calls) {
|
||||
if (r.module == module && r.method == method && r.args == args) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int MockStore::callCount(const QString& module, const QString& method) const
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
int count = 0;
|
||||
for (const MockCallRecord& r : m_calls) {
|
||||
if (r.module == module && r.method == method) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
QVariantList MockStore::lastArgs(const QString& module, const QString& method) const
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
for (int i = m_calls.size() - 1; i >= 0; --i) {
|
||||
const MockCallRecord& r = m_calls.at(i);
|
||||
if (r.module == module && r.method == method) return r.args;
|
||||
}
|
||||
return QVariantList();
|
||||
}
|
||||
|
||||
QList<MockCallRecord> MockStore::allCalls() const
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
return m_calls;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#ifndef MOCK_STORE_H
|
||||
#define MOCK_STORE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QVariantList>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
/**
|
||||
* @brief Records a single intercepted call to a mocked module method.
|
||||
*/
|
||||
struct MockCallRecord {
|
||||
QString module;
|
||||
QString method;
|
||||
QVariantList args;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stores a single configured expectation (module + method -> return value).
|
||||
*
|
||||
* If matchAnyArgs is true the expectation matches regardless of arguments.
|
||||
* Otherwise it only matches when args equal expectedArgs exactly.
|
||||
*/
|
||||
struct MockExpectation {
|
||||
QString module;
|
||||
QString method;
|
||||
QVariantList expectedArgs;
|
||||
QVariant returnValue;
|
||||
bool matchAnyArgs = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Singleton store that holds mock expectations and records calls.
|
||||
*
|
||||
* MockStore is the central registry used by MockTransportConnection.
|
||||
* Tests set up expectations via when() and verify them via wasCalled() etc.
|
||||
* Call reset() at the start of every test to clear state from previous runs.
|
||||
*/
|
||||
class MockStore {
|
||||
public:
|
||||
static MockStore& instance();
|
||||
|
||||
/**
|
||||
* @brief Remove all expectations and call records.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
// ── Fluent expectation builder ───────────────────────────────────────────
|
||||
|
||||
class ExpectationBuilder {
|
||||
public:
|
||||
ExpectationBuilder(MockStore& store, const QString& module, const QString& method);
|
||||
|
||||
/**
|
||||
* @brief Restrict this expectation to calls with exactly these arguments.
|
||||
*/
|
||||
ExpectationBuilder& withArgs(const QVariantList& args);
|
||||
|
||||
/**
|
||||
* @brief Set the value returned when the expectation is matched.
|
||||
*/
|
||||
ExpectationBuilder& thenReturn(const QVariant& value);
|
||||
|
||||
private:
|
||||
MockStore& m_store;
|
||||
int m_index; // index into m_expectations
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Begin configuring an expectation for module::method.
|
||||
*
|
||||
* Multiple calls to when() for the same module/method are allowed; the
|
||||
* last matching expectation wins (LIFO order).
|
||||
*/
|
||||
ExpectationBuilder when(const QString& module, const QString& method);
|
||||
|
||||
// ── Called by MockTransportConnection ───────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief Record a call and return the configured return value.
|
||||
*
|
||||
* If no expectation matches an invalid QVariant() is returned.
|
||||
*/
|
||||
QVariant recordAndReturn(const QString& module, const QString& method,
|
||||
const QVariantList& args);
|
||||
|
||||
// ── Verification helpers ─────────────────────────────────────────────────
|
||||
|
||||
bool wasCalled(const QString& module, const QString& method) const;
|
||||
bool wasCalledWith(const QString& module, const QString& method,
|
||||
const QVariantList& args) const;
|
||||
int callCount(const QString& module, const QString& method) const;
|
||||
QVariantList lastArgs(const QString& module, const QString& method) const;
|
||||
QList<MockCallRecord> allCalls() const;
|
||||
|
||||
private:
|
||||
MockStore() = default;
|
||||
MockStore(const MockStore&) = delete;
|
||||
MockStore& operator=(const MockStore&) = delete;
|
||||
|
||||
mutable QMutex m_mutex;
|
||||
QList<MockExpectation> m_expectations;
|
||||
QList<MockCallRecord> m_calls;
|
||||
|
||||
friend class ExpectationBuilder;
|
||||
};
|
||||
|
||||
#endif // MOCK_STORE_H
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "mock_transport.h"
|
||||
#include <QDebug>
|
||||
|
||||
// ── MockTransportHost ────────────────────────────────────────────────────────
|
||||
|
||||
bool MockTransportHost::publishObject(const QString& name, QObject* /*object*/)
|
||||
{
|
||||
qDebug() << "MockTransportHost: publishObject (no-op)" << name;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MockTransportHost::unpublishObject(const QString& name)
|
||||
{
|
||||
qDebug() << "MockTransportHost: unpublishObject (no-op)" << name;
|
||||
}
|
||||
|
||||
// ── MockTransportConnection ──────────────────────────────────────────────────
|
||||
|
||||
bool MockTransportConnection::connectToHost()
|
||||
{
|
||||
qDebug() << "MockTransportConnection: connectToHost (no-op, always connected)";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MockTransportConnection::isConnected() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MockTransportConnection::reconnect()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
LogosObject* MockTransportConnection::requestObject(const QString& objectName, int /*timeoutMs*/)
|
||||
{
|
||||
qDebug() << "MockTransportConnection: requestObject" << objectName;
|
||||
return new MockLogosObject(objectName);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef MOCK_TRANSPORT_H
|
||||
#define MOCK_TRANSPORT_H
|
||||
|
||||
#include "../../logos_transport.h"
|
||||
#include "../../logos_object.h"
|
||||
#include "mock_store.h"
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
/**
|
||||
* @brief LogosObject implementation for mock mode.
|
||||
*
|
||||
* Stores the module name and delegates callMethod to MockStore.
|
||||
* Event operations are no-ops in mock mode.
|
||||
*/
|
||||
class MockLogosObject : public LogosObject {
|
||||
public:
|
||||
explicit MockLogosObject(const QString& moduleName)
|
||||
: m_moduleName(moduleName) {}
|
||||
|
||||
const QString& moduleName() const { return m_moduleName; }
|
||||
|
||||
QVariant callMethod(const QString& /*authToken*/,
|
||||
const QString& methodName,
|
||||
const QVariantList& args,
|
||||
int /*timeoutMs*/) override
|
||||
{
|
||||
return MockStore::instance().recordAndReturn(m_moduleName, methodName, args);
|
||||
}
|
||||
|
||||
bool informModuleToken(const QString& /*authToken*/,
|
||||
const QString& moduleName,
|
||||
const QString& /*token*/,
|
||||
int /*timeoutMs*/) override
|
||||
{
|
||||
Q_UNUSED(moduleName)
|
||||
return true;
|
||||
}
|
||||
|
||||
void onEvent(const QString& /*eventName*/, EventCallback /*callback*/) override {}
|
||||
void disconnectEvents() override {}
|
||||
void emitEvent(const QString& /*eventName*/, const QVariantList& /*data*/) override {}
|
||||
|
||||
QJsonArray getMethods() override { return QJsonArray(); }
|
||||
|
||||
void release() override { delete this; }
|
||||
|
||||
quintptr id() const override { return reinterpret_cast<quintptr>(this); }
|
||||
|
||||
private:
|
||||
QString m_moduleName;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief No-op provider-side transport for mock mode.
|
||||
*
|
||||
* publishObject / unpublishObject succeed silently; there is no real
|
||||
* IPC endpoint and no object needs to be made available.
|
||||
*/
|
||||
class MockTransportHost : public LogosTransportHost {
|
||||
public:
|
||||
bool publishObject(const QString& name, QObject* object) override;
|
||||
void unpublishObject(const QString& name) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Consumer-side transport for mock mode.
|
||||
*
|
||||
* requestObject returns a MockLogosObject tagged with the module name.
|
||||
*/
|
||||
class MockTransportConnection : public LogosTransportConnection {
|
||||
public:
|
||||
bool connectToHost() override;
|
||||
bool isConnected() const override;
|
||||
bool reconnect() override;
|
||||
LogosObject* requestObject(const QString& objectName, int timeoutMs) override;
|
||||
};
|
||||
|
||||
#endif // MOCK_TRANSPORT_H
|
||||
@@ -0,0 +1,171 @@
|
||||
#include "local_transport.h"
|
||||
#include "../../plugin_registry.h"
|
||||
#include "../../module_proxy.h"
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
|
||||
// ── LocalLogosObject ─────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
class EventHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EventHelper(QObject* parent = nullptr) : QObject(parent) {}
|
||||
|
||||
void addCallback(const QString& eventName, LogosObject::EventCallback cb) {
|
||||
m_callbacks[eventName].append(std::move(cb));
|
||||
}
|
||||
|
||||
public slots:
|
||||
void onEventResponse(const QString& eventName, const QVariantList& data) {
|
||||
auto cbs = m_callbacks.value(eventName);
|
||||
if (!cbs.isEmpty()) {
|
||||
qDebug() << "[LogosObject] Local EventHelper: dispatching event" << eventName << "to" << cbs.size() << "callback(s)";
|
||||
}
|
||||
for (const auto& cb : cbs) {
|
||||
try { cb(eventName, data); } catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QHash<QString, QList<LogosObject::EventCallback>> m_callbacks;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class LocalLogosObject : public LogosObject {
|
||||
public:
|
||||
explicit LocalLogosObject(ModuleProxy* proxy)
|
||||
: m_proxy(proxy), m_helper(nullptr)
|
||||
{
|
||||
qDebug() << "[LogosObject] Created LocalLogosObject wrapping ModuleProxy" << reinterpret_cast<quintptr>(proxy);
|
||||
}
|
||||
|
||||
~LocalLogosObject() override {
|
||||
qDebug() << "[LogosObject] Destroying LocalLogosObject" << reinterpret_cast<quintptr>(m_proxy);
|
||||
delete m_helper;
|
||||
}
|
||||
|
||||
QVariant callMethod(const QString& authToken,
|
||||
const QString& methodName,
|
||||
const QVariantList& args,
|
||||
int /*timeoutMs*/) override
|
||||
{
|
||||
if (!m_proxy) return QVariant();
|
||||
qDebug() << "[LogosObject] LocalLogosObject::callMethod" << methodName << "args:" << args.size();
|
||||
return m_proxy->callRemoteMethod(authToken, methodName, args);
|
||||
}
|
||||
|
||||
bool informModuleToken(const QString& authToken,
|
||||
const QString& moduleName,
|
||||
const QString& token,
|
||||
int /*timeoutMs*/) override
|
||||
{
|
||||
if (!m_proxy) return false;
|
||||
return m_proxy->informModuleToken(authToken, moduleName, token);
|
||||
}
|
||||
|
||||
void onEvent(const QString& eventName, EventCallback callback) override
|
||||
{
|
||||
if (!m_proxy) return;
|
||||
|
||||
qDebug() << "[LogosObject] LocalLogosObject::onEvent subscribing to event:" << eventName;
|
||||
if (!m_helper) {
|
||||
m_helper = new EventHelper();
|
||||
QObject::connect(m_proxy, SIGNAL(eventResponse(QString,QVariantList)),
|
||||
m_helper, SLOT(onEventResponse(QString,QVariantList)));
|
||||
qDebug() << "[LogosObject] LocalLogosObject: connected EventHelper to ModuleProxy signals";
|
||||
}
|
||||
m_helper->addCallback(eventName, std::move(callback));
|
||||
}
|
||||
|
||||
void disconnectEvents() override
|
||||
{
|
||||
delete m_helper;
|
||||
m_helper = nullptr;
|
||||
}
|
||||
|
||||
void emitEvent(const QString& eventName, const QVariantList& data) override
|
||||
{
|
||||
if (!m_proxy) return;
|
||||
qDebug() << "[LogosObject] LocalLogosObject::emitEvent" << eventName << "data:" << data.size() << "items";
|
||||
QMetaObject::invokeMethod(m_proxy, "eventResponse",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, eventName),
|
||||
Q_ARG(QVariantList, data));
|
||||
}
|
||||
|
||||
QJsonArray getMethods() override
|
||||
{
|
||||
if (!m_proxy) return QJsonArray();
|
||||
return m_proxy->getPluginMethods();
|
||||
}
|
||||
|
||||
void release() override
|
||||
{
|
||||
// Local mode: we don't own the ModuleProxy, just stop using it
|
||||
disconnectEvents();
|
||||
}
|
||||
|
||||
quintptr id() const override { return reinterpret_cast<quintptr>(m_proxy); }
|
||||
|
||||
private:
|
||||
ModuleProxy* m_proxy;
|
||||
EventHelper* m_helper;
|
||||
};
|
||||
|
||||
// ── LocalTransportHost ───────────────────────────────────────────────────────
|
||||
|
||||
bool LocalTransportHost::publishObject(const QString& name, QObject* object)
|
||||
{
|
||||
PluginRegistry::registerPlugin(object, name);
|
||||
qDebug() << "LocalTransportHost: Published object:" << name;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalTransportHost::unpublishObject(const QString& name)
|
||||
{
|
||||
if (!name.isEmpty()) {
|
||||
PluginRegistry::unregisterPlugin(name);
|
||||
qDebug() << "LocalTransportHost: Unpublished object:" << name;
|
||||
}
|
||||
}
|
||||
|
||||
// ── LocalTransportConnection ─────────────────────────────────────────────────
|
||||
|
||||
bool LocalTransportConnection::connectToHost()
|
||||
{
|
||||
qDebug() << "LocalTransportConnection: Local mode - no connection needed";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalTransportConnection::isConnected() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalTransportConnection::reconnect()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
LogosObject* LocalTransportConnection::requestObject(const QString& objectName, int /*timeoutMs*/)
|
||||
{
|
||||
QObject* plugin = PluginRegistry::getPlugin<QObject>(objectName);
|
||||
if (!plugin) {
|
||||
qWarning() << "LocalTransportConnection: Plugin not found in registry:" << objectName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModuleProxy* proxy = qobject_cast<ModuleProxy*>(plugin);
|
||||
if (!proxy) {
|
||||
qWarning() << "LocalTransportConnection: Plugin is not a ModuleProxy:" << objectName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qDebug() << "[LogosObject] LocalTransportConnection: returning LocalLogosObject for:" << objectName;
|
||||
return new LocalLogosObject(proxy);
|
||||
}
|
||||
|
||||
#include "local_transport.moc"
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef LOCAL_TRANSPORT_H
|
||||
#define LOCAL_TRANSPORT_H
|
||||
|
||||
#include "../../logos_transport.h"
|
||||
#include "../../logos_object.h"
|
||||
|
||||
class ModuleProxy;
|
||||
|
||||
class LocalTransportHost : public LogosTransportHost {
|
||||
public:
|
||||
bool publishObject(const QString& name, QObject* object) override;
|
||||
void unpublishObject(const QString& name) override;
|
||||
};
|
||||
|
||||
class LocalTransportConnection : public LogosTransportConnection {
|
||||
public:
|
||||
bool connectToHost() override;
|
||||
bool isConnected() const override;
|
||||
bool reconnect() override;
|
||||
LogosObject* requestObject(const QString& objectName, int timeoutMs) override;
|
||||
};
|
||||
|
||||
#endif // LOCAL_TRANSPORT_H
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "qt_remote_registry.h"
|
||||
#include <QRemoteObjectRegistryHost>
|
||||
#include <QUrl>
|
||||
#include <QDebug>
|
||||
|
||||
QtRemoteRegistry::QtRemoteRegistry(const QString& url)
|
||||
: m_registryHost(nullptr)
|
||||
{
|
||||
m_registryHost = new QRemoteObjectRegistryHost(QUrl(url));
|
||||
|
||||
if (m_registryHost) {
|
||||
qDebug() << "QtRemoteRegistry: Registry host created at:" << url;
|
||||
} else {
|
||||
qCritical() << "QtRemoteRegistry: Failed to create registry host at:" << url;
|
||||
}
|
||||
}
|
||||
|
||||
QtRemoteRegistry::~QtRemoteRegistry()
|
||||
{
|
||||
delete m_registryHost;
|
||||
m_registryHost = nullptr;
|
||||
qDebug() << "QtRemoteRegistry: Registry host destroyed";
|
||||
}
|
||||
|
||||
bool QtRemoteRegistry::isInitialized() const
|
||||
{
|
||||
return m_registryHost != nullptr;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef QT_REMOTE_REGISTRY_H
|
||||
#define QT_REMOTE_REGISTRY_H
|
||||
|
||||
#include "../../logos_registry.h"
|
||||
#include <QString>
|
||||
|
||||
class QRemoteObjectRegistryHost;
|
||||
|
||||
/**
|
||||
* @brief LogosRegistry implementation backed by QRemoteObjectRegistryHost.
|
||||
*
|
||||
* Used in Remote (multi-process) mode. The registry host is created in the
|
||||
* constructor and torn down in the destructor, so the lifetime of this object
|
||||
* directly controls the lifetime of the IPC rendezvous point.
|
||||
*/
|
||||
class QtRemoteRegistry : public LogosRegistry {
|
||||
public:
|
||||
explicit QtRemoteRegistry(const QString& url);
|
||||
~QtRemoteRegistry() override;
|
||||
|
||||
bool isInitialized() const override;
|
||||
|
||||
private:
|
||||
QRemoteObjectRegistryHost* m_registryHost;
|
||||
};
|
||||
|
||||
#endif // QT_REMOTE_REGISTRY_H
|
||||
@@ -0,0 +1,311 @@
|
||||
#include "remote_transport.h"
|
||||
#include <QRemoteObjectRegistryHost>
|
||||
#include <QRemoteObjectNode>
|
||||
#include <QRemoteObjectReplica>
|
||||
#include <QRemoteObjectPendingCall>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QMetaObject>
|
||||
#include <QTime>
|
||||
#include <QJsonArray>
|
||||
|
||||
// ── RemoteLogosObject ────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
class RemoteEventHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RemoteEventHelper(QObject* parent = nullptr) : QObject(parent) {}
|
||||
|
||||
void addCallback(const QString& eventName, LogosObject::EventCallback cb) {
|
||||
m_callbacks[eventName].append(std::move(cb));
|
||||
}
|
||||
|
||||
public slots:
|
||||
void onEventResponse(const QString& eventName, const QVariantList& data) {
|
||||
auto cbs = m_callbacks.value(eventName);
|
||||
if (!cbs.isEmpty()) {
|
||||
qDebug() << "[LogosObject] Remote EventHelper: dispatching event" << eventName << "to" << cbs.size() << "callback(s) (via IPC)";
|
||||
}
|
||||
for (const auto& cb : cbs) {
|
||||
try { cb(eventName, data); } catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QHash<QString, QList<LogosObject::EventCallback>> m_callbacks;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class RemoteLogosObject : public LogosObject {
|
||||
public:
|
||||
explicit RemoteLogosObject(QObject* replica)
|
||||
: m_replica(replica), m_helper(nullptr)
|
||||
{
|
||||
qDebug() << "[LogosObject] Created RemoteLogosObject wrapping QRemoteObjectReplica" << reinterpret_cast<quintptr>(replica);
|
||||
}
|
||||
|
||||
~RemoteLogosObject() override {
|
||||
qDebug() << "[LogosObject] Destroying RemoteLogosObject" << reinterpret_cast<quintptr>(m_replica);
|
||||
delete m_helper;
|
||||
}
|
||||
|
||||
QVariant callMethod(const QString& authToken,
|
||||
const QString& methodName,
|
||||
const QVariantList& args,
|
||||
int timeoutMs) override
|
||||
{
|
||||
if (!m_replica) {
|
||||
qWarning() << "RemoteLogosObject: Cannot call method on null replica";
|
||||
return QVariant();
|
||||
}
|
||||
qDebug() << "[LogosObject] RemoteLogosObject::callMethod" << methodName << "args:" << args.size();
|
||||
|
||||
QRemoteObjectPendingCall pendingCall;
|
||||
bool success = QMetaObject::invokeMethod(
|
||||
m_replica,
|
||||
"callRemoteMethod",
|
||||
Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall),
|
||||
Q_ARG(QString, authToken),
|
||||
Q_ARG(QString, methodName),
|
||||
Q_ARG(QVariantList, args)
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
qWarning() << "RemoteLogosObject: Failed to invoke callRemoteMethod on replica";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
pendingCall.waitForFinished(timeoutMs);
|
||||
|
||||
if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) {
|
||||
qWarning() << "RemoteLogosObject: callRemoteMethod failed or timed out:" << pendingCall.error();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return pendingCall.returnValue();
|
||||
}
|
||||
|
||||
bool informModuleToken(const QString& authToken,
|
||||
const QString& moduleName,
|
||||
const QString& token,
|
||||
int timeoutMs) override
|
||||
{
|
||||
if (!m_replica) {
|
||||
qWarning() << "RemoteLogosObject: Cannot call informModuleToken on null replica";
|
||||
return false;
|
||||
}
|
||||
|
||||
QRemoteObjectPendingCall pendingCall;
|
||||
bool success = QMetaObject::invokeMethod(
|
||||
m_replica,
|
||||
"informModuleToken",
|
||||
Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QRemoteObjectPendingCall, pendingCall),
|
||||
Q_ARG(QString, authToken),
|
||||
Q_ARG(QString, moduleName),
|
||||
Q_ARG(QString, token)
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
qWarning() << "RemoteLogosObject: Failed to invoke informModuleToken on replica";
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingCall.waitForFinished(timeoutMs);
|
||||
|
||||
if (!pendingCall.isFinished() || pendingCall.error() != QRemoteObjectPendingCall::NoError) {
|
||||
qWarning() << "RemoteLogosObject: informModuleToken failed or timed out:" << pendingCall.error();
|
||||
return false;
|
||||
}
|
||||
|
||||
return pendingCall.returnValue().toBool();
|
||||
}
|
||||
|
||||
void onEvent(const QString& eventName, EventCallback callback) override
|
||||
{
|
||||
if (!m_replica) return;
|
||||
|
||||
qDebug() << "[LogosObject] RemoteLogosObject::onEvent subscribing to event:" << eventName;
|
||||
if (!m_helper) {
|
||||
m_helper = new RemoteEventHelper();
|
||||
QObject::connect(m_replica, SIGNAL(eventResponse(QString,QVariantList)),
|
||||
m_helper, SLOT(onEventResponse(QString,QVariantList)));
|
||||
qDebug() << "[LogosObject] RemoteLogosObject: connected EventHelper to QRemoteObjectReplica signals (IPC)";
|
||||
}
|
||||
m_helper->addCallback(eventName, std::move(callback));
|
||||
}
|
||||
|
||||
void disconnectEvents() override
|
||||
{
|
||||
delete m_helper;
|
||||
m_helper = nullptr;
|
||||
}
|
||||
|
||||
void emitEvent(const QString& eventName, const QVariantList& data) override
|
||||
{
|
||||
if (!m_replica) return;
|
||||
qDebug() << "[LogosObject] RemoteLogosObject::emitEvent" << eventName << "data:" << data.size() << "items (via IPC)";
|
||||
QMetaObject::invokeMethod(m_replica, "eventResponse",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, eventName),
|
||||
Q_ARG(QVariantList, data));
|
||||
}
|
||||
|
||||
QJsonArray getMethods() override
|
||||
{
|
||||
// Remote introspection not implemented — callers should use
|
||||
// the local module inspection tools (lm) instead.
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
void release() override
|
||||
{
|
||||
disconnectEvents();
|
||||
delete m_replica;
|
||||
m_replica = nullptr;
|
||||
delete this;
|
||||
}
|
||||
|
||||
quintptr id() const override { return reinterpret_cast<quintptr>(m_replica); }
|
||||
|
||||
private:
|
||||
QObject* m_replica;
|
||||
RemoteEventHelper* m_helper;
|
||||
};
|
||||
|
||||
// ── RemoteTransportHost ──────────────────────────────────────────────────────
|
||||
|
||||
RemoteTransportHost::RemoteTransportHost(const QString& registryUrl)
|
||||
: m_registryHost(nullptr)
|
||||
, m_registryUrl(registryUrl)
|
||||
{
|
||||
}
|
||||
|
||||
RemoteTransportHost::~RemoteTransportHost()
|
||||
{
|
||||
delete m_registryHost;
|
||||
}
|
||||
|
||||
bool RemoteTransportHost::publishObject(const QString& name, QObject* object)
|
||||
{
|
||||
if (!m_registryHost) {
|
||||
m_registryHost = new QRemoteObjectRegistryHost(QUrl(m_registryUrl));
|
||||
if (!m_registryHost) {
|
||||
qCritical() << "RemoteTransportHost: Failed to create registry host";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "RemoteTransportHost: Created registry host with URL:" << m_registryUrl;
|
||||
}
|
||||
|
||||
bool success = m_registryHost->enableRemoting(object, name);
|
||||
if (success) {
|
||||
qDebug() << "RemoteTransportHost: Published object:" << name;
|
||||
} else {
|
||||
qCritical() << "RemoteTransportHost: Failed to publish object:" << name;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void RemoteTransportHost::unpublishObject(const QString& /*name*/)
|
||||
{
|
||||
}
|
||||
|
||||
// ── RemoteTransportConnection ────────────────────────────────────────────────
|
||||
|
||||
RemoteTransportConnection::RemoteTransportConnection(const QString& registryUrl)
|
||||
: m_node(new QRemoteObjectNode())
|
||||
, m_registryUrl(registryUrl)
|
||||
, m_connected(false)
|
||||
{
|
||||
}
|
||||
|
||||
RemoteTransportConnection::~RemoteTransportConnection()
|
||||
{
|
||||
delete m_node;
|
||||
}
|
||||
|
||||
bool RemoteTransportConnection::connectToHost()
|
||||
{
|
||||
return connectToRegistry();
|
||||
}
|
||||
|
||||
bool RemoteTransportConnection::isConnected() const
|
||||
{
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
bool RemoteTransportConnection::reconnect()
|
||||
{
|
||||
qDebug() << "RemoteTransportConnection: Attempting to reconnect to registry:" << m_registryUrl;
|
||||
|
||||
if (m_connected) {
|
||||
delete m_node;
|
||||
m_node = new QRemoteObjectNode();
|
||||
m_connected = false;
|
||||
}
|
||||
|
||||
return connectToRegistry();
|
||||
}
|
||||
|
||||
bool RemoteTransportConnection::connectToRegistry()
|
||||
{
|
||||
if (!m_node) {
|
||||
qWarning() << "RemoteTransportConnection: Remote object node is null";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_registryUrl.isEmpty()) {
|
||||
qWarning() << "RemoteTransportConnection: Registry URL is empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "RemoteTransportConnection: Connecting to registry:" << m_registryUrl
|
||||
<< "at" << QTime::currentTime().toString("hh:mm:ss.zzz");
|
||||
|
||||
QUrl url(m_registryUrl);
|
||||
bool success = m_node->connectToNode(url);
|
||||
|
||||
if (success) {
|
||||
m_connected = true;
|
||||
qDebug() << "RemoteTransportConnection: Successfully connected to registry:" << m_registryUrl;
|
||||
} else {
|
||||
m_connected = false;
|
||||
qWarning() << "RemoteTransportConnection: Failed to connect to registry:" << m_registryUrl;
|
||||
}
|
||||
qDebug() << "RemoteTransportConnection: Connected to registry at"
|
||||
<< QTime::currentTime().toString("hh:mm:ss.zzz");
|
||||
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
LogosObject* RemoteTransportConnection::requestObject(const QString& objectName, int timeoutMs)
|
||||
{
|
||||
if (!m_connected) {
|
||||
qWarning() << "RemoteTransportConnection: Not connected. Cannot request object:" << objectName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qDebug() << "RemoteTransportConnection: Requesting object:" << objectName
|
||||
<< "at" << QTime::currentTime().toString("hh:mm:ss.zzz");
|
||||
|
||||
QRemoteObjectReplica* replica = m_node->acquireDynamic(objectName);
|
||||
if (!replica) {
|
||||
qWarning() << "RemoteTransportConnection: Failed to acquire replica for:" << objectName;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!replica->waitForSource(timeoutMs)) {
|
||||
qWarning() << "RemoteTransportConnection: Timeout waiting for replica:" << objectName;
|
||||
delete replica;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qDebug() << "[LogosObject] RemoteTransportConnection: returning RemoteLogosObject for:" << objectName;
|
||||
return new RemoteLogosObject(replica);
|
||||
}
|
||||
|
||||
#include "remote_transport.moc"
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef REMOTE_TRANSPORT_H
|
||||
#define REMOTE_TRANSPORT_H
|
||||
|
||||
#include "../../logos_transport.h"
|
||||
#include "../../logos_object.h"
|
||||
#include <QString>
|
||||
|
||||
class QRemoteObjectRegistryHost;
|
||||
class QRemoteObjectNode;
|
||||
|
||||
class RemoteTransportHost : public LogosTransportHost {
|
||||
public:
|
||||
explicit RemoteTransportHost(const QString& registryUrl);
|
||||
~RemoteTransportHost() override;
|
||||
|
||||
bool publishObject(const QString& name, QObject* object) override;
|
||||
void unpublishObject(const QString& name) override;
|
||||
|
||||
private:
|
||||
QRemoteObjectRegistryHost* m_registryHost;
|
||||
QString m_registryUrl;
|
||||
};
|
||||
|
||||
class RemoteTransportConnection : public LogosTransportConnection {
|
||||
public:
|
||||
explicit RemoteTransportConnection(const QString& registryUrl);
|
||||
~RemoteTransportConnection() override;
|
||||
|
||||
bool connectToHost() override;
|
||||
bool isConnected() const override;
|
||||
bool reconnect() override;
|
||||
LogosObject* requestObject(const QString& objectName, int timeoutMs) override;
|
||||
|
||||
private:
|
||||
bool connectToRegistry();
|
||||
|
||||
QRemoteObjectNode* m_node;
|
||||
QString m_registryUrl;
|
||||
bool m_connected;
|
||||
};
|
||||
|
||||
#endif // REMOTE_TRANSPORT_H
|
||||
Reference in New Issue
Block a user