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); }; }