#include "pythonrunner.h" #include #include #include #include PythonRunner::PythonRunner(QTextEdit* outputWidget, QObject* parent) : QObject(parent), m_outputWidget(outputWidget), m_process(nullptr), m_venvProcess(nullptr), ready(false), venvPreparing(false) { m_process = new QProcess(this); m_venvProcess = new QProcess(this); connect(m_process, &QProcess::readyReadStandardOutput, this, &PythonRunner::onOutputAvailable); connect(m_process, &QProcess::readyReadStandardError, this, &PythonRunner::onErrorAvailable); connect(m_process, QOverload::of(&QProcess::finished), this, &PythonRunner::onProcessFinished); connect(m_venvProcess, &QProcess::readyReadStandardOutput, this, [this]() { QByteArray output = m_venvProcess->readAllStandardOutput(); m_outputWidget->append(output); m_outputWidget->moveCursor(QTextCursor::End); emit venvPreparationProgress(output); }); connect(m_venvProcess, &QProcess::readyReadStandardError, this, [this]() { QByteArray error = m_venvProcess->readAllStandardError(); m_outputWidget->append(error); m_outputWidget->moveCursor(QTextCursor::End); emit venvPreparationProgress(error); }); connect(m_venvProcess, QOverload::of(&QProcess::finished), this, &PythonRunner::onVenvProcessFinished); } PythonRunner::~PythonRunner() { if (m_process) { m_process->terminate(); m_process->waitForFinished(1000); } if (m_venvProcess) { m_venvProcess->terminate(); m_venvProcess->waitForFinished(1000); } } bool PythonRunner::runScript(const QString& scriptContent) { if (m_process->state() == QProcess::Running) { m_process->terminate(); m_process->waitForFinished(1000); } m_outputWidget->clear(); // Check if venv is ready, if not prepare it if (!isVenvReady()) { m_outputWidget->append("Preparing Python environment...\n"); m_outputWidget->moveCursor(QTextCursor::End); prepareVenv(); return true; // Return early - venv preparation will handle script execution when ready } QString pythonPath = getVenvPythonPath(); m_process->start(pythonPath, QStringList() << "-u" << "-c" << scriptContent); return true; } void PythonRunner::stopScript() { if (m_process && m_process->state() == QProcess::Running) { m_process->terminate(); } } void PythonRunner::onOutputAvailable() { QByteArray output = m_process->readAllStandardOutput(); m_outputWidget->append(output); m_outputWidget->moveCursor(QTextCursor::End); } void PythonRunner::onErrorAvailable() { QByteArray error = m_process->readAllStandardError(); m_outputWidget->append(error); m_outputWidget->moveCursor(QTextCursor::End); } void PythonRunner::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit && exitCode != 0) { m_outputWidget->append(QString("Process exited with code %1\n").arg(exitCode)); } else if (exitStatus == QProcess::CrashExit) { m_outputWidget->append("Python was stopped\n"); } emit scriptFinished(exitCode); } void PythonRunner::onVenvProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit && exitCode == 0) { // Check if this was venv creation or pip installation QString venvPythonPath = getVenvPythonPath(); if (!QFile::exists(venvPythonPath)) { // This was venv creation, now install eismultiplexer package installEismultiplexerPackage(); } else { // This was pip installation, mark as ready ready = true; m_outputWidget->append("Python environment prepared successfully.\n"); // Signal that venv preparation is complete { QMutexLocker locker(&venvMutex); venvPreparing = false; venvCondition.wakeAll(); } emit venvPreparationFinished(); } } else { ready = false; m_outputWidget->append(QString("Failed to prepare Python environment (code %1)\n").arg(exitCode)); // Signal that venv preparation failed { QMutexLocker locker(&venvMutex); venvPreparing = false; venvCondition.wakeAll(); } emit venvPreparationFinished(); } } void PythonRunner::prepareVenv() { // Start venv preparation in a non-blocking manner QMutexLocker locker(&venvMutex); venvPreparing = true; // Create virtual environment QString pythonPath = getPythonExecutable(); QString venvPath = getVenvPath(); m_outputWidget->append(QString("Creating virtual environment at %1...\n").arg(venvPath)); m_outputWidget->moveCursor(QTextCursor::End); QStringList args; args << "-m" << "venv" << venvPath; m_venvProcess->start(pythonPath, args); if (!m_venvProcess->waitForStarted()) { m_outputWidget->append("Failed to start virtual environment creation process.\n"); ready = false; venvPreparing = false; return; } // Don't wait for completion - this will be handled by the finished signal // The installEismultiplexerPackage will be called in the finished signal handler } void PythonRunner::installEismultiplexerPackage() { QString venvPythonPath = getVenvPythonPath(); QString venvPipPath = QString("%1 -m pip").arg(venvPythonPath); m_outputWidget->append("Installing eismultiplexer package...\n"); m_outputWidget->moveCursor(QTextCursor::End); QStringList args; args << "install" << "eismultiplexer"; m_venvProcess->start(venvPipPath, args); if (!m_venvProcess->waitForStarted()) { m_outputWidget->append("Failed to start pip installation process.\n"); ready = false; // Signal that venv preparation failed { QMutexLocker locker(&venvMutex); venvPreparing = false; venvCondition.wakeAll(); } emit venvPreparationFinished(); return; } // Don't wait for completion - this will be handled by the finished signal } QString PythonRunner::getPythonExecutable() { QString pythonPath; // Try to find python3 first, then python QStringList pythonCandidates; pythonCandidates << "python3" << "python"; for (const QString& candidate : pythonCandidates) { QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH"); QStringList pathDirs = path.split(';', Qt::SkipEmptyParts); for (const QString& dir : pathDirs) { QString pythonExecutable = QDir(dir).filePath(candidate); if (QFile::exists(pythonExecutable)) { pythonPath = pythonExecutable; break; } } if (!pythonPath.isEmpty()) { break; } } // If still not found, use the default python from QProcessEnvironment if (pythonPath.isEmpty()) { pythonPath = "python"; } return pythonPath; } QString PythonRunner::getVenvPath() { // Create venv in a temporary directory QString venvDir = dir.path() + QDir::separator() + "venv"; return venvDir; } QString PythonRunner::getVenvPythonPath() { QString venvPath = getVenvPath(); QString venvPython; // Determine the correct python path for the venv based on platform QString pythonExecutable = "python"; // On Windows, the python executable is in Scripts directory // On Linux/macOS, it's in bin directory QString venvBinDir; #ifdef Q_OS_WIN venvBinDir = venvPath + QDir::separator() + "Scripts"; #else venvBinDir = venvPath + QDir::separator() + "bin"; #endif venvPython = QDir(venvBinDir).filePath(pythonExecutable); return venvPython; } bool PythonRunner::isVenvReady() { QString venvPythonPath = getVenvPythonPath(); return QFile::exists(venvPythonPath); }