]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Rewrite servicemenu helper utility in C++
authorAlexander Potashev <aspotashev@gmail.com>
Sun, 14 Jul 2019 21:14:51 +0000 (00:14 +0300)
committerAlexander Potashev <aspotashev@gmail.com>
Sun, 14 Jul 2019 22:39:15 +0000 (01:39 +0300)
Summary:
     - Also support MIME type "application/x-compressed-tar".
     - Update tests in Ruby, remove SimpleCov.

BUG: 399229

Test Plan: Ruby tests passed

Reviewers: sitter, elvisangelaccio, ngraham

Reviewed By: elvisangelaccio

Subscribers: cfeck, kfm-devel

Tags: #dolphin

Differential Revision: https://phabricator.kde.org/D21878

src/CMakeLists.txt
src/settings/services/servicemenu.knsrc
src/settings/services/servicemenudeinstallation [deleted file]
src/settings/services/servicemenuinstallation [deleted file]
src/settings/services/servicemenuinstaller/CMakeLists.txt [new file with mode: 0644]
src/settings/services/servicemenuinstaller/Messages.sh [new file with mode: 0755]
src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp [new file with mode: 0644]
src/settings/services/test/service_menu_deinstallation_test.rb
src/settings/services/test/service_menu_installation_test.rb
src/settings/services/test/test_helper.rb
src/tests/CMakeLists.txt

index 8a025e584369bbcc12b24e00414bc16ebf56f92c..8bbb081cdddbc93e60e5dfe4f6ee35dc9956e128 100644 (file)
@@ -382,6 +382,8 @@ install(TARGETS kcm_dolphinnavigation DESTINATION ${KDE_INSTALL_PLUGINDIR} )
 install(TARGETS kcm_dolphinservices DESTINATION ${KDE_INSTALL_PLUGINDIR} )
 install(TARGETS kcm_dolphingeneral DESTINATION ${KDE_INSTALL_PLUGINDIR} )
 
+add_subdirectory(settings/services/servicemenuinstaller)
+
 ########### install files ###############
 
 install( PROGRAMS org.kde.dolphin.desktop DESTINATION ${KDE_INSTALL_APPDIR} )
@@ -405,8 +407,6 @@ install( FILES settings/kcm/kcmdolphinnavigation.desktop DESTINATION ${KDE_INSTA
 install( FILES settings/kcm/kcmdolphinservices.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
 install( FILES settings/kcm/kcmdolphingeneral.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
 install( FILES settings/services/servicemenu.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} )
-install( PROGRAMS settings/services/servicemenuinstallation DESTINATION ${KDE_INSTALL_BINDIR} )
-install( PROGRAMS settings/services/servicemenudeinstallation DESTINATION ${KDE_INSTALL_BINDIR} )
 
 if(BUILD_TESTING)
     find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED)
index 021a975437ffe8f2f123965aa150707b35d8062b..0d1c103f67c0a172f98787060f77a76ac964e932 100644 (file)
@@ -5,5 +5,5 @@ ChecksumPolicy=ifpossible
 SignaturePolicy=ifpossible
 TargetDir=servicemenu-download
 Uncompress=never
-InstallationCommand=servicemenuinstallation %f
-UninstallCommand=servicemenudeinstallation %f
+InstallationCommand=servicemenuinstaller install %f
+UninstallCommand=servicemenuinstaller uninstall %f
diff --git a/src/settings/services/servicemenudeinstallation b/src/settings/services/servicemenudeinstallation
deleted file mode 100755 (executable)
index 9e090e2..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env ruby
-
-# Copyright (C) 2009 Jonathan Schmidt-Dominé <devel@the-user.org>
-# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the
-# Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-require 'fileutils'
-
-ARCHIVE = ARGV[0]
-
-# @param log_msg [String] error that gets logged to CLI
-def fail(log_msg: nil)
-  # FIXME: this is not translated...
-  msg = 'Dolphin service menu installation failed'
-  warn log_msg if log_msg
-  system('kdialog', '--passivepopup', msg, '15')
-  abort
-end
-
-if ARCHIVE.end_with?('.desktop')
-  data_location = `qtpaths --writable-path GenericDataLocation`.strip
-  unless $?.success?
-    fail(log_msg: "Could not get GenericDataLocation #{data_location}")
-  end
-  FileUtils.rm("#{data_location}/kservices5/ServiceMenus/#{File.basename(ARCHIVE)}")
-  exit(0)
-end
-dir = "#{ARCHIVE}-dir"
-
-deinstaller = nil
-%w[deinstall.sh deinstall].find do |script|
-  deinstaller = Dir.glob("#{dir}/**/#{script}")[0]
-end
-
-installer = nil
-%w[install-it.sh install-it installKDE4.sh installKDE4 install.sh install].find do |script|
-  installer = Dir.glob("#{dir}/**/#{script}")[0]
-end
-
-Dir.chdir(dir) do
-  deinstalled = false
-
-  [deinstaller, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
-
-  if deinstaller
-    puts "[servicemenudeinstallation]: Trying to run deinstaller #{deinstaller}"
-    deinstalled = system(deinstaller)
-  elsif installer
-    puts "[servicemenudeinstallation]: Trying to run installer #{installer}"
-    %w[--remove --delete --uninstall --deinstall].any? do |arg|
-      deinstalled = system(installer, arg)
-    end
-  end
-
-  fail unless deinstalled
-end
-
-FileUtils.rm_r(dir)
diff --git a/src/settings/services/servicemenuinstallation b/src/settings/services/servicemenuinstallation
deleted file mode 100755 (executable)
index e2d42bf..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env ruby
-
-# Copyright (C) 2009 Jonathan Schmidt-Dominé <devel@the-user.org>
-# Copyright (C) 2019 Harald Sitter <sitter@kde.org>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the
-# Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-require 'fileutils'
-
-ARCHIVE_UNCOMPRESSORS = {
-  'application/x-tar' => :"tar -xf %s -C %s",
-  'application/tar' => :"tar -xf %s -C %s",
-  'application/x-gzip' => :"tar -zxf %s -C %s",
-  'application/gzip' => :"tar -zxf %s -C %s",
-  'application/x-gzip-compressed-tar' => :"tar -zxf %s -C %s",
-  'application/gzip-compressed-tar' => :"tar -zxf %s -C %s",
-  'application/x-gzip-compressed' => :"tar -zxf %s -C %s",
-  'application/gzip-compressed' => :"tar -zxf %s -C %s",
-  'application/bzip' => :"tar -jxf %s -C %s",
-  'application/bzip2' => :"tar -jxf %s -C %s",
-  'application/x-bzip' => :"tar -jxf %s -C %s",
-  'application/x-bzip2' => :"tar -jxf %s -C %s",
-  'application/bzip-compressed' => :"tar -jxf %s -C %s",
-  'application/bzip2-compressed' => :"tar -jxf %s -C %s",
-  'application/x-bzip-compressed' => :"tar -jxf %s -C %s",
-  'application/x-bzip2-compressed' => :"tar -jxf %s -C %s",
-  'application/bzip-compressed-tar' => :"tar -jxf %s -C %s",
-  'application/bzip2-compressed-tar' => :"tar -jxf %s -C %s",
-  'application/x-bzip-compressed-tar' => :"tar -jxf %s -C %s",
-  'application/x-bzip2-compressed-tar' => :"tar -jxf %s -C %s",
-  'application/zip' => :"unzip %s -d %s",
-  'application/x-zip' => :"unzip %s -d %s",
-  'application/x-zip-compressed' => :"unzip %s -d %s",
-  'multipart/x-zip' => :"unzip %s -d %s",
-  'application/tgz' => :"tar -zxf %s -C %s",
-  'application/x-compressed-gtar' => :"tar -zxf %s -C %s",
-  'file/tgz' => :"tar -zxf %s -C %s",
-  'multipart/x-tar-gz' => :"tar -zxf %s -C %s",
-  'application/x-gunzip' => :"tar -zxf %s -C %s",
-  'application/gzipped' => :"tar -zxf %s -C %s",
-  'gzip/document' => :"tar -zxf %s -C %s",
-  'application/x-bz2 ' => :"tar -jxf %s -C %s",
-  'application/x-gtar' => :"tar -xf %s -C %s",
-  'multipart/x-tar' => :"tar -xf %s -C %s"
-}
-
-ARCHIVE = ARGV[0]
-
-# @param log_msg [String] error that gets logged to CLI
-def fail(log_msg: nil)
-  # FIXME: this is not translated...
-  msg = 'Dolphin service menu installation failed'
-  warn log_msg if log_msg
-  system('kdialog', '--passivepopup', msg, '15')
-  abort
-end
-
-def mime_type(filename)
-  ret = `xdg-mime query filetype #{filename}`.strip
-  return ret if $?.success?
-
-  warn 'Failed to xdg-mime'
-  fail(log_msg: "Failed to xdg-mime #{filename}: #{ret}")
-end
-
-def uncompress(filename, output)
-  uncompressor = ARCHIVE_UNCOMPRESSORS.fetch(mime_type(filename)).to_s
-  system(format(uncompressor, filename, output))
-rescue KeyError => e
-  # If a mimetype doesn't have an uncompressor mapped we'll get a keyerror.
-  # we'll log the error but visually report the failure.
-  fail(log_msg: "Unmapped compression format #{filename}; #{e.message}")
-end
-
-data_location = `qtpaths --writable-path GenericDataLocation`.strip
-unless $?.success?
-  fail(log_msg: "Could not get GenericDataLocation #{data_location}")
-end
-servicedir = "#{data_location}/kservices5/ServiceMenus/"
-
-FileUtils.mkdir_p(servicedir) unless File.exist?(servicedir)
-if ARCHIVE.end_with?('.desktop')
-  puts 'Single-File Service-Menu'
-  puts ARCHIVE
-  puts servicedir
-  FileUtils.cp(ARCHIVE, servicedir)
-  exit
-end
-
-dir = "#{ARCHIVE}-dir"
-
-FileUtils.rm_r(dir) if File.exist?(dir)
-FileUtils.mkdir(dir)
-
-fail(log_msg: 'uncompress failed') unless uncompress(ARCHIVE, dir)
-
-install_it = nil
-%w[install-it.sh install-it].find do |script|
-  install_it = Dir.glob("#{dir}/**/#{script}")[0]
-end
-
-installer = nil
-%w[installKDE4.sh installKDE4 install.sh install].find do |script|
-  installer = Dir.glob("#{dir}/**/#{script}")[0]
-end
-
-Dir.chdir(dir) do
-  installed = false
-
-  [install_it, installer].uniq.compact.each { |f| File.chmod(0o700, f) }
-
-  if install_it
-    puts "[servicemenuinstallation]: Trying to run install_it #{install_it}"
-    installed = system(install_it)
-  elsif installer
-    puts "[servicemenuinstallation]: Trying to run installer #{installer}"
-    %w[--local --local-install --install].any? do |arg|
-      installed = system(installer, arg)
-    end
-  end
-
-  fail unless installed
-end
diff --git a/src/settings/services/servicemenuinstaller/CMakeLists.txt b/src/settings/services/servicemenuinstaller/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b5591ca
--- /dev/null
@@ -0,0 +1,9 @@
+remove_definitions(-DTRANSLATION_DOMAIN=\"dolphin\")
+add_definitions(-DTRANSLATION_DOMAIN=\"dolphin_servicemenuinstaller\")
+
+add_executable(servicemenuinstaller servicemenuinstaller.cpp)
+target_link_libraries(servicemenuinstaller PRIVATE
+    Qt5::Core
+    KF5::I18n
+)
+install(TARGETS servicemenuinstaller ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/src/settings/services/servicemenuinstaller/Messages.sh b/src/settings/services/servicemenuinstaller/Messages.sh
new file mode 100755 (executable)
index 0000000..5012eea
--- /dev/null
@@ -0,0 +1,2 @@
+#! /usr/bin/env bash
+$XGETTEXT `find . -name \*.cpp` -o $podir/dolphin_servicemenuinstaller.pot
diff --git a/src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp b/src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp
new file mode 100644 (file)
index 0000000..9c614a8
--- /dev/null
@@ -0,0 +1,387 @@
+/***************************************************************************
+ *   Copyright © 2019 Alexander Potashev <aspotashev@gmail.com>            *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+#include <QDebug>
+#include <QProcess>
+#include <QStandardPaths>
+#include <QDir>
+#include <QDirIterator>
+#include <QCommandLineParser>
+
+#include <KLocalizedString>
+
+// @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");
+    }
+
+    exit(1);
+}
+
+bool evaluateShell(const QString &program, const QStringList &arguments, QString &output, QString &errorText)
+{
+    QProcess process;
+    process.start(program, arguments, QIODevice::ReadOnly);
+    if (!process.waitForStarted()) {
+        fail(i18n("Failed to run process: %1 %2", program, arguments.join(" ")));
+    }
+
+    if (!process.waitForFinished()) {
+        fail(i18n("Process did not finish in reasonable time: %1 %2", program, arguments.join(" ")));
+    }
+
+    const auto stdoutResult = QString::fromUtf8(process.readAllStandardOutput()).trimmed();
+    const auto stderrResult = QString::fromUtf8(process.readAllStandardError()).trimmed();
+
+    if (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0) {
+        output = stdoutResult;
+        return true;
+    } else {
+        errorText = stderrResult + stdoutResult;
+        return false;
+    }
+}
+
+QString mimeType(const QString &path)
+{
+    QString result;
+    QString errorText;
+    if (evaluateShell("xdg-mime", QStringList{"query", "filetype", path}, result, errorText)) {
+        return result;
+    } else {
+        fail(i18n("Failed to run xdg-mime %1: %2", path, errorText));
+    }
+}
+
+QString getServiceMenusDir()
+{
+    const QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
+    return QDir(dataLocation).absoluteFilePath("kservices5/ServiceMenus");
+}
+
+struct UncompressCommand
+{
+    QString command;
+    QStringList args1;
+    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"}}});
+
+    const auto mime = mimeType(inputPath);
+
+    UncompressCommand command{};
+    for (const auto &pair : mimeTypeToCommand) {
+        if (pair.first.contains(mime)) {
+            command = pair.second;
+            break;
+        }
+    }
+
+    if (command.command.isEmpty()) {
+        fail(i18n("Unsupported archive type %1: %2", mime, inputPath));
+    }
+
+    QProcess process;
+    process.start(
+        command.command,
+        QStringList() << command.args1 << inputPath << command.args2 << outputPath,
+        QIODevice::NotOpen);
+    if (!process.waitForStarted()) {
+        fail(i18n("Failed to run uncompressor command for %1", inputPath));
+    }
+
+    if (!process.waitForFinished()) {
+        fail(
+            i18n("Process did not finish in reasonable time: %1 %2", process.program(), process.arguments().join(" ")));
+    }
+
+    if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
+        fail(i18n("Failed to uncompress %1", inputPath));
+    }
+}
+
+QString findRecursive(const QString &dir, const QString &basename)
+{
+    QDirIterator it(dir, QStringList{basename}, QDir::Files, QDirIterator::Subdirectories);
+    while (it.hasNext()) {
+        return QFileInfo(it.next()).canonicalFilePath();
+    }
+
+    return QString();
+}
+
+bool runInstallerScriptOnce(const QString &path, const QStringList &args, const QString &dir)
+{
+    QProcess process;
+    process.setWorkingDirectory(dir);
+    process.start(path, args, QIODevice::NotOpen);
+    if (!process.waitForStarted()) {
+        fail(i18n("Failed to run installer script %1", path));
+    }
+
+    // Wait until installer exits, without timeout
+    if (!process.waitForFinished(-1)) {
+        qWarning() << "Failed to wait on installer:" << process.program() << process.arguments().join(" ");
+        return false;
+    }
+
+    if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
+        qWarning() << "Installer script exited with error:" << process.program() << process.arguments().join(" ");
+        return false;
+    }
+
+    return true;
+}
+
+// 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, const QString &dir,
+    QString &errorText)
+{
+    QFile file(path);
+    if (!file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
+        errorText = i18n("Failed to set permissions on %1: %2", path, file.errorString());
+        return false;
+    }
+
+    qInfo() << "[servicemenuinstaller]: Trying to run installer/uninstaller" << path;
+    if (hasArgVariants) {
+        for (const auto &arg : argVariants) {
+            if (runInstallerScriptOnce(path, QStringList{arg}, dir)) {
+                return true;
+            }
+        }
+    } else {
+        if (runInstallerScriptOnce(path, QStringList{}, dir)) {
+            return true;
+        }
+    }
+
+    errorText = i18nc(
+        "%1 = comma separated list of arguments",
+        "Installer script %1 failed, tried arguments \"%1\".", path, argVariants.join(i18nc("Separator between arguments", "\", \"")));
+    return false;
+}
+
+QString generateDirPath(const QString &archive)
+{
+    return QStringLiteral("%1-dir").arg(archive);
+}
+
+bool cmdInstall(const QString &archive, QString &errorText)
+{
+    const auto serviceDir = getServiceMenusDir();
+    if (!QDir().mkpath(serviceDir)) {
+        // TODO Cannot get error string because of this bug: https://bugreports.qt.io/browse/QTBUG-1483
+        errorText = i18n("Failed to create path %1", serviceDir);
+        return false;
+    }
+
+    if (archive.endsWith(".desktop")) {
+        // Append basename to destination directory
+        const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName());
+        qInfo() << "Single-File Service-Menu" << archive << dest;
+
+        QFile source(archive);
+        if (!source.copy(dest)) {
+            errorText = i18n("Failed to copy .desktop file %1 to %2: %3", archive, dest, source.errorString());
+            return false;
+        }
+    } else {
+        const QString dir = generateDirPath(archive);
+        if (QFile::exists(dir)) {
+            if (!QDir(dir).removeRecursively()) {
+                errorText = i18n("Failed to remove directory %1", dir);
+                return false;
+            }
+        }
+
+        if (QDir().mkdir(dir)) {
+            errorText = i18n("Failed to create directory %1", dir);
+        }
+
+        runUncompress(archive, dir);
+
+        // Try "install-it" first
+        QString installItPath;
+        const auto basenames1 = QStringList{"install-it.sh", "install-it"};
+        for (const auto &basename : qAsConst(basenames1)) {
+            const auto path = findRecursive(dir, basename);
+            if (!path.isEmpty()) {
+                installItPath = path;
+                break;
+            }
+        }
+
+        if (!installItPath.isEmpty()) {
+            return runInstallerScript(installItPath, false, QStringList{}, dir, 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 auto path = findRecursive(dir, basename);
+            if (!path.isEmpty()) {
+                installerPath = path;
+                break;
+            }
+        }
+
+        if (!installerPath.isEmpty()) {
+            return runInstallerScript(installerPath, true, QStringList{"--local", "--local-install", "--install"}, dir, errorText);
+        }
+
+        fail(i18n("Failed to find an installation script in %1", dir));
+    }
+
+    return true;
+}
+
+bool cmdUninstall(const QString &archive, QString &errorText)
+{
+    const auto serviceDir = getServiceMenusDir();
+    if (archive.endsWith(".desktop")) {
+        // Append basename to destination directory
+        const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName());
+        QFile file(dest);
+        if (!file.remove()) {
+            errorText = i18n("Failed to remove .desktop file %1: %2", dest, file.errorString());
+            return false;
+        }
+    } else {
+        const QString dir = generateDirPath(archive);
+
+        // Try "deinstall" first
+        QString deinstallPath;
+        const auto basenames1 = QStringList{"deinstall.sh", "deinstall"};
+        for (const auto &basename : qAsConst(basenames1)) {
+            const auto path = findRecursive(dir, basename);
+            if (!path.isEmpty()) {
+                deinstallPath = path;
+                break;
+            }
+        }
+
+        if (!deinstallPath.isEmpty()) {
+            bool ok = runInstallerScript(deinstallPath, false, QStringList{}, dir, 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 auto path = findRecursive(dir, basename);
+                if (!path.isEmpty()) {
+                    installerPath = path;
+                    break;
+                }
+            }
+
+            if (!installerPath.isEmpty()) {
+                bool ok = runInstallerScript(
+                    installerPath, true, QStringList{"--remove", "--delete", "--uninstall", "--deinstall"}, dir, errorText);
+                if (!ok) {
+                    return ok;
+                }
+            } else {
+                fail(i18n("Failed to find an uninstallation script in %1", dir));
+            }
+        }
+
+        QDir dirObject(dir);
+        if (!dirObject.removeRecursively()) {
+            errorText = i18n("Failed to remove directory %1", dir);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int main(int argc, char *argv[])
+{
+    QCoreApplication app(argc, argv);
+
+    QCommandLineParser parser;
+    parser.addPositionalArgument(QStringLiteral("command"), i18nc("@info:shell", "Command to execute: install or uninstall."));
+    parser.addPositionalArgument(QStringLiteral("path"), i18nc("@info:shell", "Path to archive."));
+    parser.process(app);
+
+    const QStringList args = parser.positionalArguments();
+    if (args.isEmpty()) {
+        fail(i18n("Command is required."));
+    }
+    if (args.size() == 1) {
+        fail(i18n("Path to archive is required."));
+    }
+
+    const QString cmd = args[0];
+    const QString archive = args[1];
+
+    QString errorText;
+    if (cmd == "install") {
+        if (!cmdInstall(archive, errorText)) {
+            fail(errorText);
+        }
+    } else if (cmd == "uninstall") {
+        if (!cmdUninstall(archive, errorText)) {
+            fail(errorText);
+        }
+    } else {
+        fail(i18n("Unsupported command %1", cmd));
+    }
+
+    return 0;
+}
index 2b3514a65157cb442278d921a182dc624d98839a..1c9856d948b5d47fbcbbf0e963bf3a272a0c24f6 100644 (file)
@@ -51,7 +51,7 @@ touch #{@tmpdir}/deinstall.sh-run
 touch #{@tmpdir}/install.sh-run
     INSTALL_SH
 
-    assert(covered_system('servicemenudeinstallation', archive_base))
+    assert(system('servicemenuinstaller', 'uninstall', archive_base))
 
     # deinstaller should be run
     # installer should not be run
@@ -77,7 +77,7 @@ fi
 exit 1
     INSTALL_SH
 
-    assert(covered_system('servicemenudeinstallation', archive_base))
+    assert(system('servicemenuinstaller', 'uninstall', archive_base))
 
     assert_path_not_exist('deinstall.sh-run')
     assert_path_exist('install.sh-run')
@@ -91,7 +91,7 @@ exit 1
     archive_dir = "#{archive_base}-dir/foo-1.1/"
     FileUtils.mkpath(archive_dir)
 
-    refute(covered_system('servicemenudeinstallation', archive_base))
+    refute(system('servicemenuinstaller', 'uninstall', archive_base))
 
     # I am unsure if deinstallation really should keep the files around. But
     # that's how it behaved originally so it's supposedly intentional
@@ -113,7 +113,7 @@ exit 1
     FileUtils.mkpath(menu_dir)
     FileUtils.touch(installed_file)
 
-    assert(covered_system('servicemenudeinstallation', downloaded_file))
+    assert(system('servicemenuinstaller', 'uninstall', downloaded_file))
 
     assert_path_exist(downloaded_file)
     assert_path_not_exist(installed_file)
index 43f594969c2adb7f8fbf63cfabf6a5d287722074..01bf65b236640779ed9ab01937534273607193c3 100644 (file)
@@ -54,7 +54,7 @@ touch #{@tmpdir}/install.sh-run
     INSTALL_SH
     assert(system('tar', '-cf', archive, archive_dir))
 
-    assert(covered_system('servicemenuinstallation', archive))
+    assert(system('servicemenuinstaller', 'install', archive))
 
     tar_dir = "#{service_dir}/foo.tar-dir"
     tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
@@ -81,7 +81,7 @@ exit 1
     INSTALL_SH
     assert(system('tar', '-cf', archive, archive_dir))
 
-    assert(covered_system('servicemenuinstallation', archive))
+    assert(system('servicemenuinstaller', 'install', archive))
 
     tar_dir = "#{service_dir}/foo.tar-dir"
     tar_extract_dir = "#{service_dir}/foo.tar-dir/foo"
@@ -100,7 +100,7 @@ exit 1
     FileUtils.mkpath(archive_dir)
     assert(system('tar', '-cf', archive, archive_dir))
 
-    refute(covered_system('servicemenuinstallation', archive))
+    refute(system('servicemenuinstaller', 'install', archive))
   end
 
   def test_run_desktop
@@ -111,7 +111,7 @@ exit 1
 
     installed_file = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/foo.desktop"
 
-    assert(covered_system('servicemenuinstallation', downloaded_file))
+    assert(system('servicemenuinstaller', 'install', downloaded_file))
 
     assert_path_exist(downloaded_file)
     assert_path_exist(installed_file)
index 9da5cf3c31e521290c15346c20762ef5fdb99ba3..35b5d19ebb85c0331e1274229eb3f46d56597917 100644 (file)
 # Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-GLOBAL_COVERAGE_ROOT = File.dirname(__dir__) # ../
-
-# Simplecov is a bit meh and expects src and coverage to be under the
-# same root. Since we get run through cmake that assumption absolutely
-# doesn't hold true, so we'll need to figure out the coverage_dir relative
-# to the root and the root must always be the source :/
-# The relativity only works because internally the path gets expanded, this
-# isn't fully reliable, but oh well...
-# https://github.com/colszowka/simplecov/issues/716
-GLOBAL_COVERAGE_DIR = begin
-  require 'pathname'
-  src_path = Pathname.new(GLOBAL_COVERAGE_ROOT)
-  coverage_path = Pathname.new(File.join(Dir.pwd, 'coverage'))
-  coverage_path.relative_path_from(src_path).to_s
-end
-
-begin
-  require 'simplecov'
-
-  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
-    [
-      SimpleCov::Formatter::HTMLFormatter
-    ]
-  )
-
-  SimpleCov.start do
-    root GLOBAL_COVERAGE_ROOT
-    coverage_dir GLOBAL_COVERAGE_DIR
-  end
-rescue LoadError
-  warn 'SimpleCov not loaded'
-end
-
-# FIXME: add coverage report for jenkins?
-
 $LOAD_PATH.unshift(File.absolute_path('../', __dir__)) # ../
 
 def __test_method_name__
@@ -63,38 +28,4 @@ def __test_method_name__
   caller
 end
 
-# system() variant which sets up merge-coverage. simplecov supports merging
-# of multiple coverage sets. we use this to get coverage metrics on the
-# binaries without having to refactor the script into runnable classes.
-def covered_system(cmd, *argv)
-  pid = fork do
-    Kernel.module_exec do
-      alias_method(:real_system, :system)
-      define_method(:system) do |*args|
-        return true if args.include?('kdialog') # disable kdialog call
-        real_system(*args)
-      end
-    end
-
-    begin
-      require 'simplecov'
-      SimpleCov.start do
-        root GLOBAL_COVERAGE_ROOT
-        coverage_dir GLOBAL_COVERAGE_DIR
-        command_name "#{cmd}_#{__test_method_name__}"
-      end
-    rescue LoadError
-      warn 'SimpleCov not loaded'
-    end
-
-    ARGV.replace(argv)
-    load "#{__dir__}/../#{cmd}"
-    puts 'all good, fork ending!'
-    exit 0
-  end
-  waitedpid, status = Process.waitpid2(pid)
-  assert_equal(pid, waitedpid)
-  status.success? # behave like system and return the success only
-end
-
 require 'test/unit'
index f60eabd3fc01d396c20dc2c422d2dd20cffcfa0c..a0c9bbf38b72951bc36b90bc3b5e0c15f136b5e4 100644 (file)
@@ -9,12 +9,6 @@ find_gem(test-unit REQUIRED)
 set_package_properties(Gem:test-unit PROPERTIES
     DESCRIPTION "Ruby gem 'test-unit' required for testing of servicemenu helpers.")
 
-if(BUILD_COVERAGE)
-    find_gem(simplecov)
-    set_package_properties(Gem:simplecov PROPERTIES
-        DESCRIPTION "Ruby gem 'simplecov' used for coverage statistics.")
-endif()
-
 # KItemSetTest
 ecm_add_test(kitemsettest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)