2025-09-30 14:56:47 -04:00
|
|
|
|
#include "logos_api_consumer.h"
|
2026-03-23 11:45:25 -04:00
|
|
|
|
#include "logos_object.h"
|
2025-09-30 14:56:47 -04:00
|
|
|
|
#include "module_proxy.h"
|
|
|
|
|
|
#include "logos_api_client.h"
|
|
|
|
|
|
#include "token_manager.h"
|
2025-12-02 10:22:26 -05:00
|
|
|
|
#include "logos_mode.h"
|
2026-03-17 01:43:39 +11:00
|
|
|
|
#include "logos_instance.h"
|
2026-03-23 11:45:25 -04:00
|
|
|
|
#include "logos_transport.h"
|
|
|
|
|
|
#include "logos_transport_factory.h"
|
2026-05-07 12:27:14 -03:00
|
|
|
|
#include <chrono>
|
|
|
|
|
|
#include <thread>
|
2025-09-30 14:56:47 -04:00
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
#include <QUrl>
|
|
|
|
|
|
#include <QMetaObject>
|
2026-03-23 11:45:25 -04:00
|
|
|
|
#include <QTimer>
|
2025-09-30 14:56:47 -04:00
|
|
|
|
#include <QTime>
|
2026-04-09 11:37:51 -03:00
|
|
|
|
#include <QPointer>
|
2025-09-30 14:56:47 -04:00
|
|
|
|
|
2026-05-07 12:27:14 -03:00
|
|
|
|
LogosAPIConsumer::LogosAPIConsumer(const QString& module_to_talk_to,
|
|
|
|
|
|
const QString& origin_module,
|
|
|
|
|
|
TokenManager* token_manager,
|
|
|
|
|
|
const LogosTransportConfig& transport,
|
|
|
|
|
|
QObject *parent)
|
2025-09-30 14:56:47 -04:00
|
|
|
|
: QObject(parent)
|
2026-03-17 01:43:39 +11:00
|
|
|
|
, m_registryUrl(LogosInstance::id(module_to_talk_to))
|
2025-09-30 14:56:47 -04:00
|
|
|
|
, m_token_manager(token_manager)
|
|
|
|
|
|
{
|
2026-05-07 12:27:14 -03:00
|
|
|
|
// Single transport-resolution path: the factory combines LogosMode
|
|
|
|
|
|
// + LogosTransportConfig (mode wins for Mock/Local; transport
|
|
|
|
|
|
// chooses the wire protocol in Remote mode). The choice scopes to
|
|
|
|
|
|
// this consumer only — any LogosAPIProvider in the same LogosAPI
|
|
|
|
|
|
// still constructs its host from the global default.
|
|
|
|
|
|
m_transport = LogosTransportFactory::createConnection(transport, m_registryUrl);
|
|
|
|
|
|
|
|
|
|
|
|
// Initial connect with deadline-driven retry. The target module's
|
|
|
|
|
|
// listener may not be ready yet — particularly for TCP/TLS, where
|
|
|
|
|
|
// the child subprocess's QTcpServer::listen() lags the runtime
|
|
|
|
|
|
// returning from its load callback. QLocalSocket internally
|
|
|
|
|
|
// tolerates this (it retries connect until a deadline), but
|
|
|
|
|
|
// boost::asio::connect on TCP fails fast with "connection refused"
|
|
|
|
|
|
// and we'd surface a warning + return nullptr for any subsequent
|
|
|
|
|
|
// requestObject before the listener even came up.
|
|
|
|
|
|
//
|
|
|
|
|
|
// 50ms × up-to-100 attempts ≈ 5s budget — same shape as
|
|
|
|
|
|
// logos-liblogos's sendTokenToProcess loop and generous enough to
|
|
|
|
|
|
// cover cold-start child Qt initialisation under load.
|
|
|
|
|
|
using clock = std::chrono::steady_clock;
|
|
|
|
|
|
const auto deadline = clock::now() + std::chrono::milliseconds(5000);
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
if (m_transport->connectToHost()) break;
|
|
|
|
|
|
if (clock::now() >= deadline) break;
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogosAPIConsumer::LogosAPIConsumer(const QString& module_to_talk_to,
|
|
|
|
|
|
const QString& origin_module,
|
|
|
|
|
|
TokenManager* token_manager,
|
|
|
|
|
|
QObject *parent)
|
|
|
|
|
|
: LogosAPIConsumer(module_to_talk_to, origin_module, token_manager,
|
|
|
|
|
|
LogosTransportConfigGlobal::getDefault(), parent)
|
|
|
|
|
|
{
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
LogosAPIConsumer::~LogosAPIConsumer()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* LogosAPIConsumer::requestObject(const QString& objectName, Timeout timeout)
|
2025-09-30 14:56:47 -04:00
|
|
|
|
{
|
|
|
|
|
|
qDebug() << "LogosAPIConsumer: Requesting object:" << objectName << "at" << QTime::currentTime().toString("hh:mm:ss.zzz");
|
|
|
|
|
|
|
|
|
|
|
|
if (objectName.isEmpty()) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Object name cannot be empty";
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
if (!m_transport->isConnected()) {
|
2025-12-02 10:22:26 -05:00
|
|
|
|
qWarning() << "LogosAPIConsumer: Not connected to registry. Cannot request object:" << objectName;
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* object = m_transport->requestObject(objectName, timeout.ms);
|
|
|
|
|
|
if (object) {
|
|
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: acquired LogosObject for:" << objectName << "(id:" << object->id() << ")";
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
2026-03-23 11:45:25 -04:00
|
|
|
|
return object;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LogosAPIConsumer::isConnected() const
|
|
|
|
|
|
{
|
2026-03-23 11:45:25 -04:00
|
|
|
|
return m_transport->isConnected();
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString LogosAPIConsumer::registryUrl() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_registryUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LogosAPIConsumer::reconnect()
|
|
|
|
|
|
{
|
|
|
|
|
|
qDebug() << "LogosAPIConsumer: Attempting to reconnect to registry:" << m_registryUrl;
|
2026-03-23 11:45:25 -04:00
|
|
|
|
return m_transport->reconnect();
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-25 15:15:39 +01:00
|
|
|
|
QVariant LogosAPIConsumer::invokeRemoteMethod(const QString& authToken, const QString& objectName, const QString& methodName,
|
2025-12-11 13:40:41 -05:00
|
|
|
|
const QVariantList& args, Timeout timeout)
|
2025-09-30 14:56:47 -04:00
|
|
|
|
{
|
2026-02-25 15:15:39 +01:00
|
|
|
|
qDebug() << "LogosAPIConsumer: Calling invokeRemoteMethod:" << objectName << methodName << "args_count:" << args.size() << "timeout:" << timeout.ms;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* plugin = m_transport->requestObject(objectName, timeout.ms);
|
2025-12-02 10:22:26 -05:00
|
|
|
|
if (!plugin) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << objectName;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
return QVariant();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: calling via LogosObject::callMethod" << methodName;
|
|
|
|
|
|
QVariant result = plugin->callMethod(authToken, methodName, args, timeout.ms);
|
|
|
|
|
|
plugin->release();
|
|
|
|
|
|
return result;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 20:57:08 +01:00
|
|
|
|
void LogosAPIConsumer::invokeRemoteMethodAsync(const QString& authToken, const QString& objectName, const QString& methodName,
|
2026-03-23 11:45:25 -04:00
|
|
|
|
const QVariantList& args,
|
|
|
|
|
|
AsyncResultCallback callback,
|
|
|
|
|
|
Timeout timeout)
|
2026-03-19 20:57:08 +01:00
|
|
|
|
{
|
|
|
|
|
|
if (!callback) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: invokeRemoteMethodAsync called with null callback";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* plugin = m_transport->requestObject(objectName, timeout.ms);
|
2026-03-19 20:57:08 +01:00
|
|
|
|
if (!plugin) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << objectName;
|
|
|
|
|
|
QTimer::singleShot(0, this, [callback]() { callback(QVariant()); });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 11:37:51 -03:00
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: async calling via LogosObject::callMethodAsync" << methodName;
|
|
|
|
|
|
// QPointer guards against use-after-free: if the consumer is destroyed
|
|
|
|
|
|
// before the transport callback fires, the callback is silently dropped.
|
|
|
|
|
|
// No re-queuing needed -- the transport already delivers on a deferred
|
|
|
|
|
|
// event-loop iteration (QTimer / QueuedConnection).
|
|
|
|
|
|
QPointer<LogosAPIConsumer> self(this);
|
|
|
|
|
|
plugin->callMethodAsync(authToken, methodName, args, timeout.ms,
|
|
|
|
|
|
[plugin, callback, self](QVariant result) {
|
2026-04-10 11:39:14 -03:00
|
|
|
|
// Deliver the result before release(): destroying the replica can
|
|
|
|
|
|
// invalidate storage that QVariant still references for some return
|
|
|
|
|
|
// types (matches sync invokeRemoteMethod: callMethod then release).
|
|
|
|
|
|
if (!self) {
|
|
|
|
|
|
plugin->release();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-09 11:37:51 -03:00
|
|
|
|
callback(result);
|
2026-04-10 11:39:14 -03:00
|
|
|
|
plugin->release();
|
2026-04-09 11:37:51 -03:00
|
|
|
|
});
|
2026-03-23 11:45:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void LogosAPIConsumer::onEvent(LogosObject* originObject, const QString& eventName, std::function<void(const QString&, const QVariantList&)> callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer::onEvent registering for:" << eventName << "on LogosObject id:" << originObject;
|
|
|
|
|
|
|
|
|
|
|
|
if (!originObject) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Cannot register event on null object";
|
2026-03-19 20:57:08 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
originObject->onEvent(eventName, std::move(callback));
|
2026-03-19 20:57:08 +01:00
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: event callback registered for:" << eventName;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LogosAPIConsumer::informModuleToken(const QString& authToken, const QString& moduleName, const QString& token)
|
|
|
|
|
|
{
|
|
|
|
|
|
qDebug() << "LogosAPIConsumer: Informing module token for module:" << moduleName << "with token:" << token;
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* plugin = m_transport->requestObject("capability_module", 20000);
|
2025-12-02 10:22:26 -05:00
|
|
|
|
if (!plugin) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object: capability_module";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: calling LogosObject::informModuleToken for" << moduleName;
|
|
|
|
|
|
bool result = plugin->informModuleToken(authToken, moduleName, token, 20000);
|
2025-09-30 14:56:47 -04:00
|
|
|
|
qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result;
|
2026-03-23 11:45:25 -04:00
|
|
|
|
plugin->release();
|
|
|
|
|
|
return result;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
LogosObject* plugin = m_transport->requestObject(originModule, 20000);
|
2025-12-02 10:22:26 -05:00
|
|
|
|
if (!plugin) {
|
|
|
|
|
|
qWarning() << "LogosAPIConsumer: Failed to acquire plugin/replica for object:" << originModule;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-23 11:45:25 -04:00
|
|
|
|
qDebug() << "[LogosObject] LogosAPIConsumer: calling LogosObject::informModuleToken for" << moduleName << "on" << originModule;
|
|
|
|
|
|
bool result = plugin->informModuleToken(authToken, moduleName, token, 20000);
|
2025-09-30 14:56:47 -04:00
|
|
|
|
qDebug() << "LogosAPIConsumer: informModuleToken completed with result:" << result;
|
2026-03-23 11:45:25 -04:00
|
|
|
|
plugin->release();
|
|
|
|
|
|
return result;
|
2025-09-30 14:56:47 -04:00
|
|
|
|
}
|