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