285 lines
8.5 KiB
C++
285 lines
8.5 KiB
C++
#include "olliedbusclient.h"
|
|
|
|
#include <QDBusReply>
|
|
#include <QDBusConnection>
|
|
#include <QDBusServiceWatcher>
|
|
#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();
|
|
}
|
|
|
|
void OllieDBusClient::refreshSessions()
|
|
{
|
|
QDBusReply<QStringList> reply = m_iface->call("ListSessions");
|
|
if (!reply.isValid()) return;
|
|
|
|
m_sessions.clear();
|
|
for (const QString &entry : reply.value()) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
void OllieDBusClient::fetchChat()
|
|
{
|
|
if (m_activeSessionId.isEmpty()) return;
|
|
|
|
QDBusMessage msg = QDBusMessage::createMethodCall(SERVICE, OBJ_PATH, IFACE, "GetChat");
|
|
msg << m_activeSessionId << m_chatOffset;
|
|
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
|
|
|
|
if (reply.type() != QDBusMessage::ReplyMessage) return;
|
|
QList<QVariant> args = reply.arguments();
|
|
if (args.size() < 2) return;
|
|
|
|
QString text = args[0].toString();
|
|
qlonglong newOffset = args[1].toLongLong();
|
|
|
|
if (!text.isEmpty()) {
|
|
if (m_chatOffset == 0) {
|
|
m_chatText = text;
|
|
} else {
|
|
m_chatText += text;
|
|
}
|
|
m_chatOffset = newOffset;
|
|
emit chatTextChanged();
|
|
}
|
|
}
|
|
|
|
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;
|
|
QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
|
|
|
|
if (reply.type() != QDBusMessage::ReplyMessage) return;
|
|
QList<QVariant> args = reply.arguments();
|
|
if (args.isEmpty()) return;
|
|
|
|
QString config = args[0].toString();
|
|
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();
|
|
}
|
|
}
|
|
|
|
QString OllieDBusClient::createSession(const QString &cwd)
|
|
{
|
|
QString dir = cwd.isEmpty() ? QDir::homePath() : cwd;
|
|
QDBusReply<QString> reply = m_iface->call("CreateSession", dir, QString(), QString(), "default");
|
|
if (reply.isValid()) {
|
|
QString id = reply.value();
|
|
refreshSessions();
|
|
setActiveSessionId(id);
|
|
return id;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
bool OllieDBusClient::killSession(const QString &sessionId)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("KillSession", sessionId);
|
|
if (reply.isValid() && reply.value()) {
|
|
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();
|
|
}
|
|
refreshSessions();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OllieDBusClient::renameSession(const QString &sessionId, const QString &newName)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("RenameSession", sessionId, newName);
|
|
if (reply.isValid() && reply.value()) {
|
|
if (sessionId == m_activeSessionId) {
|
|
m_activeSessionId = newName;
|
|
emit activeSessionIdChanged();
|
|
}
|
|
refreshSessions();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OllieDBusClient::setConfig(const QString &sessionId, const QString &key, const QString &value)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("SetConfig", sessionId, key, value);
|
|
if (reply.isValid() && reply.value()) {
|
|
if (sessionId == m_activeSessionId && key == "cwd") {
|
|
m_activeCwd = value;
|
|
emit activeCwdChanged();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OllieDBusClient::submit(const QString &sessionId, const QString &prompt)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("Submit", sessionId, prompt);
|
|
return reply.isValid() && reply.value();
|
|
}
|
|
|
|
bool OllieDBusClient::interrupt(const QString &sessionId)
|
|
{
|
|
QDBusReply<bool> reply = m_iface->call("Interrupt", sessionId);
|
|
return reply.isValid() && reply.value();
|
|
}
|
|
|
|
// --- 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();
|
|
}
|
|
}
|