diff --git a/discover/qml/SourcesPage.qml b/discover/qml/SourcesPage.qml
index adda4e65..6d289bee 100644
--- a/discover/qml/SourcesPage.qml
+++ b/discover/qml/SourcesPage.qml
@@ -1,231 +1,232 @@
import QtQuick 2.4
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import org.kde.discover 2.0
import org.kde.discover.app 1.0
import org.kde.kirigami 2.2 as Kirigami
import "navigation.js" as Navigation
DiscoverPage {
id: page
clip: true
title: i18n("Settings")
property string search: ""
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
contextualActions: [
KirigamiActionBridge { action: app.action("help_about_app") },
KirigamiActionBridge { action: app.action("help_report_bug") }
]
mainItem: ListView {
id: sourcesView
model: QSortFilterProxyModel {
filterRegExp: new RegExp(page.search, 'i')
dynamicSortFilter: false //We don't want to sort, as sorting can have some semantics on some backends
sourceModel: SourcesModel
}
currentIndex: -1
Component {
id: sourceBackendDelegate
Kirigami.AbstractListItem {
id: backendItem
hoverEnabled: false
supportsMouseEvents: false
readonly property QtObject backend: sourcesBackend
readonly property bool isDefault: ResourcesModel.currentApplicationBackend == resourcesBackend
RowLayout {
Connections {
target: backendItem.backend
onPassiveMessage: window.showPassiveNotification(message)
}
anchors {
right: parent.right
left: parent.left
rightMargin: parent.rightPadding
leftMargin: parent.leftPadding
}
Kirigami.Heading {
Layout.fillWidth: true
text: backendItem.isDefault ? i18n("%1 (Default)", resourcesBackend.displayName) : resourcesBackend.displayName
}
Button {
Layout.rightMargin: Kirigami.Units.smallSpacing
icon.name: "preferences-other"
visible: resourcesBackend && resourcesBackend.hasApplications
Component {
id: dialogComponent
AddSourceDialog {
source: backendItem.backend
onVisibleChanged: if (!visible) {
destroy()
}
}
}
id: this
onClicked: settingsMenu.popup(this)
Menu {
id: settingsMenu
MenuItem {
enabled: !backendItem.isDefault
text: i18n("Make default")
onTriggered: ResourcesModel.currentApplicationBackend = backendItem.backend.resourcesBackend
}
MenuItem {
text: i18n("Add Source...")
visible: backendItem.backend && backendItem.backend.supportsAdding
onTriggered: {
var addSourceDialog = dialogComponent.createObject(null, {displayName: backendItem.backend.resourcesBackend.displayName })
addSourceDialog.open()
}
}
MenuSeparator {
visible: backendActionsInst.count>0
}
Instantiator {
id: backendActionsInst
model: ActionsModel {
actions: backendItem.backend ? backendItem.backend.actions : undefined
}
delegate: MenuItem {
action: ActionBridge {
action: modelData.action
}
}
onObjectAdded: {
settingsMenu.insertItem(index, object)
}
onObjectRemoved: {
object.destroy()
}
}
}
}
}
}
}
delegate: ConditionalLoader {
anchors {
right: parent.right
left: parent.left
}
readonly property variant resourcesBackend: model.resourcesBackend
readonly property variant sourcesBackend: model.sourcesBackend
readonly property variant display: model.display
readonly property variant checked: model.checked
readonly property variant statusTip: model.statusTip
readonly property variant toolTip: model.toolTip
readonly property variant sourceId: model.sourceId
readonly property variant modelIndex: sourcesView.model.index(index, 0)
condition: resourcesBackend != null
componentTrue: sourceBackendDelegate
componentFalse: sourceDelegate
}
Component {
id: sourceDelegate
Kirigami.SwipeListItem {
Layout.fillWidth: true
enabled: display.length>0
highlighted: ListView.isCurrentItem
onClicked: Navigation.openApplicationListSource(sourceId)
Keys.onReturnPressed: clicked()
actions: [
Kirigami.Action {
iconName: "go-up"
enabled: sourcesBackend.firstSourceId !== sourceId
visible: sourcesBackend.canMoveSources
onTriggered: {
var ret = sourcesBackend.moveSource(sourceId, -1)
if (!ret)
window.showPassiveNotification(i18n("Failed to increase '%1' preference", display))
}
},
Kirigami.Action {
iconName: "go-down"
enabled: sourcesBackend.lastSourceId !== sourceId
visible: sourcesBackend.canMoveSources
onTriggered: {
var ret = sourcesBackend.moveSource(sourceId, +1)
if (!ret)
window.showPassiveNotification(i18n("Failed to decrease '%1' preference", display))
}
},
Kirigami.Action {
iconName: "edit-delete"
tooltip: i18n("Delete the origin")
onTriggered: {
var backend = sourcesBackend
if (!backend.removeSource(sourceId)) {
window.showPassiveNotification(i18n("Failed to remove the source '%1'", display))
}
}
}
]
RowLayout {
CheckBox {
id: enabledBox
readonly property variant modelChecked: sourcesView.model.data(modelIndex, Qt.CheckStateRole)
checked: modelChecked != Qt.Unchecked
enabled: modelChecked !== undefined
onClicked: {
sourcesView.model.setData(modelIndex, checkedState, Qt.CheckStateRole)
}
}
Label {
text: display + " - " + toolTip + ""
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
footer: ColumnLayout {
id: foot
anchors {
right: parent.right
left: parent.left
margins: Kirigami.Units.smallSpacing
}
Kirigami.Heading {
Layout.fillWidth: true
text: i18n("Missing Backends")
visible: back.count>0
}
+ spacing: 0
Repeater {
id: back
model: ResourcesProxyModel {
extending: "org.kde.discover.desktop"
}
delegate: Kirigami.BasicListItem {
supportsMouseEvents: false
label: name
icon: model.icon
InstallApplicationButton {
application: model.application
}
}
}
}
}
}
diff --git a/libdiscover/backends/PackageKitBackend/CMakeLists.txt b/libdiscover/backends/PackageKitBackend/CMakeLists.txt
index f739d3cc..961b105d 100644
--- a/libdiscover/backends/PackageKitBackend/CMakeLists.txt
+++ b/libdiscover/backends/PackageKitBackend/CMakeLists.txt
@@ -1,38 +1,38 @@
find_package(KF5 REQUIRED Notifications)
add_subdirectory(runservice)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-paths.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-paths.h)
add_library(packagekit-backend MODULE PackageKitBackend.cpp
PackageKitResource.cpp
AppPackageKitResource.cpp
PKTransaction.cpp
PackageKitUpdater.cpp
PackageKitMessages.cpp
PackageKitSourcesBackend.cpp
LocalFilePKResource.cpp
TransactionSet.cpp
pkui.qrc
)
target_link_libraries(packagekit-backend PRIVATE Discover::Common Qt5::Core PK::packagekitqt5 KF5::ConfigGui KF5::KIOCore KF5::Archive AppStreamQt)
install(TARGETS packagekit-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
#notifier
add_library(DiscoverPackageKitNotifier MODULE PackageKitNotifier.cpp)
target_link_libraries(DiscoverPackageKitNotifier PRIVATE PK::packagekitqt5 Discover::Notifiers KF5::I18n KF5::Notifications KF5::ConfigCore)
set_target_properties(DiscoverPackageKitNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
install(TARGETS DiscoverPackageKitNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
install(FILES packagekit-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
add_subdirectory(categoryimages)
-if(packagekitqt5_VERSION VERSION_GREATER 1.0.1)
+if(packagekitqt5_VERSION VERSION_GREATER_EQUAL 1.0.1)
target_compile_definitions(packagekit-backend PUBLIC -DPKQT_1_0)
target_compile_definitions(DiscoverPackageKitNotifier PUBLIC -DPKQT_1_0)
endif()
install( FILES org.kde.discover.packagekit.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} )
diff --git a/libdiscover/backends/PackageKitBackend/PKTransaction.cpp b/libdiscover/backends/PackageKitBackend/PKTransaction.cpp
index b8b67387..459c2847 100644
--- a/libdiscover/backends/PackageKitBackend/PKTransaction.cpp
+++ b/libdiscover/backends/PackageKitBackend/PKTransaction.cpp
@@ -1,287 +1,290 @@
/***************************************************************************
* Copyright © 2013 Aleix Pol Gonzalez *
* *
* 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 . *
***************************************************************************/
#include "PKTransaction.h"
#include "PackageKitBackend.h"
#include "PackageKitResource.h"
#include "PackageKitMessages.h"
#include "utils.h"
#include "LocalFilePKResource.h"
#include
#include
#include
#include
#include
#include
#include
PKTransaction::PKTransaction(const QVector& apps, Transaction::Role role)
: Transaction(apps.first(), apps.first(), role)
, m_apps(apps)
{
Q_ASSERT(!apps.contains(nullptr));
foreach(auto r, apps) {
PackageKitResource* res = qobject_cast(r);
m_pkgnames.unite(res->allPackageNames().toSet());
}
QTimer::singleShot(0, this, &PKTransaction::start);
}
static QStringList packageIds(const QVector& res, std::function func)
{
QStringList ret;
foreach(auto r, res) {
ret += func(qobject_cast(r));
}
ret.removeDuplicates();
return ret;
}
void PKTransaction::start()
{
trigger(PackageKit::Transaction::TransactionFlagSimulate);
}
void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)
{
if (m_trans)
m_trans->deleteLater();
m_newPackageStates.clear();
if (m_apps.size() == 1 && qobject_cast(m_apps.at(0))) {
auto app = qobject_cast(m_apps.at(0));
m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags);
connect(m_trans.data(), &PackageKit::Transaction::finished, this, [app](PackageKit::Transaction::Exit status) {
if (status == PackageKit::Transaction::ExitSuccess) {
app->markInstalled();
}
});
} else switch (role()) {
case Transaction::ChangeAddonsRole:
case Transaction::InstallRole:
m_trans = PackageKit::Daemon::installPackages(packageIds(m_apps, [](PackageKitResource* r){return r->availablePackageId(); }), flags);
break;
case Transaction::RemoveRole:
//see bug #315063
m_trans = PackageKit::Daemon::removePackages(packageIds(m_apps, [](PackageKitResource* r){return r->installedPackageId(); }), true /*allowDeps*/, false, flags);
break;
};
Q_ASSERT(m_trans);
// connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, [this]() { qDebug() << "state..." << m_trans->status(); });
connect(m_trans.data(), &PackageKit::Transaction::package, this, &PKTransaction::packageResolved);
connect(m_trans.data(), &PackageKit::Transaction::finished, this, &PKTransaction::cleanup);
connect(m_trans.data(), &PackageKit::Transaction::errorCode, this, &PKTransaction::errorFound);
connect(m_trans.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PKTransaction::mediaChange);
connect(m_trans.data(), &PackageKit::Transaction::requireRestart, this, &PKTransaction::requireRestart);
connect(m_trans.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PKTransaction::repoSignatureRequired);
connect(m_trans.data(), &PackageKit::Transaction::percentageChanged, this, &PKTransaction::progressChanged);
connect(m_trans.data(), &PackageKit::Transaction::statusChanged, this, &PKTransaction::statusChanged);
connect(m_trans.data(), &PackageKit::Transaction::eulaRequired, this, &PKTransaction::eulaRequired);
connect(m_trans.data(), &PackageKit::Transaction::allowCancelChanged, this, &PKTransaction::cancellableChanged);
connect(m_trans.data(), &PackageKit::Transaction::speedChanged, this, [this]() {
setDownloadSpeed(m_trans->speed());
});
setCancellable(m_trans->allowCancel());
}
void PKTransaction::statusChanged()
{
setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);
progressChanged();
}
int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage);
void PKTransaction::progressChanged()
{
auto percent = m_trans->percentage();
if (percent == 101) {
qWarning() << "percentage cannot be calculated";
percent = 50;
}
const auto processedPercentage = percentageWithStatus(m_trans->status(), qBound(0, percent, 100));
if (processedPercentage >= 0)
setProgress(processedPercentage);
}
void PKTransaction::cancellableChanged()
{
setCancellable(m_trans->allowCancel());
}
void PKTransaction::cancel()
{
if (!m_trans) {
setStatus(CancelledStatus);
} else if (m_trans->allowCancel()) {
m_trans->cancel();
} else {
qWarning() << "trying to cancel a non-cancellable transaction: " << resource()->name();
}
}
void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)
{
Q_UNUSED(runtime)
const bool cancel = !m_proceedFunctions.isEmpty() || exit == PackageKit::Transaction::ExitCancelled;
const bool failed = exit == PackageKit::Transaction::ExitFailed;
const bool simulate = m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate;
disconnect(m_trans, nullptr, this, nullptr);
m_trans = nullptr;
const auto backend = qobject_cast(resource()->backend());
if (!cancel && !failed && simulate) {
auto packagesToRemove = m_newPackageStates.value(PackageKit::Transaction::InfoRemoving);
QMutableListIterator i(packagesToRemove);
QSet removedResources;
while (i.hasNext()) {
const auto pkgname = PackageKit::Daemon::packageName(i.next());
removedResources.unite(backend->resourcesByPackageName(pkgname));
if (m_pkgnames.contains(pkgname)) {
i.remove();
}
}
removedResources.subtract(m_apps.toList().toSet());
if (!packagesToRemove.isEmpty() || !removedResources.isEmpty()) {
QString msg = QStringLiteral("- ") + PackageKitResource::joinPackages(packagesToRemove, QStringLiteral("
- "));
if (!removedResources.isEmpty()) {
const QStringList removedResourcesStr = kTransform(removedResources, [](AbstractResource* a) { return a->name(); });
msg += QLatin1Char('\n');
msg += removedResourcesStr.join(QStringLiteral("
- "));
}
msg += QStringLiteral("
");
Q_EMIT proceedRequest(i18n("Confirm package removal"), i18np("This action will also remove the following package:\n%2", "This action will also remove the following packages:\n%2", packagesToRemove.count(), msg));
} else {
proceed();
}
return;
}
+ if (failed && m_newPackageStates.isEmpty())
+ m_newPackageStates.insert(PackageKit::Transaction::InfoAvailable, kTransform(m_apps, [](AbstractResource* res) { return res->packageName(); }));
+
this->submitResolve();
if (failed)
setStatus(Transaction::DoneWithErrorStatus);
else
setStatus(Transaction::CancelledStatus);
}
void PKTransaction::processProceedFunction()
{
auto t = m_proceedFunctions.takeFirst()();
connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) {
if (status != PackageKit::Transaction::Exit::ExitSuccess) {
qWarning() << "transaction failed" << sender() << status;
cancel();
return;
}
if (!m_proceedFunctions.isEmpty()) {
processProceedFunction();
} else {
start();
}
});
}
void PKTransaction::proceed()
{
if (!m_proceedFunctions.isEmpty()) {
processProceedFunction();
} else {
trigger(PackageKit::Transaction::TransactionFlagOnlyTrusted);
}
}
void PKTransaction::packageResolved(PackageKit::Transaction::Info info, const QString& packageId)
{
m_newPackageStates[info].append(packageId);
}
void PKTransaction::submitResolve()
{
QStringList needResolving;
foreach(const auto &pkgids, m_newPackageStates) {
foreach(const auto &pkgid, pkgids) {
needResolving += PackageKit::Daemon::packageName(pkgid);
}
}
if (!needResolving.isEmpty()) {
needResolving.removeDuplicates();
const auto backend = qobject_cast(resource()->backend());
backend->clearPackages(needResolving);
backend->resolvePackages(needResolving);
}
}
PackageKit::Transaction* PKTransaction::transaction()
{
return m_trans;
}
void PKTransaction::eulaRequired(const QString& eulaID, const QString& packageID, const QString& vendor, const QString& licenseAgreement)
{
m_proceedFunctions << [eulaID](){
return PackageKit::Daemon::acceptEula(eulaID);
};
Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The package %1 and its vendor %2 require that you accept their license:\n %3",
PackageKit::Daemon::packageName(packageID), vendor, licenseAgreement));
}
void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString& error)
{
if (err == PackageKit::Transaction::ErrorNoLicenseAgreement)
return;
qWarning() << "PackageKit error:" << err << PackageKitMessages::errorMessage(err) << error;
Q_EMIT passiveMessage(PackageKitMessages::errorMessage(err));
}
void PKTransaction::mediaChange(PackageKit::Transaction::MediaType media, const QString& type, const QString& text)
{
Q_UNUSED(media)
Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text));
}
void PKTransaction::requireRestart(PackageKit::Transaction::Restart restart, const QString& pkgid)
{
Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid));
}
void PKTransaction::repoSignatureRequired(const QString& packageID, const QString& repoName, const QString& keyUrl,
const QString& keyUserid, const QString& keyId, const QString& keyFingerprint,
const QString& keyTimestamp, PackageKit::Transaction::SigType type)
{
Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName),
i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n",
keyUrl, keyUserid, keyFingerprint, keyTimestamp));
m_proceedFunctions << [type, keyId, packageID](){
return PackageKit::Daemon::installSignature(type, keyId, packageID);
};
}