File indexing completed on 2024-05-05 11:55:47

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
0004 */
0005 #include "juliasession.h"
0006 
0007 #include <random>
0008 
0009 #include <KProcess>
0010 #include <KMessageBox>
0011 #include <KLocalizedString>
0012 #include <QDBusConnection>
0013 #include <QDBusInterface>
0014 #include <QDBusReply>
0015 #include <QStandardPaths>
0016 #include <QDir>
0017 
0018 #include "defaultvariablemodel.h"
0019 
0020 #include "juliaexpression.h"
0021 #include "settings.h"
0022 #include "juliahighlighter.h"
0023 #include "juliakeywords.h"
0024 #include "juliavariablemodel.h"
0025 #include "juliaextensions.h"
0026 #include "juliabackend.h"
0027 #include "juliacompletionobject.h"
0028 
0029 using namespace Cantor;
0030 
0031 JuliaSession::JuliaSession(Cantor::Backend* backend) : Session(backend)
0032 {
0033     setVariableModel(new JuliaVariableModel(this));
0034 }
0035 
0036 JuliaSession::~JuliaSession()
0037 {
0038     if (m_process)
0039     {
0040         m_process->kill();
0041         m_process->deleteLater();
0042         m_process = nullptr;
0043     }
0044 }
0045 
0046 void JuliaSession::login()
0047 {
0048     if (m_process)
0049         return;
0050     emit loginStarted();
0051 
0052     m_process = new KProcess(this);
0053     m_process->setOutputChannelMode(KProcess::OnlyStdoutChannel);
0054 
0055 #ifdef Q_OS_WIN
0056     (*m_process)
0057         << QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver.exe"));
0058 #else
0059     (*m_process)
0060         << QStandardPaths::findExecutable(QLatin1String("cantor_juliaserver"));
0061 #endif
0062 
0063     connect(m_process, &QProcess::errorOccurred, this, &JuliaSession::reportServerProcessError);
0064 
0065     m_process->start();
0066     m_process->waitForStarted();
0067     m_process->waitForReadyRead();
0068     QTextStream stream(m_process->readAllStandardOutput());
0069 
0070     QString readyStatus = QLatin1String("ready");
0071     while (m_process->state() == QProcess::Running) {
0072         const QString &rl = stream.readLine();
0073         if (rl == readyStatus) {
0074             break;
0075         }
0076     }
0077 
0078     if (!QDBusConnection::sessionBus().isConnected()) {
0079         qWarning() << "Can't connect to the D-Bus session bus.\n"
0080                       "To start it, run: eval `dbus-launch --auto-syntax`";
0081         return;
0082     }
0083 
0084     const QString &serviceName =
0085         QString::fromLatin1("org.kde.Cantor.Julia-%1").arg(m_process->processId());
0086 
0087     m_interface = new QDBusInterface(
0088         serviceName,
0089         QString::fromLatin1("/"),
0090         QString(),
0091         QDBusConnection::sessionBus()
0092     );
0093 
0094     if (!m_interface->isValid()) {
0095         qWarning() << QDBusConnection::sessionBus().lastError().message();
0096         return;
0097     }
0098 
0099     const QDBusReply<int> &reply = m_interface->call(QLatin1String("login"));
0100     if (reply.isValid())
0101     {
0102         int errorCode = reply.value();
0103         if (errorCode != 0)
0104         {
0105             if (errorCode == 1)
0106             {
0107                 const QString& juliaSysimgMissingFilepath = getError();
0108                 KMessageBox::error(nullptr, i18n("Julia session can't login due internal julia problem with missing internal file - \"%1\"", juliaSysimgMissingFilepath), i18n("Error - Cantor"));
0109                 return;
0110             }
0111             else // All error codes should be supported
0112                 assert(false);
0113         }
0114     }
0115     else
0116     {
0117         KMessageBox::error(nullptr, i18n("Julia session can't login due unknown internal problem"), i18n("Error - Cantor"));
0118         return;
0119     }
0120 
0121     static_cast<JuliaVariableModel*>(variableModel())->setJuliaServer(m_interface);
0122 
0123     std::random_device rd;
0124     std::mt19937 mt(rd());
0125     std::uniform_int_distribution<int> rand_dist(0, 999999999);
0126     m_plotFilePrefixPath =
0127         QDir::tempPath()
0128         + QLatin1String("/cantor_octave_")
0129         + QString::number(m_process->processId())
0130         + QLatin1String("_")
0131         + QString::number(rand_dist(mt))
0132         + QLatin1String("_");
0133 
0134     updateVariables();
0135 
0136     changeStatus(Session::Done);
0137     emit loginDone();
0138     qDebug() << "login to julia done";
0139 }
0140 
0141 void JuliaSession::logout()
0142 {
0143     if(!m_process)
0144         return;
0145 
0146     if(status() == Cantor::Session::Running)
0147     {
0148         if(m_process && m_process->state() == QProcess::Running)
0149         {
0150             disconnect(m_process, &QProcess::errorOccurred, this, &JuliaSession::reportServerProcessError);
0151             m_process->kill();
0152         }
0153         m_process->deleteLater();
0154         m_process = nullptr;
0155         interrupt();
0156     }
0157 
0158     if (!m_plotFilePrefixPath.isEmpty())
0159     {
0160         int i = 0;
0161         const QString& extension = JuliaExpression::plotExtensions[JuliaSettings::inlinePlotFormat()];
0162         QString filename = m_plotFilePrefixPath + QString::number(i) + QLatin1String(".") + extension;
0163         while (QFile::exists(filename))
0164         {
0165             QFile::remove(filename);
0166             i++;
0167             filename = m_plotFilePrefixPath + QString::number(i) + QLatin1String(".") + extension;
0168         }
0169     }
0170     m_isIntegratedPlotsEnabled = false;
0171     m_isIntegratedPlotsSettingsEnabled = false;
0172 
0173     Session::logout();
0174 }
0175 
0176 void JuliaSession::interrupt()
0177 {
0178     if (expressionQueue().isEmpty())
0179         return;
0180 
0181     if (m_process && m_process->processId())
0182     {
0183         disconnect(m_process, &QProcess::errorOccurred, this, &JuliaSession::reportServerProcessError);
0184         m_process->kill();
0185     }
0186 
0187     qDebug()<<"interrupting " << expressionQueue().first()->command();
0188     for(auto* expression : expressionQueue())
0189         expression->setStatus(Cantor::Expression::Interrupted);
0190 
0191     expressionQueue().clear();
0192 
0193     changeStatus(Cantor::Session::Done);
0194 }
0195 
0196 Cantor::Expression *JuliaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal)
0197 {
0198     auto* expr = new JuliaExpression(this, internal);
0199     expr->setFinishingBehavior(behave);
0200     expr->setCommand(cmd);
0201     expr->evaluate();
0202 
0203     return expr;
0204 }
0205 
0206 Cantor::CompletionObject* JuliaSession::completionFor(const QString &command, int index)
0207 {
0208     return new JuliaCompletionObject(command, index, this);
0209 }
0210 
0211 QSyntaxHighlighter* JuliaSession::syntaxHighlighter(QObject *parent)
0212 {
0213     return new JuliaHighlighter(parent, this);
0214 }
0215 
0216 void JuliaSession::runJuliaCommand(const QString &command) const
0217 {
0218     m_interface->call(QLatin1String("runJuliaCommand"), command);
0219 }
0220 
0221 void JuliaSession::runJuliaCommandAsync(const QString &command)
0222 {
0223     m_interface->callWithCallback(
0224         QLatin1String("runJuliaCommand"),
0225         {command},
0226         this,
0227         SLOT(onResultReady())
0228     );
0229 }
0230 
0231 void JuliaSession::onResultReady()
0232 {
0233     static_cast<JuliaExpression*>(expressionQueue().first())->finalize(getOutput(), getError(), getWasException());
0234     finishFirstExpression(true);
0235 }
0236 
0237 void JuliaSession::reportServerProcessError(QProcess::ProcessError serverError)
0238 {
0239     switch(serverError)
0240     {
0241         case QProcess::Crashed:
0242             emit error(i18n("Julia process stopped working."));
0243             break;
0244         case QProcess::FailedToStart:
0245             emit error(i18n("Failed to start Julia process."));
0246             break;
0247         default:
0248             emit error(i18n("Communication with Julia process failed for unknown reasons."));
0249             break;
0250     }
0251     qDebug() << "reportSessionCrash" << serverError;
0252     reportSessionCrash();
0253 }
0254 
0255 void JuliaSession::runFirstExpression()
0256 {
0257     auto* expr = expressionQueue().first();
0258     expr->setStatus(Cantor::Expression::Computing);
0259 
0260     runJuliaCommandAsync(expr->internalCommand());
0261 }
0262 
0263 QString JuliaSession::getStringFromServer(const QString &method)
0264 {
0265     const QDBusReply<QString> &reply = m_interface->call(method);
0266     return (reply.isValid() ? reply.value() : reply.error().message());
0267 }
0268 
0269 QString JuliaSession::getOutput()
0270 {
0271     return getStringFromServer(QLatin1String("getOutput"));
0272 }
0273 
0274 QString JuliaSession::getError()
0275 {
0276     return getStringFromServer(QLatin1String("getError"));
0277 }
0278 
0279 bool JuliaSession::getWasException()
0280 {
0281     const QDBusReply<bool> &reply =
0282         m_interface->call(QLatin1String("getWasException"));
0283     return reply.isValid() && reply.value();
0284 }
0285 
0286 QString JuliaSession::plotFilePrefixPath() const
0287 {
0288     return m_plotFilePrefixPath;
0289 }
0290 
0291 void JuliaSession::updateGraphicPackagesFromSettings()
0292 {
0293     if (m_isIntegratedPlotsSettingsEnabled == JuliaSettings::integratePlots())
0294         return;
0295 
0296     if (m_isIntegratedPlotsEnabled && JuliaSettings::integratePlots() == false)
0297     {
0298         updateEnabledGraphicPackages(QList<Cantor::GraphicPackage>());
0299         m_isIntegratedPlotsEnabled = false;
0300         m_isIntegratedPlotsSettingsEnabled = true;
0301         return;
0302     }
0303     else if (!m_isIntegratedPlotsEnabled && JuliaSettings::integratePlots() == true)
0304     {
0305         m_isIntegratedPlotsEnabled = true;
0306         m_isIntegratedPlotsSettingsEnabled = true;
0307 
0308         updateEnabledGraphicPackages(backend()->availableGraphicPackages());
0309     }
0310 }
0311 
0312 QString JuliaSession::graphicPackageErrorMessage(QString packageId) const
0313 {
0314     QString text;
0315 
0316     if (packageId == QLatin1String("gr")) {
0317         return i18n(
0318             "For Julia only GR (https://gr-framework.org/), a framework for visualization applications, is supported at the moment. "
0319             "This package has to be installed first, if not done yet. "
0320             "For this, run Pkg.install(\"GR\") in Cantor or in Julia REPL. "
0321             "Note, this operation can take some time and it's better to perform it in Julia REPL that is able to show the current progress of the package installation."
0322         );
0323     }
0324     return text;
0325 }
0326 
0327