This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-kde/gui/ninepconnection.cpp

192 lines
5.4 KiB
C++

#include "ninepconnection.h"
/*
* TRANSITION TABLE
* ================
*
* State | Event | Guard | Next State | Actions
* -------------|--------------|------------------------|--------------|------------------
* Disconnected | EvStart | | Connecting | spawnProcess
* Connecting | EvOpenMarker | | Connected | emit connected
* Connecting | EvExitFail | canRetry | RetryWait | scheduleRetry
* Connecting | EvExitFail | !canRetry | Disconnected |
* Connected | EvExitOk | | Connected | scheduleRetry (silent)
* Connected | EvExitFail | canRetry | RetryWait | emit disconnected, scheduleRetry
* Connected | EvExitFail | !canRetry | Disconnected | emit disconnected
* RetryWait | EvRetryTimer | | Connecting | spawnProcess
* Any | EvStop | | Disconnected | killProcess, emit disconnected (if was Connected)
*
* Signals:
* - connected: emitted when entering Connected from any other state
* - disconnected: emitted when leaving Connected for any other state
*/
NinePConnection::NinePConnection(QObject *parent)
: QObject(parent)
{
m_retryTimer.setSingleShot(true);
connect(&m_retryTimer, &QTimer::timeout, this, [this]() { dispatch(EvRetryTimer); });
}
NinePConnection::~NinePConnection()
{
stop();
}
void NinePConnection::start(const QString &program, const QStringList &args, int retryMs, int maxRetries)
{
m_program = program;
m_args = args;
m_retryMs = retryMs;
m_maxRetries = maxRetries;
m_retryCount = 0;
dispatch(EvStart);
}
void NinePConnection::stop()
{
dispatch(EvStop);
}
void NinePConnection::dispatch(Event ev)
{
State next = m_state;
switch (m_state) {
case Disconnected:
if (ev == EvStart) {
next = Connecting;
spawnProcess();
}
break;
case Connecting:
if (ev == EvStop) {
next = Disconnected;
killProcess();
} else if (ev == EvOpenMarker) {
next = Connected;
} else if (ev == EvExitFail) {
bool canRetry = m_retryMs > 0 && (m_maxRetries == 0 || m_retryCount < m_maxRetries);
if (canRetry) {
next = RetryWait;
++m_retryCount;
scheduleRetry();
} else {
next = Disconnected;
}
}
break;
case Connected:
if (ev == EvStop) {
next = Disconnected;
killProcess();
} else if (ev == EvExitOk) {
// Silent restart - stay Connected
m_retryCount = 0;
scheduleRetry();
} else if (ev == EvExitFail) {
bool canRetry = m_retryMs > 0 && (m_maxRetries == 0 || m_retryCount < m_maxRetries);
if (canRetry) {
next = RetryWait;
++m_retryCount;
scheduleRetry();
} else {
next = Disconnected;
}
}
break;
case RetryWait:
if (ev == EvStop) {
next = Disconnected;
m_retryTimer.stop();
} else if (ev == EvRetryTimer) {
next = Connecting;
spawnProcess();
}
break;
}
if (next != m_state)
enter(next);
}
void NinePConnection::enter(State state)
{
State prev = m_state;
m_state = state;
emit stateChanged(state);
if (state == Connected && prev != Connected)
emit connected();
else if (prev == Connected && state != Connected)
emit disconnected();
}
void NinePConnection::spawnProcess()
{
if (m_process) return;
m_stderr.clear();
m_openMarkerSeen = false;
m_process = new QProcess(this);
connect(m_process, &QProcess::readyReadStandardOutput, this, &NinePConnection::onStdout);
connect(m_process, &QProcess::readyReadStandardError, this, &NinePConnection::onStderr);
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &NinePConnection::onFinished);
m_process->start(m_program, m_args);
}
void NinePConnection::killProcess()
{
m_retryTimer.stop();
if (m_process) {
m_process->disconnect(this);
m_process->kill();
m_process->waitForFinished(1000);
m_process->deleteLater();
m_process = nullptr;
}
}
void NinePConnection::scheduleRetry()
{
if (m_retryMs > 0)
m_retryTimer.start(m_retryMs);
else
spawnProcess();
}
void NinePConnection::onStdout()
{
const QByteArray data = m_process->readAllStandardOutput();
if (!data.isEmpty())
emit readyRead(data);
}
void NinePConnection::onStderr()
{
m_stderr += m_process->readAllStandardError();
while (true) {
int end = m_stderr.indexOf('\n');
if (end < 0) break;
QByteArray line = m_stderr.left(end).trimmed();
m_stderr.remove(0, end + 1);
if (line == "OLLIE_9P_OPEN") {
m_openMarkerSeen = true;
dispatch(EvOpenMarker);
}
}
}
void NinePConnection::onFinished(int exitCode, QProcess::ExitStatus)
{
if (!m_process) return;
m_process->deleteLater();
m_process = nullptr;
bool success = (exitCode == 0 && m_openMarkerSeen);
dispatch(success ? EvExitOk : EvExitFail);
}