1
0
mirror of https://invent.kde.org/system/dolphin synced 2024-07-02 16:31:23 +00:00

Make DolphinPackageInstaller a KJob

This means developers can use a familiar pattern here.
This commit is contained in:
Felix Ernst 2024-06-06 19:05:22 +02:00 committed by Felix Ernst
parent d21984ba5b
commit fb651f0cbc
5 changed files with 143 additions and 111 deletions

View File

@ -274,7 +274,7 @@ target_sources(dolphinstatic PRIVATE
dolphincontextmenu.cpp dolphincontextmenu.cpp
dolphinnavigatorswidgetaction.cpp dolphinnavigatorswidgetaction.cpp
dolphintabbar.cpp dolphintabbar.cpp
dolphinpackagemanager.cpp dolphinpackageinstaller.cpp
dolphinplacesmodelsingleton.cpp dolphinplacesmodelsingleton.cpp
dolphinrecenttabsmenu.cpp dolphinrecenttabsmenu.cpp
dolphintabpage.cpp dolphintabpage.cpp
@ -335,7 +335,7 @@ target_sources(dolphinstatic PRIVATE
dolphincontextmenu.h dolphincontextmenu.h
dolphinnavigatorswidgetaction.h dolphinnavigatorswidgetaction.h
dolphintabbar.h dolphintabbar.h
dolphinpackagemanager.h dolphinpackageinstaller.h
dolphinplacesmodelsingleton.h dolphinplacesmodelsingleton.h
dolphinrecenttabsmenu.h dolphinrecenttabsmenu.h
dolphintabpage.h dolphintabpage.h

View File

@ -5,7 +5,7 @@
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/ */
#include "dolphinpackagemanager.h" #include "dolphinpackageinstaller.h"
#include <KLocalizedString> #include <KLocalizedString>
@ -18,51 +18,54 @@
#include <QTimer> #include <QTimer>
#include <QtAssert> #include <QtAssert>
DolphinPackageManager::DolphinPackageManager(QObject *parent) DolphinPackageInstaller::DolphinPackageInstaller(const QString &packageName,
: QObject(parent) const QUrl &fallBackInstallationPageUrl,
std::function<bool()> isPackageInstalledCheck,
QObject *parent)
: KJob(parent)
, m_packageName{packageName}
, m_fallBackInstallationPageUrl{fallBackInstallationPageUrl}
, m_isPackageInstalledCheck{isPackageInstalledCheck}
{ {
} }
void DolphinPackageManager::install(const QString &packageName, const QUrl &fallBackInstallationPageUrl, std::function<bool()> isPackageInstalledCheck) void DolphinPackageInstaller::start()
{ {
Q_ASSERT_X(m_packageName.isEmpty(), "install", "Reusing a DolphinPackageManager object has not been implemented and can lead to conflicts."); if (m_isPackageInstalledCheck()) {
if (isPackageInstalledCheck()) { emitResult();
Q_EMIT success();
return; return;
} }
m_packageName = packageName;
#if HAVE_PACKAGEKIT #if HAVE_PACKAGEKIT
Q_UNUSED(fallBackInstallationPageUrl)
PackageKit::Daemon::setHints(PackageKit::Daemon::hints() + QStringList{QStringLiteral("interactive=true")}); PackageKit::Daemon::setHints(PackageKit::Daemon::hints() + QStringList{QStringLiteral("interactive=true")});
const PackageKit::Transaction *resolveTransaction = PackageKit::Daemon::resolve(packageName); const PackageKit::Transaction *resolveTransaction = PackageKit::Daemon::resolve(m_packageName);
connect(resolveTransaction, &PackageKit::Transaction::errorCode, this, &DolphinPackageManager::slotInstallationFailed); connect(resolveTransaction, &PackageKit::Transaction::errorCode, this, &DolphinPackageInstaller::slotInstallationFailed);
connect(resolveTransaction, &PackageKit::Transaction::finished, this, [this, packageName]() { // Will be disconnected if we find a package. connect(resolveTransaction, &PackageKit::Transaction::finished, this, [this]() { // Will be disconnected if we find a package.
slotInstallationFailed(PackageKit::Transaction::ErrorPackageNotFound, slotInstallationFailed(PackageKit::Transaction::ErrorPackageNotFound,
i18nc("@info:shell about system packages", "Could not find package %1.", packageName)); i18nc("@info:shell about system packages", "Could not find package %1.", m_packageName));
}); });
connect(resolveTransaction, connect(resolveTransaction,
&PackageKit::Transaction::package, &PackageKit::Transaction::package,
this, this,
[this, resolveTransaction](PackageKit::Transaction::Info /* info */, const QString &packageId) { [this, resolveTransaction](PackageKit::Transaction::Info /* info */, const QString &packageId) {
disconnect(resolveTransaction, nullptr, this, nullptr); // We only care about the first package. disconnect(resolveTransaction, nullptr, this, nullptr); // We only care about the first package.
PackageKit::Transaction *installTransaction = PackageKit::Daemon::installPackage(packageId); const PackageKit::Transaction *installTransaction = PackageKit::Daemon::installPackage(packageId);
connectTransactionToJobProgress(*installTransaction);
connect(installTransaction, connect(installTransaction,
&PackageKit::Transaction::errorCode, &PackageKit::Transaction::errorCode,
this, this,
[installTransaction, this](PackageKit::Transaction::Error error, const QString &details) { [installTransaction, this](PackageKit::Transaction::Error error, const QString &details) {
disconnect(installTransaction, nullptr, this, nullptr); // We only want to emit failure() or success() once. disconnect(installTransaction, nullptr, this, nullptr); // We only want to emit a result once.
slotInstallationFailed(error, details); slotInstallationFailed(error, details);
}); });
connect(installTransaction, connect(installTransaction,
&PackageKit::Transaction::finished, &PackageKit::Transaction::finished,
this, this,
[installTransaction, this](const PackageKit::Transaction::Exit status, uint /* runtime */) { [installTransaction, this](const PackageKit::Transaction::Exit status, uint /* runtime */) {
disconnect(installTransaction, nullptr, this, nullptr); // We only want to emit failure() or success() once. disconnect(installTransaction, nullptr, this, nullptr); // We only want to emit a result once.
if (status == PackageKit::Transaction::ExitSuccess) { if (status == PackageKit::Transaction::ExitSuccess) {
Q_EMIT success(); emitResult();
deleteLater();
} else { } else {
slotInstallationFailed(PackageKit::Transaction::ErrorUnknown, slotInstallationFailed(PackageKit::Transaction::ErrorUnknown,
i18nc("@info %1 is error code", i18nc("@info %1 is error code",
@ -72,13 +75,11 @@ void DolphinPackageManager::install(const QString &packageName, const QUrl &fall
}); });
}); });
#else #else
Q_UNUSED(packageName) QDesktopServices::openUrl(m_fallBackInstallationPageUrl);
QDesktopServices::openUrl(fallBackInstallationPageUrl);
auto waitForSuccess = new QTimer(this); auto waitForSuccess = new QTimer(this);
connect(waitForSuccess, &QTimer::timeout, this, [isPackageInstalledCheck, this, waitForSuccess]() { connect(waitForSuccess, &QTimer::timeout, this, [this]() {
if (isPackageInstalledCheck()) { if (m_isPackageInstalledCheck()) {
Q_EMIT success(); emitResult();
deleteLater();
} }
}); });
waitForSuccess->start(3000); waitForSuccess->start(3000);
@ -86,13 +87,24 @@ void DolphinPackageManager::install(const QString &packageName, const QUrl &fall
} }
#if HAVE_PACKAGEKIT #if HAVE_PACKAGEKIT
void DolphinPackageManager::slotInstallationFailed(PackageKit::Transaction::Error error, const QString &details) void DolphinPackageInstaller::connectTransactionToJobProgress(const PackageKit::Transaction &transaction)
{ {
Q_EMIT failure(xi18nc("@info:shell %1 is package name, %2 is error message, %3 is error e.g. 'ErrorNoNetwork'", connect(&transaction, &PackageKit::Transaction::speedChanged, this, [this, &transaction]() {
emitSpeed(transaction.speed());
});
connect(&transaction, &PackageKit::Transaction::percentageChanged, this, [this, &transaction]() {
setPercent(transaction.percentage());
});
}
void DolphinPackageInstaller::slotInstallationFailed(PackageKit::Transaction::Error error, const QString &details)
{
setErrorString(xi18nc("@info:shell %1 is package name, %2 is error message, %3 is error e.g. 'ErrorNoNetwork'",
"Installing <application>%1</application> failed: %2 (%3)<nl/>Please try installing <application>%1</application> manually instead.", "Installing <application>%1</application> failed: %2 (%3)<nl/>Please try installing <application>%1</application> manually instead.",
m_packageName, m_packageName,
details, details,
QMetaEnum::fromType<PackageKit::Transaction::Error>().valueToKey(error))); QMetaEnum::fromType<PackageKit::Transaction::Error>().valueToKey(error)));
deleteLater(); setError(error);
emitResult();
} }
#endif #endif

View File

@ -0,0 +1,85 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef dolphinpackageinstaller_H
#define dolphinpackageinstaller_H
#include "config-dolphin.h"
#if HAVE_PACKAGEKIT
#include <PackageKit/Transaction>
#endif
#include <KJob>
#include <QUrl>
/**
* @brief A KJob providing simple means to install a package.
*/
class DolphinPackageInstaller : public KJob
{
public:
/**
* @brief Installs a system package.
*
* @param packageName A name that can be resolved to a package.
* @param fallBackInstallationPageUrl This url will be opened if Dolphin was installed without the PackageKit library. A good choice for this parameter
* is an appstream url that will be opened in a software store like Discover
* e.g. "appstream://org.kde.filelight.desktop". The user is then expected to install the package themselves and
* KJob::result() will be emitted when it is detected that the installation finished successfully.
* @param isPackageInstalledCheck A function that can be regularly checked to determine if the installation was already successful.
*/
explicit DolphinPackageInstaller(const QString &packageName,
const QUrl &fallBackInstallationPageUrl,
std::function<bool()> isPackageInstalledCheck,
QObject *parent = nullptr);
/**
* @see KJob::start().
* Make sure to connect to the KJob::result() signal and show the KJob::errorString() to users there before calling this.
*/
void start() override;
/** @see KJob::errorString(). */
inline QString errorString() const override
{
return m_errorString;
};
private:
/** @see KJob::errorString(). */
inline void setErrorString(const QString &errorString)
{
m_errorString = errorString;
};
#if HAVE_PACKAGEKIT
/**
* Makes sure progress signals of @p transaction are forwarded to KJob's progress signals.
*/
void connectTransactionToJobProgress(const PackageKit::Transaction &transaction);
private Q_SLOTS:
/** Creates a nice user-facing error message from its parameters and then finishes this job with an @p error. */
void slotInstallationFailed(PackageKit::Transaction::Error error, const QString &details);
#endif
private:
/** The name of the package that is supposed to be installed. */
const QString m_packageName;
/** @see DolphinPackageInstaller::DolphinPackageInstaller(). */
const QUrl m_fallBackInstallationPageUrl;
/** @see DolphinPackageInstaller::DolphinPackageInstaller(). */
const std::function<bool()> m_isPackageInstalledCheck;
/** @see KJob::errorString(). */
QString m_errorString;
};
#endif // dolphinpackageinstaller_H

View File

@ -1,71 +0,0 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef DOLPHINPACKAGEMANAGER_H
#define DOLPHINPACKAGEMANAGER_H
#include "config-dolphin.h"
#if HAVE_PACKAGEKIT
#include <PackageKit/Transaction>
#endif
#include <QObject>
/**
* @brief A class providing a simple API to install packages.
*/
class DolphinPackageManager : public QObject
{
Q_OBJECT
public:
explicit DolphinPackageManager(QObject *parent = nullptr);
/**
* @brief Installs a system package.
*
* @param packageName A name that can be resolved to a package.
* @param fallBackInstallationPageUrl This url will be opened if Dolphin was installed without the PackageKit library. A good choice for this parameter
* is an appstream url that will be opened in a software store (like Discover) e.g. "appstream://org.kde.kio.admin".
* The user is then expected to install the package themselves and success() will be emitted once that finished.
* @param isPackageInstalledCheck A function that can be regularly checked to determine if the installation was already successful.
*
* Calling this method will lead to either emitting the success() or the failure() signal. Once either is emitted this object will delete itself.
* This generally happens asynchronously with rare exceptions, so connect to the signals before calling this method.
*/
void install(const QString &packageName, const QUrl &fallBackInstallationPageUrl, std::function<bool()> isPackageInstalledCheck);
Q_SIGNALS:
/**
* Emitted when this object assumes that the package was successfully installed.
* This object will delete itself after emitting this signal.
*
* @note This does not always mean that the isPackageInstalledCheck of DolphinPackageManager::install() also passes.
*/
void success();
/**
* Emitted when the installation finally failed.
* This object will delete itself after emitting this signal.
*
* Be sure to show the @p userFacingErrorMessage to the user or they won't know what happened and can't write useful bug reports.
*/
void failure(const QString &userFacingErrorMessage);
#if HAVE_PACKAGEKIT
private Q_SLOTS:
/** Creates a nice user-facing error message from its parameters and then emits failure() and deletes this object. */
void slotInstallationFailed(PackageKit::Transaction::Error error, const QString &details);
#endif
private:
/** The name of the package that is currently being installed. */
QString m_packageName;
};
#endif // DOLPHINPACKAGEMANAGER_H

View File

@ -7,7 +7,7 @@
#include "statusbarspaceinfo.h" #include "statusbarspaceinfo.h"
#include "config-dolphin.h" #include "config-dolphin.h"
#include "dolphinpackagemanager.h" #include "dolphinpackageinstaller.h"
#include "global.h" #include "global.h"
#include "spaceinfoobserver.h" #include "spaceinfoobserver.h"
@ -154,7 +154,7 @@ void StatusBarSpaceInfo::updateMenu()
vLayout->addSpacing(Dolphin::VERTICAL_SPACER_HEIGHT); vLayout->addSpacing(Dolphin::VERTICAL_SPACER_HEIGHT);
auto installFilelightButton = auto installFilelightButton =
new QPushButton(QIcon::fromTheme(QStringLiteral("filelight")), i18nc("@action:button", "Install Filelight"), containerWidget); new QPushButton(QIcon::fromTheme(QStringLiteral("filelight")), i18nc("@action:button", "Install Filelight"), containerWidget);
installFilelightButton->setFixedWidth(std::max(installFilelightButton->sizeHint().width(), installFilelightTitle->sizeHint().width())); installFilelightButton->setFixedWidth(std::max(installFilelightButton->sizeHint().width(), installFilelightTitle->sizeHint().width()));
auto buttonLayout = new QHBoxLayout{containerWidget}; auto buttonLayout = new QHBoxLayout{containerWidget};
buttonLayout->addWidget(installFilelightButton, 0, Qt::AlignHCenter); buttonLayout->addWidget(installFilelightButton, 0, Qt::AlignHCenter);
@ -170,15 +170,21 @@ void StatusBarSpaceInfo::updateMenu()
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QDesktopServices::openUrl(QUrl("https://apps.kde.org/filelight")); QDesktopServices::openUrl(QUrl("https://apps.kde.org/filelight"));
#else #else
auto packageInstaller = new DolphinPackageManager(this); auto packageInstaller = new DolphinPackageInstaller(
connect(packageInstaller, &DolphinPackageManager::failure, this, [this, packageInstaller](const QString &userFacingErrorMessage) { FILELIGHT_PACKAGE_NAME,
Q_EMIT showMessage(userFacingErrorMessage, KMessageWidget::Error); QUrl("appstream://org.kde.filelight.desktop"),
}); []() {
connect(packageInstaller, &DolphinPackageManager::success, this, [this, packageInstaller]() { return KService::serviceByDesktopName(QStringLiteral("org.kde.filelight"));
Q_EMIT showMessage(xi18nc("@info", "<application>Filelight</application> installed successfully."), KMessageWidget::Positive); },
}); this);
packageInstaller->install(FILELIGHT_PACKAGE_NAME, QUrl("appstream://org.kde.filelight.desktop"), [](){ return KService::serviceByDesktopName(QStringLiteral("org.kde.filelight")); connect(packageInstaller, &KJob::result, this, [this](KJob *job) {
}); if (job->error()) {
Q_EMIT showMessage(job->errorString(), KMessageWidget::Error);
} else {
Q_EMIT showMessage(xi18nc("@info", "<application>Filelight</application> installed successfully."), KMessageWidget::Positive);
}
});
packageInstaller->start();
#endif #endif
}); });