File indexing completed on 2024-04-28 08:44:23

0001 /*
0002     SPDX-FileCopyrightText: 2022 Julius Künzel <jk.kdedev@smartlab.uber.space>
0003 
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "abstractpythoninterface.h"
0008 #include "core.h"
0009 #include "mainwindow.h"
0010 #include "utils/KMessageBox_KdenliveCompat.h"
0011 
0012 #include <KIO/DirectorySizeJob>
0013 #include <KLocalizedString>
0014 #include <KMessageBox>
0015 #include <QAction>
0016 #include <QDebug>
0017 #include <QGuiApplication>
0018 #include <QMutex>
0019 #include <QProcess>
0020 #include <QStandardPaths>
0021 #include <QtConcurrent>
0022 
0023 static QMutex mutex;
0024 static bool installInProgress;
0025 
0026 PythonDependencyMessage::PythonDependencyMessage(QWidget *parent, AbstractPythonInterface *interface, bool setupErrorOnly)
0027     : KMessageWidget(parent)
0028     , m_interface(interface)
0029 {
0030     setWordWrap(true);
0031     setCloseButtonVisible(false);
0032     hide();
0033     m_installAction = new QAction(i18n("Install missing dependencies"), this);
0034     m_abortAction = new QAction(i18n("Abort installation"), this);
0035     connect(m_abortAction, &QAction::triggered, m_interface, &AbstractPythonInterface::abortScript);
0036     connect(m_interface, &AbstractPythonInterface::setupError, this, [&](const QString &message) {
0037         removeAction(m_installAction);
0038         removeAction(m_abortAction);
0039         doShowMessage(message, KMessageWidget::Warning);
0040     });
0041     connect(m_interface, &AbstractPythonInterface::setupMessage, this,
0042             [&](const QString &message, int messageType) { doShowMessage(message, KMessageWidget::MessageType(messageType)); });
0043 
0044     if (!setupErrorOnly) {
0045         connect(m_interface, &AbstractPythonInterface::checkVersionsResult, this, [&](const QStringList &list) {
0046             removeAction(m_abortAction);
0047             if (list.isEmpty()) {
0048                 if (m_interface->featureName().isEmpty()) {
0049                     doShowMessage(i18n("Everything is properly configured."), KMessageWidget::Positive);
0050                 } else {
0051                     doShowMessage(i18n("%1 is properly configured.", m_interface->featureName()), KMessageWidget::Positive);
0052                 }
0053             } else {
0054                 if (m_interface->featureName().isEmpty()) {
0055                     doShowMessage(i18n("Everything is configured: %1", list.join(QStringLiteral(", "))), KMessageWidget::Positive);
0056                 } else {
0057                     doShowMessage(i18n("%1 is configured: %2", m_interface->featureName(), list.join(QStringLiteral(", "))), KMessageWidget::Positive);
0058                 }
0059             }
0060         });
0061 
0062         connect(m_interface, &AbstractPythonInterface::dependenciesMissing, this, [&](const QStringList &messages) {
0063             if (!m_interface->installDisabled()) {
0064                 m_installAction->setEnabled(true);
0065                 removeAction(m_abortAction);
0066                 m_installAction->setText(i18n("Install missing dependencies"));
0067                 addAction(m_installAction);
0068             }
0069 
0070             doShowMessage(messages.join(QStringLiteral("\n")), KMessageWidget::Warning);
0071         });
0072 
0073         if (!m_interface->installDisabled()) {
0074             connect(m_interface, &AbstractPythonInterface::proposeUpdate, this, [&](const QString &message) {
0075                 // only allow upgrading python modules once
0076                 m_installAction->setText(i18n("Check for update"));
0077                 m_installAction->setEnabled(true);
0078                 removeAction(m_abortAction);
0079                 addAction(m_installAction);
0080                 doShowMessage(message, KMessageWidget::Warning);
0081             });
0082         }
0083 
0084         connect(m_interface, &AbstractPythonInterface::dependenciesAvailable, this, [&]() {
0085             if (!m_updated && !m_interface->installDisabled()) {
0086                 // only allow upgrading python modules once
0087                 m_installAction->setText(i18n("Check for update"));
0088                 m_installAction->setEnabled(true);
0089                 removeAction(m_abortAction);
0090                 addAction(m_installAction);
0091             }
0092             if (text().isEmpty()) {
0093                 hide();
0094             }
0095         });
0096 
0097         connect(m_installAction, &QAction::triggered, this, [&]() {
0098             if (!m_interface->missingDependencies().isEmpty()) {
0099                 m_installAction->setEnabled(false);
0100                 doShowMessage(i18n("Installing modules… this can take a while"), KMessageWidget::Information);
0101                 addAction(m_abortAction);
0102                 qApp->processEvents();
0103                 m_interface->installMissingDependencies();
0104                 removeAction(m_installAction);
0105             } else {
0106                 // upgrade
0107                 m_updated = true;
0108                 m_installAction->setEnabled(false);
0109                 addAction(m_abortAction);
0110                 doShowMessage(i18n("Updating modules…"), KMessageWidget::Information);
0111                 qApp->processEvents();
0112                 m_interface->updateDependencies();
0113                 removeAction(m_installAction);
0114             }
0115         });
0116     }
0117 }
0118 
0119 void PythonDependencyMessage::doShowMessage(const QString &message, KMessageWidget::MessageType messageType)
0120 {
0121     if (message.isEmpty()) {
0122         hide();
0123     } else {
0124         setMessageType(messageType);
0125         setText(message);
0126         show();
0127     }
0128 }
0129 
0130 void PythonDependencyMessage::checkAfterInstall()
0131 {
0132     doShowMessage(i18n("Checking configuration…"), KMessageWidget::Information);
0133     m_interface->checkDependencies(true, false);
0134 }
0135 
0136 AbstractPythonInterface::AbstractPythonInterface(QObject *parent)
0137     : QObject{parent}
0138     , m_dependencies()
0139     , m_versions(new QMap<QString, QString>())
0140     , m_disableInstall(pCore->packageType() == QStringLiteral("flatpak"))
0141     , m_dependenciesChecked(false)
0142     , m_scripts(new QMap<QString, QString>())
0143 {
0144     addScript(QStringLiteral("checkpackages.py"));
0145     addScript(QStringLiteral("checkgpu.py"));
0146 }
0147 
0148 AbstractPythonInterface::~AbstractPythonInterface()
0149 {
0150     delete m_versions;
0151     delete m_scripts;
0152 }
0153 
0154 bool AbstractPythonInterface::checkPython(bool useVenv, bool calculateSize, bool forceInstall)
0155 {
0156     if (installInProgress) {
0157         return false;
0158     }
0159     if (!calculateSize && useVenv == KdenliveSettings::usePythonVenv() && !KdenliveSettings::pythonPath().isEmpty() && !KdenliveSettings::pipPath().isEmpty() &&
0160         QFile::exists(KdenliveSettings::pythonPath())) {
0161         // Already setup
0162         if (KdenliveSettings::pythonPath().contains(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation))) {
0163             if (useVenv) {
0164                 // Everything ok, using venv python
0165                 qDebug() << "::::: CHECKING PYTHON WITH VENV EVEYTHING OK...";
0166                 return true;
0167             }
0168         } else if (!useVenv) {
0169             // Everything ok, using system python
0170             qDebug() << "::::: CHECKING PYTHON NO VENV EVEYTHING OK...";
0171             return true;
0172         }
0173     }
0174     qDebug() << "::::: CHECKING PYTHON, RQST VENV: " << useVenv;
0175     Q_EMIT setupMessage(i18nc("@label:textbox", "Checking setup…"), int(KMessageWidget::Information));
0176     QMutexLocker bk(&mutex);
0177     QStringList pythonPaths;
0178     if (useVenv) {
0179 #ifdef Q_OS_WIN
0180         const QString pythonPath = QStringLiteral("venv/Scripts/");
0181 #else
0182         const QString pythonPath = QStringLiteral("venv/bin/");
0183 #endif
0184         QDir pluginDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
0185         if (!pluginDir.exists(pythonPath)) {
0186             // Setup venv
0187             if (forceInstall && !setupVenv()) {
0188                 return false;
0189             } else {
0190                 // Venv folder not found, disable
0191                 useVenv = false;
0192             }
0193         }
0194         if (pluginDir.exists(pythonPath)) {
0195             pythonPaths << pluginDir.absoluteFilePath(pythonPath);
0196         }
0197     }
0198 #ifdef Q_OS_WIN
0199     KdenliveSettings::setPythonPath(QStandardPaths::findExecutable(QStringLiteral("python"), pythonPaths));
0200     KdenliveSettings::setPipPath(QStandardPaths::findExecutable(QStringLiteral("pip"), pythonPaths));
0201 #else
0202     KdenliveSettings::setPythonPath(QStandardPaths::findExecutable(QStringLiteral("python3"), pythonPaths));
0203     KdenliveSettings::setPipPath(QStandardPaths::findExecutable(QStringLiteral("pip3"), pythonPaths));
0204 #endif
0205     if (KdenliveSettings::pythonPath().isEmpty()) {
0206         Q_EMIT setupError(i18n("Cannot find python3, please install it on your system.\n"
0207                                "If already installed, check it is installed in a directory "
0208                                "listed in PATH environment variable"));
0209         return false;
0210     }
0211     if (KdenliveSettings::pipPath().isEmpty() && !m_disableInstall) {
0212         Q_EMIT setupError(i18n("Cannot find pip3, please install it on your system.\n"
0213                                "If already installed, check it is installed in a directory "
0214                                "listed in PATH environment variable"));
0215         return false;
0216     }
0217     if (useVenv != KdenliveSettings::usePythonVenv()) {
0218         KdenliveSettings::setUsePythonVenv(useVenv);
0219         Q_EMIT venvSetupChanged();
0220     }
0221     Q_EMIT setupMessage(i18n("Using python from %1", QFileInfo(KdenliveSettings::pythonPath()).absolutePath()), int(KMessageWidget::Information));
0222     if (calculateSize) {
0223         // Calculate venv size
0224         QDir pluginDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
0225         if (pluginDir.cd(QStringLiteral("venv"))) {
0226             KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(pluginDir.absolutePath()));
0227             connect(job, &KIO::DirectorySizeJob::result, this, &AbstractPythonInterface::gotFolderSize);
0228         } else {
0229             Q_EMIT gotPythonSize(i18n("No python venv found"));
0230         }
0231     }
0232     return true;
0233 }
0234 
0235 void AbstractPythonInterface::gotFolderSize(KJob *job)
0236 {
0237     auto *sourceJob = static_cast<KIO::DirectorySizeJob *>(job);
0238     KIO::filesize_t total = sourceJob->totalSize();
0239     if (sourceJob->totalFiles() == 0) {
0240         total = 0;
0241     }
0242     Q_EMIT gotPythonSize(i18n("Python venv size: %1", KIO::convertSize(total)));
0243 }
0244 
0245 bool AbstractPythonInterface::checkSetup()
0246 {
0247     if (!(KdenliveSettings::pythonPath().isEmpty() || KdenliveSettings::pipPath().isEmpty() || m_scripts->values().contains(QStringLiteral("")))) {
0248         return true;
0249     }
0250     if (!checkPython(KdenliveSettings::usePythonVenv())) {
0251         return false;
0252     }
0253 
0254     for (int i = 0; i < m_scripts->count(); i++) {
0255         QString key = m_scripts->keys()[i];
0256         (*m_scripts)[key] = locateScript(key);
0257         if ((*m_scripts)[key].isEmpty()) {
0258             return false;
0259         }
0260     }
0261     return true;
0262 }
0263 
0264 bool AbstractPythonInterface::setupVenv()
0265 {
0266     // First check if python and venv are available
0267     QString pyExec;
0268 #ifdef Q_OS_WIN
0269     pyExec = QStandardPaths::findExecutable(QStringLiteral("python"));
0270 #else
0271     pyExec = QStandardPaths::findExecutable(QStringLiteral("python3"));
0272 #endif
0273     // Check that the system python is found
0274     if (pyExec.isEmpty()) {
0275         Q_EMIT setupError(i18n("Cannot find python3, please install it on your system.\n"
0276                                "If already installed, check it is installed in a directory "
0277                                "listed in PATH environment variable"));
0278         return false;
0279     }
0280     // Use system python to check for venv
0281     installInProgress = true;
0282     KdenliveSettings::setPythonPath(pyExec);
0283     const QString missingDeps = runScript(QStringLiteral("checkpackages.py"), {"virtualenv"}, QStringLiteral("--check"), false);
0284     if (!missingDeps.isEmpty()) {
0285         Q_EMIT setupError(i18n("Cannot find python virtualenv, please install it on your system. Defaulting to system python."));
0286         installInProgress = false;
0287         return false;
0288     }
0289     QDir pluginDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
0290     pluginDir.mkpath(QStringLiteral("."));
0291 
0292     QProcess envProcess;
0293     // For some reason, this fails in AppImage, but when extracting the Appimage it works...
0294     // No workaround found yet for AppImage
0295     QStringList args = {QStringLiteral("-m"), QStringLiteral("venv"), pluginDir.absoluteFilePath(QStringLiteral("venv"))};
0296     envProcess.start(pyExec, args);
0297     envProcess.waitForStarted();
0298     envProcess.waitForFinished(-1);
0299     if (envProcess.exitStatus() == QProcess::NormalExit) {
0300 #ifdef Q_OS_WIN
0301         const QString pythonPath = QStringLiteral("venv/Scripts/");
0302         QStringList pythonPaths = {pluginDir.absoluteFilePath(pluginDir.absoluteFilePath(pythonPath))};
0303         KdenliveSettings::setPythonPath(QStandardPaths::findExecutable(QStringLiteral("python"), pythonPaths));
0304         KdenliveSettings::setPipPath(QStandardPaths::findExecutable(QStringLiteral("pip"), pythonPaths));
0305 #else
0306         const QString pythonPath = QStringLiteral("venv/bin/");
0307         QStringList pythonPaths = {pluginDir.absoluteFilePath(pluginDir.absoluteFilePath(pythonPath))};
0308         KdenliveSettings::setPythonPath(QStandardPaths::findExecutable(QStringLiteral("python3"), pythonPaths));
0309         KdenliveSettings::setPipPath(QStandardPaths::findExecutable(QStringLiteral("pip3"), pythonPaths));
0310 #endif
0311         if (!KdenliveSettings::pipPath().isEmpty()) {
0312             installPackage({QStringLiteral("importlib")});
0313             installInProgress = false;
0314             return true;
0315         }
0316     }
0317     installInProgress = false;
0318     return false;
0319 }
0320 
0321 QString AbstractPythonInterface::locateScript(const QString &script)
0322 {
0323     QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("scripts/%1").arg(script));
0324     if (path.isEmpty()) {
0325         Q_EMIT setupError(i18n("The %1 script was not found, check your install.", script));
0326     }
0327     return path;
0328 }
0329 
0330 void AbstractPythonInterface::addDependency(const QString &pipname, const QString &purpose)
0331 {
0332     m_dependencies.insert(pipname, purpose);
0333 }
0334 
0335 void AbstractPythonInterface::addScript(const QString &script)
0336 {
0337     m_scripts->insert(script, QStringLiteral(""));
0338 }
0339 
0340 void AbstractPythonInterface::checkDependenciesConcurrently()
0341 {
0342 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0343     QtConcurrent::run(this, &AbstractPythonInterface::checkDependencies, false, false);
0344 #else
0345     QtConcurrent::run(&AbstractPythonInterface::checkDependencies, this, false, false);
0346 #endif
0347 }
0348 
0349 void AbstractPythonInterface::checkVersionsConcurrently()
0350 {
0351 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0352     QtConcurrent::run(this, &AbstractPythonInterface::checkVersions, true);
0353 #else
0354     QtConcurrent::run(&AbstractPythonInterface::checkVersions, this, true);
0355 #endif
0356 }
0357 
0358 void AbstractPythonInterface::checkDependencies(bool force, bool async)
0359 {
0360     if (!force && m_dependenciesChecked) {
0361         // Don't check twice if dependecies are satisfied
0362         return;
0363     }
0364     // Force check, reset flag
0365     m_missing.clear();
0366     const QString output = runPackageScript(QStringLiteral("--check"));
0367     QStringList messages;
0368     if (!output.isEmpty()) {
0369         // We have missing dependencies
0370         for (auto i : m_dependencies.keys()) {
0371             if (output.contains(i)) {
0372                 m_missing.append(i);
0373                 if (m_dependencies.value(i).isEmpty()) {
0374                     messages.append(xi18n("The <application>%1</application> python module is required.", i));
0375                 } else {
0376                     messages.append(xi18n("The <application>%1</application> python module is required for %2.", i, m_dependencies.value(i)));
0377                 }
0378             }
0379         }
0380     }
0381     m_dependenciesChecked = true;
0382     if (messages.isEmpty()) {
0383         Q_EMIT dependenciesAvailable();
0384         if (async) {
0385             checkVersionsConcurrently();
0386         } else {
0387             checkVersions(true);
0388         }
0389     } else {
0390         Q_EMIT dependenciesMissing(messages);
0391     }
0392 }
0393 
0394 QStringList AbstractPythonInterface::missingDependencies(const QStringList &filter)
0395 {
0396     if (filter.isEmpty()) {
0397         return m_missing;
0398     }
0399     QStringList filtered;
0400     for (auto item : filter) {
0401         if (m_missing.contains(item)) {
0402             filtered.append(item);
0403         }
0404     }
0405     return filtered;
0406 };
0407 
0408 void AbstractPythonInterface::installMissingDependencies()
0409 {
0410     if (!KdenliveSettings::usePythonVenv()) {
0411         QDir pluginDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
0412         if (KMessageBox::questionTwoActions(
0413                 pCore->window(),
0414                 i18n("Kdenlive can install the missing python modules in a virtual environment under %1.\nThis way, it won't touch your system libraries.",
0415                      pluginDir.absoluteFilePath(QStringLiteral("venv"))),
0416                 i18n("Python environment"), KGuiItem(i18n("Use virtual environment (recommended)")),
0417                 KGuiItem(i18n("Use system install"))) == KMessageBox::PrimaryAction) {
0418             if (!checkPython(true, true, true)) {
0419                 return;
0420             }
0421         }
0422     }
0423     runPackageScript(QStringLiteral("--install"), true);
0424 }
0425 
0426 void AbstractPythonInterface::updateDependencies()
0427 {
0428     runPackageScript(QStringLiteral("--upgrade"), true);
0429 }
0430 
0431 void AbstractPythonInterface::runConcurrentScript(const QString &script, QStringList args)
0432 {
0433     if (m_dependencies.keys().isEmpty()) {
0434         qWarning() << "No dependencies specified";
0435         Q_EMIT setupError(i18n("Internal Error: Cannot find dependency list"));
0436         return;
0437     }
0438     if (!checkSetup()) {
0439         return;
0440     }
0441 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0442     QtConcurrent::run(this, &AbstractPythonInterface::runScript, script, args, QString(), true, false);
0443 #else
0444     QtConcurrent::run(&AbstractPythonInterface::runScript, this, script, args, QString(), true, false);
0445 #endif
0446 }
0447 
0448 void AbstractPythonInterface::proposeMaybeUpdate(const QString &dependency, const QString &minVersion)
0449 {
0450     checkVersions(false);
0451     QString currentVersion = m_versions->value(dependency);
0452     if (currentVersion.isEmpty()) {
0453         Q_EMIT setupError(i18n("Error while checking version of module %1", dependency));
0454         return;
0455     }
0456     if (versionToInt(currentVersion) < versionToInt(minVersion)) {
0457         Q_EMIT proposeUpdate(i18n("At least version %1 of module %2 is required, "
0458                                   "but your current version is %3",
0459                                   minVersion, dependency, currentVersion));
0460     } else {
0461         Q_EMIT proposeUpdate(i18n("Please consider to update your setup."));
0462     }
0463 }
0464 
0465 int AbstractPythonInterface::versionToInt(const QString &version)
0466 {
0467     QStringList v = version.split(QStringLiteral("."));
0468     return QT_VERSION_CHECK(v.length() > 0 ? v.at(0).toInt() : 0, v.length() > 1 ? v.at(1).toInt() : 0, v.length() > 2 ? v.at(2).toInt() : 0);
0469 }
0470 
0471 void AbstractPythonInterface::checkVersions(bool signalOnResult)
0472 {
0473     if (installDisabled()) {
0474         return;
0475     }
0476     QString output = runPackageScript(QStringLiteral("--details"));
0477     if (output.isEmpty() || !output.contains(QStringLiteral("Version: "))) {
0478         Q_EMIT setupMessage(i18nc("@label:textbox", "No version information available."), int(KMessageWidget::Warning));
0479         qDebug() << "::: CHECKING DEPENDENCIES... NO VERSION INFO AVAILABLE";
0480         return;
0481     }
0482     QStringList raw = output.split(QStringLiteral("Version: "));
0483     QStringList versions;
0484     for (int i = 0; i < raw.count(); i++) {
0485         QString name = raw.at(i);
0486         int pos = name.indexOf(QStringLiteral("Name:"));
0487         if (pos > -1) {
0488             if (pos != 0) {
0489                 name.remove(0, pos);
0490             }
0491             name = name.simplified().section(QLatin1Char(' '), 1, 1);
0492             QString version = raw.at(i + 1);
0493             version = version.simplified().section(QLatin1Char(' '), 0, 0);
0494             versions.append(QString("%1 %2").arg(name, version));
0495             if (m_versions->contains(name)) {
0496                 (*m_versions)[name.toLower()] = version;
0497             } else {
0498                 m_versions->insert(name.toLower(), version);
0499             }
0500         }
0501     }
0502     qDebug() << "::: CHECKING DEPENDENCIES... VERSION FOUND: " << versions;
0503     if (signalOnResult) {
0504         Q_EMIT checkVersionsResult(versions);
0505     }
0506 }
0507 
0508 QString AbstractPythonInterface::runPackageScript(const QString &mode, bool concurrent)
0509 {
0510     if (m_dependencies.keys().isEmpty()) {
0511         qWarning() << "No dependencies specified";
0512         Q_EMIT setupError(i18n("Internal Error: Cannot find dependency list"));
0513         return {};
0514     }
0515     if (!checkSetup()) {
0516         return {};
0517     }
0518     if (concurrent) {
0519 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0520         QtConcurrent::run(this, &AbstractPythonInterface::runScript, QStringLiteral("checkpackages.py"), m_dependencies.keys(), mode, concurrent, true);
0521 #else
0522         QtConcurrent::run(&AbstractPythonInterface::runScript, this, QStringLiteral("checkpackages.py"), m_dependencies.keys(), mode, concurrent, true);
0523 #endif
0524         return {};
0525     } else {
0526         return runScript(QStringLiteral("checkpackages.py"), m_dependencies.keys(), mode, concurrent, true);
0527     }
0528 }
0529 
0530 QString AbstractPythonInterface::installPackage(const QStringList packageNames)
0531 {
0532     if (!checkSetup()) {
0533         return {};
0534     }
0535     return runScript(QStringLiteral("checkpackages.py"), packageNames, "--install", false, true);
0536 }
0537 
0538 QString AbstractPythonInterface::runScript(const QString &script, QStringList args, const QString &firstarg, bool concurrent, bool packageFeedback)
0539 {
0540     QString scriptpath = m_scripts->value(script);
0541     if (KdenliveSettings::pythonPath().isEmpty() || scriptpath.isEmpty()) {
0542         if (KdenliveSettings::pythonPath().isEmpty()) {
0543             Q_EMIT setupError(i18n("Python exec not found"));
0544         } else {
0545             Q_EMIT setupError(i18n("Failed to find script file %1", script));
0546         }
0547         return {};
0548     }
0549     if (concurrent && (firstarg == QLatin1String("--install") || firstarg == QLatin1String("--upgrade"))) {
0550         Q_EMIT scriptStarted();
0551     }
0552     if (!firstarg.isEmpty()) {
0553         args.prepend(firstarg);
0554     }
0555     args.prepend(scriptpath);
0556     QProcess scriptJob;
0557     if (concurrent) {
0558         if (packageFeedback) {
0559             connect(&scriptJob, &QProcess::readyReadStandardOutput, [this, &scriptJob]() {
0560                 const QString processData = QString::fromUtf8(scriptJob.readAll());
0561                 if (!processData.isEmpty()) {
0562                     Q_EMIT installFeedback(processData.simplified());
0563                 }
0564             });
0565         } else {
0566             connect(&scriptJob, &QProcess::readyReadStandardOutput, [this, &scriptJob]() {
0567                 const QString processData = QString::fromUtf8(scriptJob.readAll());
0568                 Q_EMIT scriptFeedback(processData.split(QLatin1Char('\n'), Qt::SkipEmptyParts));
0569             });
0570         }
0571     }
0572     connect(this, &AbstractPythonInterface::abortScript, &scriptJob, &QProcess::kill, Qt::DirectConnection);
0573     scriptJob.start(KdenliveSettings::pythonPath(), args);
0574     // Don't timeout
0575     qDebug() << "::: RUNNONG SCRIPT: " << KdenliveSettings::pythonPath() << " = " << args;
0576     scriptJob.waitForFinished(-1);
0577     if (!concurrent && (scriptJob.exitStatus() != QProcess::NormalExit || scriptJob.exitCode() != 0)) {
0578         qDebug() << "::::: WARNING ERRROR EXIT STATUS: " << scriptJob.exitCode();
0579         const QString errorMessage = scriptJob.readAllStandardError();
0580         Q_EMIT setupError(i18n("Error while running python3 script:\n %1\n%2", scriptpath, errorMessage));
0581         qWarning() << " SCRIPT ERROR: " << errorMessage;
0582         return {};
0583     }
0584     if (script == QLatin1String("checkgpu.py")) {
0585         Q_EMIT scriptGpuCheckFinished();
0586     } else if (concurrent && (firstarg == QLatin1String("--install") || firstarg == QLatin1String("--upgrade"))) {
0587         Q_EMIT scriptFinished();
0588     }
0589     return scriptJob.readAllStandardOutput();
0590 }
0591 
0592 bool AbstractPythonInterface::removePythonVenv()
0593 {
0594     QDir pluginDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
0595     if (!pluginDir.exists(QStringLiteral("venv")) || !pluginDir.absolutePath().contains(QStringLiteral("kdenlive"))) {
0596         return false;
0597     }
0598     if (!pluginDir.cd(QStringLiteral("venv"))) {
0599         return false;
0600     }
0601     if (KMessageBox::warningContinueCancel(pCore->window(),
0602                                            i18n("This will delete the python virtual environment from:<br/><b>%1</b><br/>The environment will be recreated "
0603                                                 "and modules downloaded whenever you reenable the python virtual environment.",
0604                                                 pluginDir.absolutePath())) != KMessageBox::Continue) {
0605         return false;
0606     }
0607     return pluginDir.removeRecursively();
0608 }
0609 
0610 bool AbstractPythonInterface::installInProcess() const
0611 {
0612     return installInProgress;
0613 }