#include "ninepconnection.h" NinePConnection::NinePConnection(QObject *parent) : QObject(parent) { m_retryTimer.setSingleShot(true); connect(&m_retryTimer, &QTimer::timeout, this, &NinePConnection::retry); } NinePConnection::~NinePConnection() { stop(); } void NinePConnection::start(const QString &program, const QStringList &args, int retryMs, bool probe, int maxRetries) { m_program = program; m_args = args; m_retryMs = retryMs; m_probe = probe; m_maxRetries = maxRetries; m_retryCount = 0; m_stopRequested = false; m_openMarkerSeen = false; m_retryTimer.stop(); if (!m_process) startProcess(); } void NinePConnection::stop() { m_stopRequested = true; m_retryTimer.stop(); if (m_process) { m_process->disconnect(this); m_process->kill(); m_process->waitForFinished(1000); m_process->deleteLater(); m_process = nullptr; } setState(Disconnected); } void NinePConnection::startProcess() { if (m_stopRequested || m_process) return; m_stderr.clear(); m_openMarkerSeen = false; m_process = new QProcess(this); connect(m_process, &QProcess::started, this, &NinePConnection::onStarted); 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); // A finite probe refreshes an already connected agent. Keep the // established state while the replacement probe is opening so a // successful periodic probe does not flash disconnected in the UI. if (!(m_probe && m_state == Connected)) setState(Connecting); m_process->start(m_program, m_args); } void NinePConnection::onStarted() { // The helper emits the readiness marker only after the 9P open succeeds. } 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) { const int end = m_stderr.indexOf('\n'); if (end < 0) break; const QByteArray line = m_stderr.left(end).trimmed(); m_stderr.remove(0, end + 1); if (line == "OLLIE_9P_OPEN") { m_openMarkerSeen = true; setState(Connected); } } } void NinePConnection::onFinished(int exitCode, QProcess::ExitStatus) { if (!m_process) return; m_process->deleteLater(); m_process = nullptr; // Successful probe that was connected: schedule re-probe (reset retry count). if (m_probe && m_state == Connected && m_openMarkerSeen && exitCode == 0 && !m_stopRequested) { m_retryCount = 0; if (m_retryMs > 0) m_retryTimer.start(m_retryMs); else startProcess(); // immediate re-probe return; } // No retry configured, or stop requested, or max retries exceeded. if (m_stopRequested || m_retryMs <= 0 || (m_maxRetries > 0 && m_retryCount >= m_maxRetries)) { setState(Disconnected); return; } ++m_retryCount; setState(RetryWait); m_retryTimer.start(m_retryMs); } void NinePConnection::retry() { if (!m_stopRequested) startProcess(); } void NinePConnection::setState(State state) { if (m_state == state) return; const State previous = m_state; m_state = state; emit stateChanged(state); if (state == Connected) { emit connected(); if (m_probe && m_process) m_process->closeWriteChannel(); } else if (state != Connected && previous != Disconnected) emit disconnected(); }