192 lines
5.4 KiB
C++
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);
|
|
}
|