2026-06-12 18:59:01 -03:00
|
|
|
#include "local_transport.h"
|
|
|
|
|
#include "../../plugin_registry.h"
|
|
|
|
|
#include "../../module_proxy.h"
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QMetaObject>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
|
|
|
|
|
// ── 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) {
|
|
|
|
|
// Dispatch to callbacks registered for this specific event name,
|
|
|
|
|
// plus any wildcard subscribers (callbacks registered with an
|
|
|
|
|
// empty event name, meaning "receive every event").
|
|
|
|
|
auto cbs = m_callbacks.value(eventName);
|
|
|
|
|
cbs.append(m_callbacks.value(QString()));
|
|
|
|
|
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
|
|
|
|
|
|
2026-08-06 12:29:53 -03:00
|
|
|
class LocalLogosObject : public LogosObject, public LogosObjectErrorChannel {
|
2026-06-12 18:59:01 -03:00
|
|
|
public:
|
2026-08-06 12:29:53 -03:00
|
|
|
// objectName is carried purely so a failure can name the module it belongs
|
|
|
|
|
// to (logos::CallError::origin).
|
|
|
|
|
LocalLogosObject(ModuleProxy* proxy, QString objectName)
|
|
|
|
|
: m_proxy(proxy), m_helper(nullptr), m_objectName(std::move(objectName))
|
2026-06-12 18:59:01 -03:00
|
|
|
{
|
|
|
|
|
qDebug() << "[LogosObject] Created LocalLogosObject wrapping ModuleProxy" << reinterpret_cast<quintptr>(proxy);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~LocalLogosObject() override {
|
|
|
|
|
qDebug() << "[LogosObject] Destroying LocalLogosObject" << reinterpret_cast<quintptr>(m_proxy);
|
|
|
|
|
delete m_helper;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 12:29:53 -03:00
|
|
|
// Adapters over the error-carrying implementations: they discard the
|
|
|
|
|
// diagnosis, which is exactly what these entry points have always done.
|
2026-06-12 18:59:01 -03:00
|
|
|
QVariant callMethod(const QString& authToken,
|
|
|
|
|
const QString& methodName,
|
|
|
|
|
const QVariantList& args,
|
2026-08-06 12:29:53 -03:00
|
|
|
int timeoutMs) override
|
2026-06-12 18:59:01 -03:00
|
|
|
{
|
2026-08-06 12:29:53 -03:00
|
|
|
return callMethodWithError(authToken, methodName, args, timeoutMs, nullptr);
|
2026-06-12 18:59:01 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void callMethodAsync(const QString& authToken,
|
|
|
|
|
const QString& methodName,
|
|
|
|
|
const QVariantList& args,
|
2026-08-06 12:29:53 -03:00
|
|
|
int timeoutMs,
|
2026-06-12 18:59:01 -03:00
|
|
|
AsyncResultCallback callback) override
|
|
|
|
|
{
|
|
|
|
|
if (!callback) return;
|
2026-08-06 12:29:53 -03:00
|
|
|
callMethodAsyncWithError(authToken, methodName, args, timeoutMs,
|
|
|
|
|
[cb = std::move(callback)](QVariant v, const logos::CallError&) mutable {
|
|
|
|
|
cb(std::move(v));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// In-process direct dispatch: there is no wire to drop and no deadline to
|
|
|
|
|
// miss, so a vanished ModuleProxy is the only failure this transport has.
|
|
|
|
|
QVariant callMethodWithError(const QString& authToken,
|
|
|
|
|
const QString& methodName,
|
|
|
|
|
const QVariantList& args,
|
|
|
|
|
int /*timeoutMs*/,
|
|
|
|
|
logos::CallError* err) override
|
|
|
|
|
{
|
|
|
|
|
if (err) err->clear();
|
2026-06-12 18:59:01 -03:00
|
|
|
if (!m_proxy) {
|
2026-08-06 12:29:53 -03:00
|
|
|
if (err) *err = proxyGoneError();
|
|
|
|
|
return QVariant();
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "[LogosObject] LocalLogosObject::callMethod" << methodName << "args:" << args.size();
|
|
|
|
|
return m_proxy->callRemoteMethod(authToken, methodName, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void callMethodAsyncWithError(const QString& authToken,
|
|
|
|
|
const QString& methodName,
|
|
|
|
|
const QVariantList& args,
|
|
|
|
|
int /*timeoutMs*/,
|
|
|
|
|
AsyncResultErrorCallback callback) override
|
|
|
|
|
{
|
|
|
|
|
if (!callback) return;
|
|
|
|
|
if (!m_proxy) {
|
|
|
|
|
const logos::CallError e = proxyGoneError();
|
|
|
|
|
QTimer::singleShot(0, [callback, e]() { callback(QVariant(), e); });
|
2026-06-12 18:59:01 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ModuleProxy* proxy = m_proxy;
|
|
|
|
|
QTimer::singleShot(0, [proxy, authToken, methodName, args, callback]() {
|
|
|
|
|
QVariant result = proxy->callRemoteMethod(authToken, methodName, args);
|
2026-08-06 12:29:53 -03:00
|
|
|
callback(result, logos::CallError{});
|
2026-06-12 18:59:01 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:
|
2026-08-06 12:29:53 -03:00
|
|
|
logos::CallError proxyGoneError() const
|
|
|
|
|
{
|
|
|
|
|
const std::string origin = m_objectName.toStdString();
|
|
|
|
|
return logos::callErrorObjectUnavailable(
|
|
|
|
|
origin, "module '" + origin + "' is no longer registered locally");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 18:59:01 -03:00
|
|
|
ModuleProxy* m_proxy;
|
|
|
|
|
EventHelper* m_helper;
|
2026-08-06 12:29:53 -03:00
|
|
|
QString m_objectName;
|
2026-06-12 18:59:01 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── 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;
|
2026-08-06 12:29:53 -03:00
|
|
|
return new LocalLogosObject(proxy, objectName);
|
2026-06-12 18:59:01 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "local_transport.moc"
|