]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp
Dolphin: Implement package kit for deb/rpm/pacman service packages
[dolphin.git] / src / settings / services / servicemenuinstaller / servicemenuinstaller.cpp
index b1f0f631635ff045b50e17b1186cc46424fa58bf..89f2545f80b9de6982a97685fda1abbfdf0bd652 100644 (file)
 
 #include <QDebug>
 #include <QProcess>
+#include <QTimer>
 #include <QStandardPaths>
 #include <QDir>
 #include <QDirIterator>
 #include <QCommandLineParser>
 #include <QMimeDatabase>
-
+#include <QUrl>
+#include <QGuiApplication>
 #include <KLocalizedString>
+#include <KShell>
+
+#include "../../../config-packagekit.h"
+
+const static QStringList binaryPackages = {QStringLiteral("application/vnd.debian.binary-package"),
+                                     QStringLiteral("application/x-rpm"),
+                                     QStringLiteral("application/x-xz"),
+                                     QStringLiteral("application/zstd")};
+enum PackageOperation {
+    Install,
+    Uninstall
+};
+
+#ifdef HAVE_PACKAGEKIT
+#include <PackageKit/Daemon>
+#include <PackageKit/Details>
+#include <PackageKit/Transaction>
+#else
+#include <QDesktopServices>
+#endif
 
 // @param msg Error that gets logged to CLI
 Q_NORETURN void fail(const QString &str)
 {
     qCritical() << str;
-
-    QProcess process;
-    auto args = QStringList{"--passivepopup", i18n("Dolphin service menu installation failed"), "15"};
-    process.start("kdialog", args, QIODevice::ReadOnly);
-    if (!process.waitForStarted()) {
-        qFatal("Failed to run kdialog");
-    }
+    const QStringList args = {"--detailederror" ,i18n("Dolphin service menu installation failed"),  str};
+    QProcess::startDetached("kdialog", args);
 
     exit(1);
 }
@@ -49,6 +66,84 @@ QString getServiceMenusDir()
     return QDir(dataLocation).absoluteFilePath("kservices5/ServiceMenus");
 }
 
+#ifdef HAVE_PACKAGEKIT
+void packageKitInstall(const QString &fileName)
+{
+    PackageKit::Transaction *transaction = PackageKit::Daemon::installFile(fileName);
+
+    const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) {
+       fail(details);
+    };
+
+    QObject::connect(transaction, &PackageKit::Transaction::finished,
+                     [=](PackageKit::Transaction::Exit status, uint) {
+                        if (status == PackageKit::Transaction::ExitSuccess) {
+                            exit(0);
+                        }
+                        // Fallback error handling
+                        QTimer::singleShot(500, [=](){
+                            fail(i18n("Failed to install \"%1\", exited with status \"%2\"",
+                                      fileName, QVariant::fromValue(status).toString()));
+                        });
+                    });
+    QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError);
+}
+
+void packageKitUninstall(const QString &fileName)
+{
+    const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) {
+        fail(details);
+    };
+    const auto uninstallLambda = [=](PackageKit::Transaction::Exit status, uint) {
+        if (status == PackageKit::Transaction::ExitSuccess) {
+            exit(0);
+        }
+    };
+
+    PackageKit::Transaction *transaction = PackageKit::Daemon::getDetailsLocal(fileName);
+    QObject::connect(transaction, &PackageKit::Transaction::details,
+                     [=](const PackageKit::Details &details) {
+                         PackageKit::Transaction *transaction = PackageKit::Daemon::removePackage(details.packageId());
+                         QObject::connect(transaction, &PackageKit::Transaction::finished, uninstallLambda);
+                         QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError);
+                     });
+
+    QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError);
+    // Fallback error handling
+    QObject::connect(transaction, &PackageKit::Transaction::finished,
+        [=](PackageKit::Transaction::Exit status, uint) {
+            if (status != PackageKit::Transaction::ExitSuccess) {
+                QTimer::singleShot(500, [=]() {
+                    fail(i18n("Failed to uninstall \"%1\", exited with status \"%2\"",
+                              fileName, QVariant::fromValue(status).toString()));
+                });
+            }
+        });
+    }
+#endif
+
+Q_NORETURN void packageKit(PackageOperation operation, const QString &fileName)
+{
+#ifdef HAVE_PACKAGEKIT
+    QFileInfo fileInfo(fileName);
+    if (!fileInfo.exists()) {
+        fail(i18n("The file does not exist!"));
+    }
+    const QString absPath = fileInfo.absoluteFilePath();
+    if (operation == PackageOperation::Install) {
+        packageKitInstall(absPath);
+    } else {
+        packageKitUninstall(absPath);
+    }
+    QGuiApplication::exec(); // For event handling, no return after signals finish
+    fail(i18n("Unknown error when installing package"));
+#else
+    Q_UNUSED(operation)
+    QDesktopServices::openUrl(QUrl(fileName));
+    exit(0);
+#endif
+}
+
 struct UncompressCommand
 {
     QString command;
@@ -56,34 +151,39 @@ struct UncompressCommand
     QStringList args2;
 };
 
-void runUncompress(const QString &inputPath, const QString &outputPath) {
-    QList<QPair<QStringList, UncompressCommand>> mimeTypeToCommand;
-    mimeTypeToCommand.append({QStringList{"application/x-tar", "application/tar", "application/x-gtar",
-                                          "multipart/x-tar"},
-                              UncompressCommand{"tar", QStringList() << "-xf", QStringList() << "-C"}});
-    mimeTypeToCommand.append({QStringList{"application/x-gzip", "application/gzip",
-                                          "application/x-gzip-compressed-tar", "application/gzip-compressed-tar",
-                                          "application/x-gzip-compressed", "application/gzip-compressed",
-                                          "application/tgz", "application/x-compressed-tar",
-                                          "application/x-compressed-gtar", "file/tgz",
-                                          "multipart/x-tar-gz", "application/x-gunzip", "application/gzipped",
-                                          "gzip/document"},
-                              UncompressCommand{"tar", QStringList{"-zxf"}, QStringList{"-C"}}});
-    mimeTypeToCommand.append({QStringList{"application/bzip", "application/bzip2", "application/x-bzip",
-                                          "application/x-bzip2", "application/bzip-compressed",
-                                          "application/bzip2-compressed", "application/x-bzip-compressed",
-                                          "application/x-bzip2-compressed", "application/bzip-compressed-tar",
-                                          "application/bzip2-compressed-tar", "application/x-bzip-compressed-tar",
-                                          "application/x-bzip2-compressed-tar", "application/x-bz2"},
-                              UncompressCommand{"tar", QStringList{"-jxf"}, QStringList{"-C"}}});
-    mimeTypeToCommand.append({QStringList{"application/zip", "application/x-zip", "application/x-zip-compressed",
-                                          "multipart/x-zip"},
-                              UncompressCommand{"unzip", QStringList{}, QStringList{"-d"}}});
+enum ScriptExecution{
+    Process,
+    Konsole
+};
+
+void runUncompress(const QString &inputPath, const QString &outputPath)
+{
+    QVector<QPair<QStringList, UncompressCommand>> mimeTypeToCommand;
+    mimeTypeToCommand.append({{"application/x-tar", "application/tar", "application/x-gtar", "multipart/x-tar"},
+                              UncompressCommand({"tar", {"-xf"}, {"-C"}})});
+    mimeTypeToCommand.append({{"application/x-gzip", "application/gzip",
+                               "application/x-gzip-compressed-tar", "application/gzip-compressed-tar",
+                               "application/x-gzip-compressed", "application/gzip-compressed",
+                               "application/tgz", "application/x-compressed-tar",
+                               "application/x-compressed-gtar", "file/tgz",
+                               "multipart/x-tar-gz", "application/x-gunzip", "application/gzipped",
+                               "gzip/document"},
+                              UncompressCommand({"tar", {"-zxf"}, {"-C"}})});
+    mimeTypeToCommand.append({{"application/bzip", "application/bzip2", "application/x-bzip",
+                               "application/x-bzip2", "application/bzip-compressed",
+                               "application/bzip2-compressed", "application/x-bzip-compressed",
+                               "application/x-bzip2-compressed", "application/bzip-compressed-tar",
+                               "application/bzip2-compressed-tar", "application/x-bzip-compressed-tar",
+                               "application/x-bzip2-compressed-tar", "application/x-bz2"},
+                              UncompressCommand({"tar", {"-jxf"}, {"-C"}})});
+    mimeTypeToCommand.append({{"application/zip", "application/x-zip", "application/x-zip-compressed",
+                               "multipart/x-zip"},
+                              UncompressCommand({"unzip", {}, {"-d"}})});
 
     const auto mime = QMimeDatabase().mimeTypeForFile(inputPath).name();
 
     UncompressCommand command{};
-    for (const auto &pair : mimeTypeToCommand) {
+    for (const auto &pair : qAsConst(mimeTypeToCommand)) {
         if (pair.first.contains(mime)) {
             command = pair.second;
             break;
@@ -123,12 +223,24 @@ QString findRecursive(const QString &dir, const QString &basename)
     return QString();
 }
 
-bool runInstallerScriptOnce(const QString &path, const QStringList &args)
+bool runScriptOnce(const QString &path, const QStringList &args, ScriptExecution execution)
 {
     QProcess process;
     process.setWorkingDirectory(QFileInfo(path).absolutePath());
 
-    process.start(path, args, QIODevice::NotOpen);
+    const static bool konsoleAvailable = !QStandardPaths::findExecutable("konsole").isEmpty();
+    if (konsoleAvailable && execution == ScriptExecution::Konsole) {
+        QString bashCommand = KShell::quoteArg(path) + ' ';
+        if (!args.isEmpty()) {
+            bashCommand.append(args.join(' '));
+        }
+        bashCommand.append("|| $SHELL");
+        // If the install script fails a shell opens and the user can fix the problem
+        // without an error konsole closes
+        process.start("konsole", QStringList() << "-e" << "bash" << "-c" << bashCommand, QIODevice::NotOpen);
+    } else {
+        process.start(path, args, QIODevice::NotOpen);
+    }
     if (!process.waitForStarted()) {
         fail(i18n("Failed to run installer script %1", path));
     }
@@ -149,7 +261,7 @@ bool runInstallerScriptOnce(const QString &path, const QStringList &args)
 
 // If hasArgVariants is true, run "path".
 // If hasArgVariants is false, run "path argVariants[i]" until successful.
-bool runInstallerScript(const QString &path, bool hasArgVariants, const QStringList &argVariants, QString &errorText)
+bool runScriptVariants(const QString &path, bool hasArgVariants, const QStringList &argVariants, QString &errorText)
 {
     QFile file(path);
     if (!file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
@@ -160,14 +272,12 @@ bool runInstallerScript(const QString &path, bool hasArgVariants, const QStringL
     qInfo() << "[servicemenuinstaller]: Trying to run installer/uninstaller" << path;
     if (hasArgVariants) {
         for (const auto &arg : argVariants) {
-            if (runInstallerScriptOnce(path, QStringList{arg})) {
+            if (runScriptOnce(path, {arg}, ScriptExecution::Process)) {
                 return true;
             }
         }
-    } else {
-        if (runInstallerScriptOnce(path, QStringList{})) {
-            return true;
-        }
+    } else if (runScriptOnce(path, {}, ScriptExecution::Konsole)) {
+        return true;
     }
 
     errorText = i18nc(
@@ -201,6 +311,9 @@ bool cmdInstall(const QString &archive, QString &errorText)
             return false;
         }
     } else {
+        if (binaryPackages.contains(QMimeDatabase().mimeTypeForFile(archive).name())) {
+            packageKit(PackageOperation::Install, archive);
+        }
         const QString dir = generateDirPath(archive);
         if (QFile::exists(dir)) {
             if (!QDir(dir).removeRecursively()) {
@@ -217,8 +330,8 @@ bool cmdInstall(const QString &archive, QString &errorText)
 
         // Try "install-it" first
         QString installItPath;
-        const auto basenames1 = QStringList{"install-it.sh", "install-it"};
-        for (const auto &basename : qAsConst(basenames1)) {
+        const QStringList basenames1 = {"install-it.sh", "install-it"};
+        for (const auto &basename : basenames1) {
             const auto path = findRecursive(dir, basename);
             if (!path.isEmpty()) {
                 installItPath = path;
@@ -227,13 +340,13 @@ bool cmdInstall(const QString &archive, QString &errorText)
         }
 
         if (!installItPath.isEmpty()) {
-            return runInstallerScript(installItPath, false, QStringList{}, errorText);
+            return runScriptVariants(installItPath, false, QStringList{}, errorText);
         }
 
         // If "install-it" is missing, try "install"
         QString installerPath;
-        const auto basenames2 = QStringList{"installKDE4.sh", "installKDE4", "install.sh", "install"};
-        for (const auto &basename : qAsConst(basenames2)) {
+        const QStringList basenames2 = {"installKDE4.sh", "installKDE4", "install.sh", "install"};
+        for (const auto &basename : basenames2) {
             const auto path = findRecursive(dir, basename);
             if (!path.isEmpty()) {
                 installerPath = path;
@@ -242,7 +355,11 @@ bool cmdInstall(const QString &archive, QString &errorText)
         }
 
         if (!installerPath.isEmpty()) {
-            return runInstallerScript(installerPath, true, QStringList{"--local", "--local-install", "--install"}, errorText);
+            // Try to run script without variants first
+            if (!runScriptVariants(installerPath, false, {}, errorText)) {
+                return runScriptVariants(installerPath, true, {"--local", "--local-install", "--install"}, errorText);
+            }
+            return true;
         }
 
         fail(i18n("Failed to find an installation script in %1", dir));
@@ -263,12 +380,15 @@ bool cmdUninstall(const QString &archive, QString &errorText)
             return false;
         }
     } else {
+        if (binaryPackages.contains(QMimeDatabase().mimeTypeForFile(archive).name())) {
+            packageKit(PackageOperation::Uninstall, archive);
+        }
         const QString dir = generateDirPath(archive);
 
         // Try "deinstall" first
         QString deinstallPath;
-        const auto basenames1 = QStringList{"deinstall.sh", "deinstall"};
-        for (const auto &basename : qAsConst(basenames1)) {
+        const QStringList basenames1 = {"uninstall.sh", "uninstal", "deinstall.sh", "deinstall"};
+        for (const auto &basename : basenames1) {
             const auto path = findRecursive(dir, basename);
             if (!path.isEmpty()) {
                 deinstallPath = path;
@@ -277,17 +397,16 @@ bool cmdUninstall(const QString &archive, QString &errorText)
         }
 
         if (!deinstallPath.isEmpty()) {
-            bool ok = runInstallerScript(deinstallPath, false, QStringList{}, errorText);
+            const bool ok = runScriptVariants(deinstallPath, false, {}, errorText);
             if (!ok) {
                 return ok;
             }
         } else {
             // If "deinstall" is missing, try "install --uninstall"
-
             QString installerPath;
-            const auto basenames2 = QStringList{"install-it.sh", "install-it", "installKDE4.sh",
-                                                "installKDE4", "install.sh", "install"};
-            for (const auto &basename : qAsConst(basenames2)) {
+            const QStringList basenames2 = {"install-it.sh", "install-it", "installKDE4.sh",
+                                            "installKDE4", "install.sh", "install"};
+            for (const auto &basename : basenames2) {
                 const auto path = findRecursive(dir, basename);
                 if (!path.isEmpty()) {
                     installerPath = path;
@@ -296,8 +415,8 @@ bool cmdUninstall(const QString &archive, QString &errorText)
             }
 
             if (!installerPath.isEmpty()) {
-                bool ok = runInstallerScript(
-                    installerPath, true, QStringList{"--remove", "--delete", "--uninstall", "--deinstall"}, errorText);
+                const bool ok = runScriptVariants(installerPath, true,
+                                                  {"--remove", "--delete", "--uninstall", "--deinstall"}, errorText);
                 if (!ok) {
                     return ok;
                 }
@@ -318,7 +437,7 @@ bool cmdUninstall(const QString &archive, QString &errorText)
 
 int main(int argc, char *argv[])
 {
-    QCoreApplication app(argc, argv);
+    QGuiApplication app(argc, argv);
 
     QCommandLineParser parser;
     parser.addPositionalArgument(QStringLiteral("command"), i18nc("@info:shell", "Command to execute: install or uninstall."));
@@ -337,11 +456,11 @@ int main(int argc, char *argv[])
     const QString archive = args[1];
 
     QString errorText;
-    if (cmd == "install") {
+    if (cmd == QLatin1String("install")) {
         if (!cmdInstall(archive, errorText)) {
             fail(errorText);
         }
-    } else if (cmd == "uninstall") {
+    } else if (cmd == QLatin1String("uninstall")) {
         if (!cmdUninstall(archive, errorText)) {
             fail(errorText);
         }