#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::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); }