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:
Iuri Matias
2026-03-23 11:45:25 -04:00
committed by GitHub
co-authored by Logos Workspace
parent 4b66dac015
commit 4197ee1830
37 changed files with 2652 additions and 1317 deletions
+106
View File
@@ -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
+17
View File
@@ -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
+125
View File
@@ -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;
}
+109
View File
@@ -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);
}
+79
View File
@@ -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