#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);
}
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;
QStringList args2;
};
-void runUncompress(const QString &inputPath, const QString &outputPath) {
+enum ScriptExecution{
+ Process,
+ Konsole
+};
+
+void runUncompress(const QString &inputPath, const QString &outputPath)
+{
QVector<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"}}});
+ 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;
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));
}
// 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)) {
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(
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()) {
// 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;
}
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;
}
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));
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;
}
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;
}
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;
}
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."));
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);
}