314 lines
9.4 KiB
C++
314 lines
9.4 KiB
C++
#include "olliedbusclient.h"
|
|
|
|
#include <QDBusReply>
|
|
#include <QDBusConnection>
|
|
#include <QDBusServiceWatcher>
|
|
#include <QDBusPendingReply>
|
|
#include <QDir>
|
|
|
|
static const char *SERVICE = "org.ollie.SessionManager";
|
|
static const char *OBJ_PATH = "/org/ollie/SessionManager";
|
|
static const char *IFACE = "org.ollie.SessionManager";
|
|
|
|
OllieDBusClient::OllieDBusClient(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
m_iface = new QDBusInterface(SERVICE, OBJ_PATH, IFACE,
|
|
QDBusConnection::sessionBus(), this);
|
|
|
|
// Watch for service appearing/disappearing
|
|
auto *watcher = new QDBusServiceWatcher(SERVICE,
|
|
QDBusConnection::sessionBus(),
|
|
QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
|
|
this);
|
|
connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() {
|
|
refreshSessions();
|
|
});
|
|
connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
|
|
m_sessions.clear();
|
|
m_activeSessionId.clear();
|
|
m_chatText.clear();
|
|
m_chatOffset = 0;
|
|
m_activeState = "idle";
|
|
m_activeCwd.clear();
|
|
emit sessionsChanged();
|
|
emit activeSessionIdChanged();
|
|
emit chatTextChanged();
|
|
emit activeStateChanged();
|
|
emit activeCwdChanged();
|
|
});
|
|
|
|
// Connect D-Bus signals
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionCreated",
|
|
this, SLOT(onSessionCreated(QString)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "SessionKilled",
|
|
this, SLOT(onSessionKilled(QString)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "StateChanged",
|
|
this, SLOT(onStateChanged(QString,QString)));
|
|
bus.connect(SERVICE, OBJ_PATH, IFACE, "ChatUpdated",
|
|
this, SLOT(onChatUpdated(QString,qlonglong,QString)));
|
|
|
|
refreshSessions();
|
|
}
|
|
|
|
void OllieDBusClient::setActiveSessionId(const QString &id)
|
|
{
|
|
if (m_activeSessionId == id) return;
|
|
m_activeSessionId = id;
|
|
m_chatText.clear();
|
|
m_chatOffset = 0;
|
|
emit activeSessionIdChanged();
|
|
emit chatTextChanged();
|
|
fetchChat();
|
|
fetchConfig();
|
|
}
|
|
|
|
// --- Async refresh sessions ---
|
|
|
|
void OllieDBusClient::refreshSessions()
|
|
{
|
|
if (m_refreshPending) return; // coalesce rapid-fire signals
|
|
m_refreshPending = true;
|
|
|
|
QDBusPendingCall pending = m_iface->asyncCall("ListSessions");
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished,
|
|
this, &OllieDBusClient::onRefreshSessionsFinished);
|
|
}
|
|
|
|
void OllieDBusClient::onRefreshSessionsFinished(QDBusPendingCallWatcher *watcher)
|
|
{
|
|
m_refreshPending = false;
|
|
watcher->deleteLater();
|
|
|
|
QDBusPendingReply<QStringList> reply = *watcher;
|
|
if (reply.isError()) return;
|
|
|
|
processSessionList(reply.value());
|
|
}
|
|
|
|
void OllieDBusClient::processSessionList(const QStringList &entries)
|
|
{
|
|
m_sessions.clear();
|
|
for (const QString &entry : entries) {
|
|
QStringList parts = entry.split('\t');
|
|
if (parts.size() >= 4) {
|
|
QVariantMap session;
|
|
session["id"] = parts[0];
|
|
session["state"] = parts[1];
|
|
session["model"] = parts[2];
|
|
session["agent"] = parts[3];
|
|
m_sessions.append(session);
|
|
}
|
|
}
|
|
emit sessionsChanged();
|
|
|
|
// Update active state
|
|
for (const QVariant &v : m_sessions) {
|
|
QVariantMap s = v.toMap();
|
|
if (s["id"].toString() == m_activeSessionId) {
|
|
QString newState = s["state"].toString();
|
|
if (newState != m_activeState) {
|
|
m_activeState = newState;
|
|
emit activeStateChanged();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Active session no longer exists
|
|
if (!m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) {
|
|
setActiveSessionId(m_sessions.first().toMap()["id"].toString());
|
|
} else if (m_activeSessionId.isEmpty() && !m_sessions.isEmpty()) {
|
|
setActiveSessionId(m_sessions.first().toMap()["id"].toString());
|
|
} else if (m_sessions.isEmpty() && !m_activeSessionId.isEmpty()) {
|
|
m_activeSessionId.clear();
|
|
m_activeState = "idle";
|
|
m_chatText.clear();
|
|
m_chatOffset = 0;
|
|
m_activeCwd.clear();
|
|
emit activeSessionIdChanged();
|
|
emit activeStateChanged();
|
|
emit chatTextChanged();
|
|
emit activeCwdChanged();
|
|
}
|
|
}
|
|
|
|
// --- Async fetch chat ---
|
|
|
|
void OllieDBusClient::fetchChat()
|
|
{
|
|
if (m_activeSessionId.isEmpty()) return;
|
|
if (m_fetchChatPending) return; // don't pile up requests
|
|
m_fetchChatPending = true;
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetChat");
|
|
msg << m_activeSessionId << m_chatOffset;
|
|
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished,
|
|
this, &OllieDBusClient::onFetchChatFinished);
|
|
}
|
|
|
|
void OllieDBusClient::onFetchChatFinished(QDBusPendingCallWatcher *watcher)
|
|
{
|
|
m_fetchChatPending = false;
|
|
watcher->deleteLater();
|
|
|
|
QDBusPendingReply<QString, qlonglong> reply = *watcher;
|
|
if (reply.isError()) return;
|
|
|
|
QString text = reply.argumentAt<0>();
|
|
qlonglong newOffset = reply.argumentAt<1>();
|
|
|
|
if (!text.isEmpty()) {
|
|
if (m_chatOffset == 0) {
|
|
m_chatText = text;
|
|
} else {
|
|
m_chatText += text;
|
|
}
|
|
m_chatOffset = newOffset;
|
|
emit chatTextChanged();
|
|
}
|
|
}
|
|
|
|
// --- Async fetch config ---
|
|
|
|
void OllieDBusClient::fetchConfig()
|
|
{
|
|
if (m_activeSessionId.isEmpty()) {
|
|
if (!m_activeCwd.isEmpty()) {
|
|
m_activeCwd.clear();
|
|
emit activeCwdChanged();
|
|
}
|
|
return;
|
|
}
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetConfig");
|
|
msg << m_activeSessionId;
|
|
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(msg);
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished,
|
|
this, &OllieDBusClient::onFetchConfigFinished);
|
|
}
|
|
|
|
void OllieDBusClient::onFetchConfigFinished(QDBusPendingCallWatcher *watcher)
|
|
{
|
|
watcher->deleteLater();
|
|
|
|
QDBusPendingReply<QString> reply = *watcher;
|
|
if (reply.isError()) return;
|
|
|
|
QString config = reply.value();
|
|
QString newCwd;
|
|
const QStringList lines = config.split('\n', Qt::SkipEmptyParts);
|
|
for (const QString &line : lines) {
|
|
if (line.startsWith(QLatin1String("cwd="))) {
|
|
newCwd = line.mid(4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newCwd != m_activeCwd) {
|
|
m_activeCwd = newCwd;
|
|
emit activeCwdChanged();
|
|
}
|
|
}
|
|
|
|
// --- User-initiated actions (fire-and-forget async) ---
|
|
|
|
QString OllieDBusClient::createSession(const QString &cwd)
|
|
{
|
|
QString dir = cwd.isEmpty() ? QDir::homePath() : cwd;
|
|
// Use asyncCall — we'll pick up the new session via the SessionCreated signal
|
|
m_iface->asyncCall("CreateSession", dir, QString(), QString(), QString("default"));
|
|
return QString(); // ID not available synchronously; signal will trigger refresh
|
|
}
|
|
|
|
bool OllieDBusClient::killSession(const QString &sessionId)
|
|
{
|
|
if (sessionId == m_activeSessionId) {
|
|
m_activeSessionId.clear();
|
|
m_chatText.clear();
|
|
m_chatOffset = 0;
|
|
m_activeState = "idle";
|
|
m_activeCwd.clear();
|
|
emit activeSessionIdChanged();
|
|
emit chatTextChanged();
|
|
emit activeStateChanged();
|
|
emit activeCwdChanged();
|
|
}
|
|
m_iface->asyncCall("KillSession", sessionId);
|
|
return true;
|
|
}
|
|
|
|
bool OllieDBusClient::renameSession(const QString &sessionId, const QString &newName)
|
|
{
|
|
if (sessionId == m_activeSessionId) {
|
|
m_activeSessionId = newName;
|
|
emit activeSessionIdChanged();
|
|
}
|
|
m_iface->asyncCall("RenameSession", sessionId, newName);
|
|
return true;
|
|
}
|
|
|
|
bool OllieDBusClient::setConfig(const QString &sessionId, const QString &key, const QString &value)
|
|
{
|
|
if (sessionId == m_activeSessionId && key == "cwd") {
|
|
m_activeCwd = value;
|
|
emit activeCwdChanged();
|
|
}
|
|
m_iface->asyncCall("SetConfig", sessionId, key, value);
|
|
return true;
|
|
}
|
|
|
|
bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt)
|
|
{
|
|
m_iface->asyncCall("Submit", sessionId, prompt);
|
|
return true;
|
|
}
|
|
|
|
bool OllieDBusClient::interrupt(const QString &sessionId)
|
|
{
|
|
m_iface->asyncCall("Interrupt", sessionId);
|
|
return true;
|
|
}
|
|
|
|
// --- D-Bus signal handlers ---
|
|
|
|
void OllieDBusClient::onSessionCreated(const QString &session_id)
|
|
{
|
|
Q_UNUSED(session_id);
|
|
refreshSessions();
|
|
}
|
|
|
|
void OllieDBusClient::onSessionKilled(const QString &session_id)
|
|
{
|
|
Q_UNUSED(session_id);
|
|
refreshSessions();
|
|
}
|
|
|
|
void OllieDBusClient::onStateChanged(const QString &session_id, const QString &new_state)
|
|
{
|
|
if (session_id == m_activeSessionId) {
|
|
m_activeState = new_state;
|
|
emit activeStateChanged();
|
|
}
|
|
refreshSessions();
|
|
}
|
|
|
|
void OllieDBusClient::onChatUpdated(const QString &session_id, qlonglong offset, const QString &new_text)
|
|
{
|
|
if (session_id != m_activeSessionId) return;
|
|
|
|
// Append directly from the signal — no need to poll
|
|
if (offset == m_chatOffset) {
|
|
m_chatText += new_text;
|
|
m_chatOffset += new_text.size();
|
|
emit chatTextChanged();
|
|
} else {
|
|
// Offset mismatch — do a full fetch
|
|
fetchChat();
|
|
}
|
|
}
|