diff --git a/CMakeLists.txt b/CMakeLists.txt index dce841be..94d6d326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,79 +1,81 @@ project(discover) set(PROJECT_VERSION "5.12.80") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12) find_package(ECM REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_SOURCE_DIR}/cmake") find_package(Qt5 5.7.0 REQUIRED CONFIG COMPONENTS Widgets Test Network Xml Concurrent DBus Quick) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMAddTests) include(GenerateExportHeader) find_package(PkgConfig REQUIRED) find_package(KF5 5.45 REQUIRED CoreAddons Config Crash DBusAddons I18n Archive XmlGui ItemModels KIO) find_package(KF5Kirigami2 2.1.0) find_package(packagekitqt5 CONFIG) find_package(AppStreamQt 0.11.1 CONFIG) find_package(KF5Attica 5.23 CONFIG) find_package(KF5NewStuff 5.23 CONFIG) +set(CMAKE_AUTORCC ON) + pkg_check_modules(FLATPAK flatpak>=0.6.12) if(NOT CMAKE_VERSION VERSION_LESS "3.10.0") # CMake 3.9+ warns about automoc on files without Q_OBJECT, and doesn't know about other macros. # 3.10+ lets us provide more macro names that require automoc. list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "DISCOVER_BACKEND_PLUGIN") endif() configure_file(DiscoverVersion.h.in DiscoverVersion.h) add_subdirectory(libdiscover) add_subdirectory(discover) add_subdirectory(exporter) option(WITH_NOTIFIER "Build and install the notifier plasmoid" ON) if(WITH_NOTIFIER) find_package(KF5 REQUIRED Notifications KIO) add_subdirectory(notifier) endif() set_package_properties(KF5Attica PROPERTIES DESCRIPTION "KDE Framework that implements the Open Collaboration Services API" PURPOSE "Required to build the KNewStuff3 backend" TYPE OPTIONAL) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "KDE's lightweight user interface framework for mobile and convergent applications" URL "https://techbase.kde.org/Kirigami" PURPOSE "Required by discover qml components" TYPE RUNTIME) set_package_properties(KF5NewStuff PROPERTIES DESCRIPTION "Qt library that allows to interact with KNewStuff implementations" PURPOSE "Required to build the KNS backend" TYPE OPTIONAL) set_package_properties(packagekitqt5 PROPERTIES DESCRIPTION "Library that exposes PackageKit resources" URL "http://www.packagekit.org" PURPOSE "Required to build the PackageKit backend" TYPE OPTIONAL) set_package_properties(AppStreamQt PROPERTIES DESCRIPTION "Library that lists Appstream resources" URL "http://www.freedesktop.org" PURPOSE "Required to build the PackageKit backend" TYPE OPTIONAL) set_package_properties(FLATPAK PROPERTIES DESCRIPTION "Library that exposes flatpak repositories" URL "http://www.freedesktop.org" PURPOSE "Required to build the Flatpak backend" TYPE OPTIONAL) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/discover/CMakeLists.txt b/discover/CMakeLists.txt index 7eb03e50..8c094bb1 100644 --- a/discover/CMakeLists.txt +++ b/discover/CMakeLists.txt @@ -1,68 +1,68 @@ add_subdirectory(icons) add_subdirectory(autotests) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/..) -qt5_add_resources(plasma_discover_SRCS resources.qrc) -qt5_add_resources(plasma_discover_SRCS assets.qrc) - add_executable(plasma-discover ${plasma_discover_SRCS} main.cpp DiscoverObject.cpp DiscoverDeclarativePlugin.cpp FeaturedModel.cpp PaginateModel.cpp UnityLauncher.cpp + + resources.qrc + assets.qrc ) add_executable(Plasma::Discover ALIAS plasma-discover) set_target_properties(plasma-discover PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover) target_link_libraries(plasma-discover PUBLIC KF5::Crash KF5::DBusAddons KF5::I18n KF5::XmlGui KF5::ItemModels KF5::KIOWidgets Qt5::Quick Discover::Common ) install(TARGETS plasma-discover ${INSTALL_TARGETS_DEFAULT_ARGS} ) # if (BUILD_DummyBackend) # target_compile_definitions(plasma-discover PRIVATE $<$:QT_QML_DEBUG=1>) # endif() # Standard desktop file accepts local files as input. set(DesktopNoDisplay "false") set(DesktopMimeType "application/vnd.debian.binary-package;application/x-rpm;") set(DesktopExec "plasma-discover %F") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # Support appstream:// URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/appstream;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # support snap:/ URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/snap;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) if(EXISTS "/etc/debian_version") set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/apt") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) endif() install(FILES plasmadiscoverui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/plasmadiscover) install( FILES org.kde.discover.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index 3752c77b..0f692099 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,280 +1,281 @@ import QtQuick.Controls 1.2 import QtQuick.Controls 2.1 as QQC2 import QtQuick.Layouts 1.1 import QtQuick 2.4 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import "navigation.js" as Navigation import org.kde.kirigami 2.1 as Kirigami DiscoverPage { id: page title: i18n("Updates") property string footerLabel: "" ResourcesUpdatesModel { id: resourcesUpdatesModel onPassiveMessage: window.showPassiveNotification(message) onIsProgressingChanged: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } Component.onCompleted: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } } UpdateModel { id: updateModel backend: resourcesUpdatesModel } Kirigami.Action { id: updateAction text: page.unselected>0 ? i18n("Update Selected") : i18n("Update All") visible: updateModel.toUpdateCount iconName: "update-none" enabled: !resourcesUpdatesModel.isProgressing onTriggered: resourcesUpdatesModel.updateAll() } Kirigami.Action { id: cancelUpdateAction iconName: "dialog-cancel" text: i18n("Cancel") enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable onTriggered: resourcesUpdatesModel.transaction.cancel() } readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount) readonly property QtObject currentAction: resourcesUpdatesModel.isProgressing ? cancelUpdateAction : updateAction actions { left: refreshAction main: currentAction } header: QQC2.ToolBar { visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates RowLayout { anchors.fill: parent LabelBackground { Layout.leftMargin: Kirigami.Units.gridUnit text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")" } QQC2.Label { text: i18n("updates selected") } LabelBackground { id: unselectedItem text: page.unselected visible: page.unselected>0 } QQC2.Label { text: i18n("updates not selected") visible: unselectedItem.visible } Item { Layout.fillWidth: true } + enabled: page.currentAction.enabled } } supportsRefreshing: true onRefreshingChanged: { showPassiveNotification("Fetching updates...") ResourcesModel.updateAction.triggered() refreshing = false } ListView { id: updatesView currentIndex: -1 footer: ColumnLayout { anchors.right: parent.right anchors.left: parent.left Kirigami.Heading { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter visible: page.footerLabel !== "" text: page.footerLabel } Kirigami.Icon { Layout.alignment: Qt.AlignHCenter visible: page.footerLabel !== "" source: "update-none" opacity: 0.3 width: 200 height: 200 } Item { visible: page.footerLabel === "" height: Kirigami.Units.gridUnit width: 1 } } model: updateModel section { property: "section" delegate: Kirigami.Heading { x: Kirigami.Units.gridUnit level: 2 text: section } } spacing: Kirigami.Units.smallSpacing delegate: Kirigami.AbstractListItem { backgroundColor: Kirigami.Theme.viewBackgroundColor x: Kirigami.Units.gridUnit width: ListView.view.width - Kirigami.Units.gridUnit * 2 highlighted: ListView.isCurrentItem onEnabledChanged: if (!enabled) { layout.extended = false; } Keys.onReturnPressed: { itemChecked.clicked() } Keys.onPressed: if (event.key===Qt.Key_Alt) layout.extended = true Keys.onReleased: if (event.key===Qt.Key_Alt) layout.extended = false ColumnLayout { id: layout property bool extended: false onExtendedChanged: if (extended) { updateModel.fetchChangelog(index) } RowLayout { Layout.fillWidth: true Layout.fillHeight: true CheckBox { id: itemChecked Layout.alignment: Qt.AlignVCenter checked: model.checked == Qt.Checked onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked) } Kirigami.Icon { Layout.fillHeight: true Layout.preferredWidth: height source: decoration smooth: true } QQC2.Label { Layout.fillWidth: true text: i18n("%1 (%2)", display, version) elide: Text.ElideRight } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: size progress: resourceProgress/100 } } QQC2.Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 QQC2.Label { id: view anchors { right: parent.right left: parent.left } text: changelog textFormat: Text.StyledText wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) } //This saves a binding loop on implictHeight, as the Label //height is updated twice (first time with the wrong value) Behavior on implicitHeight { PropertyAnimation { duration: Kirigami.Units.shortDuration } } } Button { Layout.alignment: Qt.AlignRight text: i18n("More Information...") visible: layout.extended enabled: !resourcesUpdatesModel.isProgressing onClicked: Navigation.openApplication(resource) } } onClicked: { layout.extended = !layout.extended } } } readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate state: ( updateModel.hasUpdates ? "has-updates" : ResourcesModel.isFetching ? "fetching" : resourcesUpdatesModel.isProgressing ? "progressing" : secSinceUpdate < 0 ? "unknown" : secSinceUpdate === 0 ? "now-uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 ? "uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 * 7 ? "medium" : "low" ) states: [ State { name: "fetching" PropertyChanges { target: page; title: i18nc("@info", "Fetching...") } PropertyChanges { target: page; footerLabel: i18nc("@info", "Looking for updates") } }, State { name: "progressing" PropertyChanges { target: page; title: i18nc("@info", "Updating...") } PropertyChanges { target: page; footerLabel: resourcesUpdatesModel.progress<=0 ? i18nc("@info", "Fetching updates") : "" } }, State { name: "has-updates" PropertyChanges { target: page; title: i18nc("@info", "Updates") } }, State { name: "now-uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "medium" PropertyChanges { target: page; title: i18nc("@info", "No updates are available") } }, State { name: "low" PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") } }, State { name: "unknown" PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") } } ] } diff --git a/libdiscover/UpdateModel/UpdateModel.cpp b/libdiscover/UpdateModel/UpdateModel.cpp index d0cd006c..5cc48c87 100644 --- a/libdiscover/UpdateModel/UpdateModel.cpp +++ b/libdiscover/UpdateModel/UpdateModel.cpp @@ -1,283 +1,294 @@ /*************************************************************************** * Copyright © 2011 Jonathan Thomas * * * * 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 "UpdateModel.h" // Qt includes #include #include // KDE includes #include #include // Own includes #include "UpdateItem.h" #include #include #include UpdateModel::UpdateModel(QObject *parent) : QAbstractListModel(parent) , m_updates(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::fetchingChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::updatesCountChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &UpdateModel::resourceDataChanged); connect(this, &UpdateModel::toUpdateChanged, this, &UpdateModel::updateSizeChanged); } UpdateModel::~UpdateModel() = default; QHash UpdateModel::roleNames() const { return QAbstractItemModel::roleNames().unite({ { Qt::CheckStateRole, "checked" }, { ResourceProgressRole, "resourceProgress" }, { ResourceRole, "resource" }, { SizeRole, "size" }, { VersionRole, "version" }, { SectionRole, "section" }, { ChangelogRole, "changelog" } } ); } void UpdateModel::setBackend(ResourcesUpdatesModel* updates) { if (m_updates) { disconnect(m_updates, nullptr, this, nullptr); } m_updates = updates; connect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged); connect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed); activityChanged(); } void UpdateModel::resourceHasProgressed(AbstractResource* res, qreal progress) { UpdateItem* item = itemFromResource(res); if (!item) return; item->setProgress(progress); const QModelIndex idx = indexFromItem(item); Q_EMIT dataChanged(idx, idx, { ResourceProgressRole }); } void UpdateModel::activityChanged() { if (m_updates) { if (!m_updates->isProgressing()) { m_updates->prepare(); + setResources(m_updates->toUpdate()); + + for(auto item : m_updateItems) { + item->setProgress(0); + } } - setResources(m_updates->toUpdate()); } } QVariant UpdateModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } UpdateItem *item = itemFromIndex(index); switch (role) { case Qt::DisplayRole: return item->name(); case Qt::DecorationRole: return item->icon(); case Qt::CheckStateRole: return item->checked(); case VersionRole: return item->version(); case SizeRole: return KFormat().formatByteSize(item->size()); case ResourceRole: return QVariant::fromValue(item->resource()); case ResourceProgressRole: return item->progress(); case ChangelogRole: return item->changelog(); case SectionRole: return item->section(); default: break; } return QVariant(); } void UpdateModel::checkResources(const QList& resource, bool checked) { if(checked) m_updates->addResources(resource); else m_updates->removeResources(resource); } Qt::ItemFlags UpdateModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int UpdateModel::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_updateItems.count() : 0; } bool UpdateModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { UpdateItem *item = itemFromIndex(idx); const bool newValue = value.toInt() == Qt::Checked; const QList apps = { item->app() }; checkResources(apps, newValue); Q_ASSERT(idx.data(Qt::CheckStateRole) == value); - Q_EMIT dataChanged(idx, idx, { Qt::CheckStateRole }); + + //When un/checking some backends will decide to add or remove a bunch of packages, so refresh it all + auto m = idx.model(); + Q_EMIT dataChanged(m->index(0, 0), m->index(m->rowCount()-1, 0), { Qt::CheckStateRole }); Q_EMIT toUpdateChanged(); return true; } return false; } void UpdateModel::fetchChangelog(int row) { UpdateItem *item = itemFromIndex(index(row, 0)); Q_ASSERT(item); if (!item) return; item->app()->fetchChangelog(); } void UpdateModel::integrateChangelog(const QString &changelog) { auto app = qobject_cast(sender()); Q_ASSERT(app); auto item = itemFromResource(app); if (!item) return; item->setChangelog(changelog); const QModelIndex idx = indexFromItem(item); Q_ASSERT(idx.isValid()); emit dataChanged(idx, idx, { ChangelogRole }); } -void UpdateModel::setResources(const QList< AbstractResource* >& resources) +void UpdateModel::setResources(const QList& resources) { + if (resources == m_resources) { + return; + } + m_resources = resources; + beginResetModel(); qDeleteAll(m_updateItems); m_updateItems.clear(); - const QString importantUpdatesSection = i18nc("@item:inlistbox", "Important Security Updates"); const QString appUpdatesSection = i18nc("@item:inlistbox", "Application Updates"); const QString systemUpdateSection = i18nc("@item:inlistbox", "System Updates"); QVector appItems, systemItems; foreach(AbstractResource* res, resources) { connect(res, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection); UpdateItem *updateItem = new UpdateItem(res); if(!res->isTechnical()) { updateItem->setSection(appUpdatesSection); appItems += updateItem; } else { updateItem->setSection(systemUpdateSection); systemItems += updateItem; } } const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) { return a->name() < b->name(); }; qSort(appItems.begin(), appItems.end(), sortUpdateItems); qSort(systemItems.begin(), systemItems.end(), sortUpdateItems); m_updateItems = (QVector() << appItems << systemItems); endResetModel(); Q_EMIT hasUpdatesChanged(!resources.isEmpty()); Q_EMIT toUpdateChanged(); } bool UpdateModel::hasUpdates() const { return rowCount() > 0; } ResourcesUpdatesModel* UpdateModel::backend() const { return m_updates; } int UpdateModel::toUpdateCount() const { int ret = 0; foreach (UpdateItem* item, m_updateItems) { ret += item->checked() != Qt::Unchecked ? 1 : 0; } return ret; } UpdateItem * UpdateModel::itemFromResource(AbstractResource* res) { foreach (UpdateItem* item, m_updateItems) { if (item->app() == res) return item; } return nullptr; } QString UpdateModel::updateSize() const { return KFormat().formatByteSize(m_updates->updateSize()); } QModelIndex UpdateModel::indexFromItem(UpdateItem* item) const { return index(m_updateItems.indexOf(item), 0, {}); } UpdateItem * UpdateModel::itemFromIndex(const QModelIndex& index) const { return m_updateItems[index.row()]; } void UpdateModel::resourceDataChanged(AbstractResource* res, const QVector& properties) { auto item = itemFromResource(res); if (!item) return; const auto index = indexFromItem(item); if (properties.contains("state")) dataChanged(index, index, {SizeRole, VersionRole}); else if (properties.contains("size")) { dataChanged(index, index, {SizeRole}); Q_EMIT updateSizeChanged(); } } diff --git a/libdiscover/UpdateModel/UpdateModel.h b/libdiscover/UpdateModel/UpdateModel.h index 9ab6012c..89b18347 100644 --- a/libdiscover/UpdateModel/UpdateModel.h +++ b/libdiscover/UpdateModel/UpdateModel.h @@ -1,98 +1,99 @@ /*************************************************************************** * Copyright © 2011 Jonathan Thomas * * * * 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 . * ***************************************************************************/ #ifndef UPDATEMODEL_H #define UPDATEMODEL_H #include #include "discovercommon_export.h" class ResourcesUpdatesModel; class AbstractResource; class UpdateItem; class DISCOVERCOMMON_EXPORT UpdateModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(ResourcesUpdatesModel* backend READ backend WRITE setBackend) Q_PROPERTY(bool hasUpdates READ hasUpdates NOTIFY hasUpdatesChanged) Q_PROPERTY(int toUpdateCount READ toUpdateCount NOTIFY toUpdateChanged) Q_PROPERTY(int totalUpdatesCount READ totalUpdatesCount NOTIFY hasUpdatesChanged) Q_PROPERTY(QString updateSize READ updateSize NOTIFY updateSizeChanged) public: enum Roles { VersionRole = Qt::UserRole + 1, SizeRole, ResourceRole, ResourceProgressRole, ChangelogRole, SectionRole }; explicit UpdateModel(QObject *parent = nullptr); ~UpdateModel() override; QVariant data(const QModelIndex &index, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; void setResources(const QList& res); UpdateItem *itemFromIndex(const QModelIndex &index) const; void checkResources(const QList& resource, bool checked); QHash roleNames() const override; bool hasUpdates() const; ///all upgradeable packages int totalUpdatesCount() const { return m_updateItems.count(); } ///packages marked to upgrade int toUpdateCount() const; Q_SCRIPTABLE void fetchChangelog(int row); QString updateSize() const; ResourcesUpdatesModel* backend() const; public Q_SLOTS: void setBackend(ResourcesUpdatesModel* updates); Q_SIGNALS: void hasUpdatesChanged(bool hasUpdates); void toUpdateChanged(); void updateSizeChanged(); private: void resourceDataChanged(AbstractResource* res, const QVector &properties); void integrateChangelog(const QString &changelog); QModelIndex indexFromItem(UpdateItem* item) const; UpdateItem* itemFromResource(AbstractResource* res); void resourceHasProgressed(AbstractResource* res, qreal progress); void activityChanged(); QVector m_updateItems; ResourcesUpdatesModel* m_updates; + QList m_resources; }; #endif // UPDATEMODEL_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp index 39602c70..8138aa87 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp @@ -1,549 +1,549 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * 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 "FlatpakResource.h" #include "FlatpakBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QString iconCachePath(const AppStream::Icon &icon) { Q_ASSERT(icon.kind() == AppStream::Icon::KindRemote); return QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).arg(icon.url().fileName()); } FlatpakResource::FlatpakResource(const AppStream::Component &component, FlatpakInstallation* installation, FlatpakBackend *parent) : AbstractResource(parent) , m_appdata(component) , m_downloadSize(0) , m_installedSize(0) , m_propertyStates({{DownloadSize, NotKnownYet}, {InstalledSize, NotKnownYet},{RequiredRuntime, NotKnownYet}}) , m_installation(installation) , m_state(AbstractResource::None) , m_type(FlatpakResource::DesktopApp) { setObjectName(component.id()); // Start fetching remote icons during initialization const auto icons = m_appdata.icons(); if (!icons.isEmpty()) { foreach (const AppStream::Icon &icon, icons) { if (icon.kind() == AppStream::Icon::KindRemote) { const QString fileName = iconCachePath(icon); if (!QFileInfo::exists(fileName)) { const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); // Create $HOME/.cache/discover/icons folder cacheDir.mkdir(QStringLiteral("icons")); QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, [this, icon, fileName, manager] (QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray iconData = reply->readAll(); QFile file(fileName); if (file.open(QIODevice::WriteOnly)) { file.write(iconData); } file.close(); Q_EMIT iconChanged(); } manager->deleteLater(); }); manager->get(QNetworkRequest(icon.url())); } } } } } AppStream::Component FlatpakResource::appstreamComponent() const { return m_appdata; } QList FlatpakResource::addonsInformation() { return m_addons; } QString FlatpakResource::availableVersion() const { QString theBranch = branch(); if (theBranch.isEmpty()) { theBranch = i18n("Unknown"); } if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return i18n("%1 (%2)", release.version(), theBranch); } return theBranch; } QString FlatpakResource::appstreamId() const { return m_appdata.id(); } QString FlatpakResource::arch() const { return m_arch; } QString FlatpakResource::branch() const { return m_branch; } bool FlatpakResource::canExecute() const { return (m_type == DesktopApp && (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable)); } void FlatpakResource::updateFromRef(FlatpakRef* ref) { setArch(QString::fromUtf8(flatpak_ref_get_arch(ref))); setBranch(QString::fromUtf8(flatpak_ref_get_branch(ref))); setCommit(QString::fromUtf8(flatpak_ref_get_commit(ref))); setFlatpakName(QString::fromUtf8(flatpak_ref_get_name(ref))); setType(flatpak_ref_get_kind(ref) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime); } QStringList FlatpakResource::categories() { auto cats = m_appdata.categories(); if (m_appdata.kind() != AppStream::Component::KindAddon) cats.append(QStringLiteral("Application")); return cats; } QString FlatpakResource::comment() { const auto summary = m_appdata.summary(); if (!summary.isEmpty()) { return summary; } return QString(); } QString FlatpakResource::commit() const { return m_commit; } int FlatpakResource::downloadSize() const { return m_downloadSize; } QVariant FlatpakResource::icon() const { QIcon ret; const auto icons = m_appdata.icons(); if (!m_bundledIcon.isNull()) { ret = QIcon(m_bundledIcon); } else if (icons.isEmpty()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } else foreach(const AppStream::Icon &icon, icons) { switch (icon.kind()) { case AppStream::Icon::KindLocal: case AppStream::Icon::KindCached: { const QString path = m_iconPath + icon.url().path(); if (QFileInfo::exists(path)) { ret.addFile(path, icon.size()); } else { const QString altPath = m_iconPath + QStringLiteral("%1x%2/").arg(icon.size().width()).arg(icon.size().height()) + icon.url().path(); if (QFileInfo::exists(altPath)) { ret.addFile(altPath, icon.size()); } } } break; case AppStream::Icon::KindStock: return QIcon::fromTheme(icon.name(), QIcon::fromTheme(QStringLiteral("package-x-generic"))); case AppStream::Icon::KindRemote: { const QString fileName = iconCachePath(icon); if (QFileInfo::exists(fileName)) { ret.addFile(fileName, icon.size()); } break; } case AppStream::Icon::KindUnknown: break; } } if (ret.isNull()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } return ret; } QString FlatpakResource::installedVersion() const { // TODO check if there is actually version available QString version = branch(); if (version.isEmpty()) { version = i18n("Unknown"); } return version; } int FlatpakResource::installedSize() const { return m_installedSize; } bool FlatpakResource::isTechnical() const { return m_type == FlatpakResource::Runtime; } QUrl FlatpakResource::homepage() { return m_appdata.url(AppStream::Component::UrlKindHomepage); } QUrl FlatpakResource::helpURL() { return m_appdata.url(AppStream::Component::UrlKindHelp); } QUrl FlatpakResource::bugURL() { return m_appdata.url(AppStream::Component::UrlKindBugtracker); } QUrl FlatpakResource::donationURL() { return m_appdata.url(AppStream::Component::UrlKindDonation); } QString FlatpakResource::flatpakFileType() const { return m_flatpakFileType; } QString FlatpakResource::flatpakName() const { // If the flatpak name is not known (known only for installed apps), then use // appstream id instead; if (m_flatpakName.isEmpty()) { return m_appdata.id(); } return m_flatpakName; } QString FlatpakResource::license() { return m_appdata.projectLicense(); } QString FlatpakResource::longDescription() { return m_appdata.description(); } QString FlatpakResource::name() const { QString name = m_appdata.name(); if (name.isEmpty()) { name = m_appdata.id(); } if (name.startsWith(QLatin1String("(Nightly) "))) { return name.mid(10); } return name; } QString FlatpakResource::origin() const { return m_origin; } QString FlatpakResource::packageName() const { return flatpakName(); } FlatpakResource::PropertyState FlatpakResource::propertyState(FlatpakResource::PropertyKind kind) const { return m_propertyStates[kind]; } QUrl FlatpakResource::resourceFile() const { return m_resourceFile; } QString FlatpakResource::runtime() const { return m_runtime; } QString FlatpakResource::section() { return QString(); } int FlatpakResource::size() { if (m_state == Installed) { return m_installedSize; } else { return m_downloadSize; } } QString FlatpakResource::sizeDescription() { KFormat f; if (!isInstalled() || canUpgrade()) { if (propertyState(DownloadSize) == NotKnownYet || propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); } else if (propertyState(DownloadSize) == UnknownOrFailed || propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 to download, %2 on disk", f.formatByteSize(downloadSize()), f.formatByteSize(installedSize())); } } else { if (propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); } else if (propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 on disk", f.formatByteSize(installedSize())); } } } AbstractResource::State FlatpakResource::state() { return m_state; } FlatpakResource::ResourceType FlatpakResource::type() const { return m_type; } QString FlatpakResource::typeAsString() const { switch (m_type) { case FlatpakResource::DesktopApp: case FlatpakResource::Source: return QLatin1String("app"); case FlatpakResource::Runtime: return QLatin1String("runtime"); default: return QLatin1String("app"); } } QString FlatpakResource::uniqueId() const { - return installationPath() + QStringLiteral("%1/flatpak/") + origin() + return installationPath() + QStringLiteral("/flatpak/") + origin() + QLatin1Char('/') + typeAsString() + QLatin1Char('/') + m_appdata.id() + QLatin1Char('/') + branch(); } void FlatpakResource::invokeApplication() const { g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; if (!flatpak_installation_launch(m_installation, flatpakName().toUtf8().constData(), arch().toUtf8().constData(), branch().toUtf8().constData(), nullptr, cancellable, &localError)) { qWarning() << "Failed to launch " << m_appdata.name() << ": " << localError->message; } } void FlatpakResource::fetchChangelog() { emit changelogFetched(AppStreamUtils::changelogToHtml(m_appdata)); } void FlatpakResource::fetchScreenshots() { QList thumbnails, screenshots; Q_FOREACH (const AppStream::Screenshot &s, m_appdata.screenshots()) { const QUrl thumbnail = AppStreamUtils::imageOfKind(s.images(), AppStream::Image::KindThumbnail); const QUrl plain = AppStreamUtils::imageOfKind(s.images(), AppStream::Image::KindSource); if (plain.isEmpty()) qWarning() << "invalid screenshot for" << name(); screenshots << plain; thumbnails << (thumbnail.isEmpty() ? plain : thumbnail); } Q_EMIT screenshotsFetched(thumbnails, screenshots); } void FlatpakResource::setArch(const QString &arch) { m_arch = arch; } void FlatpakResource::setBranch(const QString &branch) { m_branch = branch; } void FlatpakResource::setBundledIcon(const QPixmap &pixmap) { m_bundledIcon = pixmap; } void FlatpakResource::setCommit(const QString &commit) { m_commit = commit; } void FlatpakResource::setDownloadSize(int size) { m_downloadSize = size; setPropertyState(DownloadSize, AlreadyKnown); Q_EMIT sizeChanged(); } void FlatpakResource::setFlatpakFileType(const QString &fileType) { m_flatpakFileType = fileType; } void FlatpakResource::setFlatpakName(const QString &name) { m_flatpakName = name; } void FlatpakResource::setIconPath(const QString &path) { m_iconPath = path; } void FlatpakResource::setInstalledSize(int size) { m_installedSize = size; setPropertyState(InstalledSize, AlreadyKnown); Q_EMIT sizeChanged(); } void FlatpakResource::setOrigin(const QString &origin) { m_origin = origin; } void FlatpakResource::setPropertyState(FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState newState) { auto & state = m_propertyStates[kind]; if (state != newState) { state = newState; Q_EMIT propertyStateChanged(kind, newState); } } void FlatpakResource::setResourceFile(const QUrl &url) { m_resourceFile = url; } void FlatpakResource::setRuntime(const QString &runtime) { m_runtime = runtime; setPropertyState(RequiredRuntime, AlreadyKnown); } void FlatpakResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void FlatpakResource::setType(FlatpakResource::ResourceType type) { m_type = type; } QString FlatpakResource::installationPath() const { return installationPath(m_installation); } QString FlatpakResource::installationPath(FlatpakInstallation* flatpakInstallation) { g_autoptr(GFile) path = flatpak_installation_get_path(flatpakInstallation); return QString::fromUtf8(g_file_get_path(path)); } QUrl FlatpakResource::url() const { return m_resourceFile.isEmpty() ? QUrl(QStringLiteral("appstream://") + appstreamId()) : m_resourceFile; } QDate FlatpakResource::releaseDate() const { if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return release.timestamp().date(); } return {}; } diff --git a/libdiscover/backends/FlatpakBackend/org.kde.discover.flatpak.appdata.xml b/libdiscover/backends/FlatpakBackend/org.kde.discover.flatpak.appdata.xml index 930769f6..2db68786 100644 --- a/libdiscover/backends/FlatpakBackend/org.kde.discover.flatpak.appdata.xml +++ b/libdiscover/backends/FlatpakBackend/org.kde.discover.flatpak.appdata.xml @@ -1,80 +1,80 @@ org.kde.discover.flatpak Flatpak backend سَند فلاتپاك Dorsal del Flatpak Dorsal del Flatpak Podpůrná vrstva Flatpak Flatpak-Backend Flatpak backend Motor Flatpak Flatpak-taustaosa Moteur Flatpak Infraestrutura de Flatpak Backend Flatpak Motore Flatpak Flatpak-backend Flatpak-motor Silnik Flatpak Infra-estrutura do Flatpak Модуль для работы с форматом Flatpak Backend Flatpak Zaledje Flatpak Gränssnitt för Flatpak Модуль Flatpak xxFlatpak backendxx Flatpak 后端 Integrates Flatpak applications into Discover يُكامل تطبيقات ”فلاتپاك“ في «استكشف» Integra aplicacions del Flatpak al Discover Integra aplicacions del Flatpak al Discover - Integriert Flatpak-Anwendungen in Discover + Integriert Flatpak-Programme in Discover Integrates Flatpak applications into Discover Integra aplicaciones Flatpak en Discover Yhdistää Flatpak-sovellukset Discoveriin Intègre les applications Flatpak au sein de Discover Integra aplicativos de Flatpak con Discover. Aplikasi Flatpak terintegrasi ke dalam Discover Integra le applicazioni Flatpak in Discover Integreert Flatpak-toepassingen in Discover Integrerer Flatpak-program i Discover Integruje aplikacje Flatpak w Odkrywcy Integra as aplicações do Flatpak no Discover Добавление поддержки формата Flatpak в центр программ Discover Integruje aplikácie Flatpad do Discoveru V Discover vgradi programe Flatpack Integrerar Flatpak-program i Discover Інтеграція програм Flatpak з Discover xxIntegrates Flatpak applications into Discoverxx 将 Flatpak 应用程序集成到发现者中 org.kde.discover.desktop CC0-1.0 GPL-2.0+ Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol González Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez xxAleix Pol Gonzalezxx Aleix Pol Gonzalez system-software-install diff --git a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp index 2f3cdb47..0c89fe28 100644 --- a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp @@ -1,208 +1,215 @@ /*************************************************************************** * 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 "AppPackageKitResource.h" #include #include #include #include #include #include #include #include #include #include #include #include "config-paths.h" AppPackageKitResource::AppPackageKitResource(const AppStream::Component& data, const QString &packageName, PackageKitBackend* parent) : PackageKitResource(packageName, QString(), parent) , m_appdata(data) { Q_ASSERT(data.isValid()); } QString AppPackageKitResource::name() const { - return m_appdata.name(); + QString ret; + if (!m_appdata.extends().isEmpty()) { + auto components = backend()->componentsById(m_appdata.extends().constFirst()); + Q_ASSERT(!components.isEmpty()); + ret = components.constFirst().name() + QStringLiteral(" - ") + m_appdata.name(); + } else + ret = m_appdata.name(); + return ret; } QString AppPackageKitResource::longDescription() { const auto desc = m_appdata.description(); if (!desc.isEmpty()) return desc; return PackageKitResource::longDescription(); } static QIcon componentIcon(const AppStream::Component &comp) { QIcon ret; foreach(const AppStream::Icon &icon, comp.icons()) { QStringList stock; switch(icon.kind()) { case AppStream::Icon::KindLocal: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindCached: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindStock: return QIcon::fromTheme(icon.name(), QIcon::fromTheme(QStringLiteral("package-x-generic"))); default: break; } } if (ret.isNull()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } return ret; } QVariant AppPackageKitResource::icon() const { return componentIcon(m_appdata); } QString AppPackageKitResource::license() { const auto license = m_appdata.projectLicense(); return license.isEmpty() ? PackageKitResource::license() : license; } QStringList AppPackageKitResource::mimetypes() const { return m_appdata.provided(AppStream::Provided::KindMimetype).items(); } QStringList AppPackageKitResource::categories() { auto cats = m_appdata.categories(); if (m_appdata.kind() != AppStream::Component::KindAddon) cats.append(QStringLiteral("Application")); return cats; } QString AppPackageKitResource::comment() { const auto summary = m_appdata.summary(); if (!summary.isEmpty()) return summary; return PackageKitResource::comment(); } QString AppPackageKitResource::appstreamId() const { return m_appdata.id(); } QUrl AppPackageKitResource::homepage() { return m_appdata.url(AppStream::Component::UrlKindHomepage); } QUrl AppPackageKitResource::helpURL() { return m_appdata.url(AppStream::Component::UrlKindHelp); } QUrl AppPackageKitResource::bugURL() { return m_appdata.url(AppStream::Component::UrlKindBugtracker); } QUrl AppPackageKitResource::donationURL() { return m_appdata.url(AppStream::Component::UrlKindDonation); } bool AppPackageKitResource::isTechnical() const { static QString desktop = QString::fromUtf8(qgetenv("XDG_CURRENT_DESKTOP")); const auto desktops = m_appdata.compulsoryForDesktops(); return (!desktops.isEmpty() && !desktops.contains(desktop)) || m_appdata.kind() == AppStream::Component::KindAddon; } void AppPackageKitResource::fetchScreenshots() { QList thumbnails, screenshots; Q_FOREACH (const AppStream::Screenshot &s, m_appdata.screenshots()) { const QUrl thumbnail = AppStreamUtils::imageOfKind(s.images(), AppStream::Image::KindThumbnail); const QUrl plain = AppStreamUtils::imageOfKind(s.images(), AppStream::Image::KindSource); if (plain.isEmpty()) qWarning() << "invalid screenshot for" << name(); screenshots << plain; thumbnails << (thumbnail.isEmpty() ? plain : thumbnail); } Q_EMIT screenshotsFetched(thumbnails, screenshots); } QStringList AppPackageKitResource::allPackageNames() const { return m_appdata.packageNames(); } QList AppPackageKitResource::addonsInformation() { const PackageKitBackend* p = static_cast(parent()); const QVector res = p->extendedBy(m_appdata.id()); QList ret; Q_FOREACH (AppPackageKitResource* r, res) { ret += PackageState(r->appstreamId(), r->name(), r->comment(), r->isInstalled()); } return ret; } QStringList AppPackageKitResource::extends() const { return m_appdata.extends(); } void AppPackageKitResource::fetchChangelog() { emit changelogFetched(AppStreamUtils::changelogToHtml(m_appdata)); } void AppPackageKitResource::invokeApplication() const { const QStringList exes = m_appdata.provided(AppStream::Provided::KindBinary).items(); if (exes.isEmpty()) { const auto servicePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, m_appdata.id()); QProcess::startDetached(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/discover/runservice"), {servicePath}); } else { QProcess::startDetached(exes.constFirst()); } } QDate AppPackageKitResource::releaseDate() const { if (!m_appdata.releases().isEmpty()) { auto release = m_appdata.releases().constFirst(); return release.timestamp().date(); } return {}; } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp index 3c1d3bcc..240e179d 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp @@ -1,637 +1,642 @@ /*************************************************************************** * Copyright © 2012 Aleix Pol Gonzalez * * Copyright © 2013 Lukas Appelhans * * * * 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 "PackageKitBackend.h" #include "PackageKitSourcesBackend.h" #include "PackageKitResource.h" #include "PackageKitUpdater.h" #include "AppPackageKitResource.h" #include "PKTransaction.h" #include "LocalFilePKResource.h" #include "TransactionSet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "config-paths.h" DISCOVER_BACKEND_PLUGIN(PackageKitBackend) QString PackageKitBackend::locateService(const QString &filename) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/")+filename); } PackageKitBackend::PackageKitBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new PackageKitUpdater(this)) , m_refresher(nullptr) , m_isFetching(0) , m_reviews(AppStreamIntegration::global()->reviews()) { QTimer* t = new QTimer(this); connect(t, &QTimer::timeout, this, &PackageKitBackend::refreshDatabase); t->setInterval(60 * 60 * 1000); t->setSingleShot(false); t->start(); m_delayedDetailsFetch.setSingleShot(true); m_delayedDetailsFetch.setInterval(0); connect(&m_delayedDetailsFetch, &QTimer::timeout, this, &PackageKitBackend::performDetailsFetch); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::updatesChanged, this, &PackageKitBackend::fetchUpdates); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::isRunningChanged, this, &PackageKitBackend::checkDaemonRunning); connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); SourcesModel::global()->addSourcesBackend(new PackageKitSourcesBackend(this)); QString error; const bool b = m_appdata.load(&error); reloadPackageList(); if (!b && m_packages.packages.isEmpty()) { qWarning() << "Could not open the AppStream metadata pool" << error; QTimer::singleShot(0, this, [this]() { Q_EMIT passiveMessage(i18n("Please make sure that Appstream is properly set up on your system")); }); } } PackageKitBackend::~PackageKitBackend() = default; bool PackageKitBackend::isFetching() const { return m_isFetching; } void PackageKitBackend::acquireFetching(bool f) { if (f) m_isFetching++; else m_isFetching--; if ((!f && m_isFetching==0) || (f && m_isFetching==1)) { emit fetchingChanged(); } Q_ASSERT(m_isFetching>=0); } void PackageKitBackend::reloadPackageList() { acquireFetching(true); if (m_refresher) { disconnect(m_refresher.data(), &PackageKit::Transaction::finished, this, &PackageKitBackend::reloadPackageList); } const auto components = m_appdata.components(); QStringList neededPackages; neededPackages.reserve(components.size()); foreach(const AppStream::Component& component, components) { if (component.kind() == AppStream::Component::KindFirmware) continue; const auto pkgNames = component.packageNames(); if (pkgNames.isEmpty()) { if (component.kind() == AppStream::Component::KindDesktopApp) { const QString file = locateService(component.desktopId()); if (!file.isEmpty()) { auto trans = PackageKit::Daemon::searchFiles(file); connect(trans, &PackageKit::Transaction::package, this, [trans](PackageKit::Transaction::Info info, const QString &packageID){ if (info == PackageKit::Transaction::InfoInstalled) trans->setProperty("installedPackage", packageID); }); connect(trans, &PackageKit::Transaction::finished, this, [this, trans, component](PackageKit::Transaction::Exit status) { const auto pkgidVal = trans->property("installedPackage"); if (status == PackageKit::Transaction::ExitSuccess && !pkgidVal.isNull()) { const auto pkgid = pkgidVal.toString(); acquireFetching(true); auto res = addComponent(component, {PackageKit::Daemon::packageName(pkgid)}); res->addPackageId(PackageKit::Transaction::InfoInstalled, pkgid, true); acquireFetching(false); } }); continue; } } qDebug() << "no packages for" << component.id(); continue; } neededPackages += pkgNames; addComponent(component, pkgNames); } acquireFetching(false); neededPackages.removeDuplicates(); resolvePackages(neededPackages); } AppPackageKitResource* PackageKitBackend::addComponent(const AppStream::Component& component, const QStringList& pkgNames) { Q_ASSERT(isFetching()); Q_ASSERT(!pkgNames.isEmpty()); const auto res = new AppPackageKitResource(component, pkgNames.at(0), this); m_packages.packages[component.id()] = res; foreach (const QString& pkg, pkgNames) { m_packages.packageToApp[pkg] += component.id(); } foreach (const QString& pkg, component.extends()) { m_packages.extendedBy[pkg] += res; } return res; } void PackageKitBackend::clearPackages(const QStringList& packageNames) { const auto resources = resourcesByPackageNames>(packageNames); for(auto res: resources) { qobject_cast(res)->clearPackageIds(); } } void PackageKitBackend::resolvePackages(const QStringList &packageNames) { PackageKit::Transaction * tArch = PackageKit::Daemon::resolve(packageNames, PackageKit::Transaction::FilterArch); connect(tArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageArch); connect(tArch, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); PackageKit::Transaction * tNotArch = PackageKit::Daemon::resolve(packageNames, PackageKit::Transaction::FilterNotArch); connect(tNotArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageNotArch); connect(tNotArch, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); TransactionSet* merge = new TransactionSet({tArch, tNotArch}); connect(merge, &TransactionSet::allFinished, this, &PackageKitBackend::getPackagesFinished); fetchUpdates(); } void PackageKitBackend::fetchUpdates() { if (m_updater->isProgressing()) return; PackageKit::Transaction * tUpdates = PackageKit::Daemon::getUpdates(); connect(tUpdates, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesFinished); connect(tUpdates, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageToUpdate); connect(tUpdates, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); m_updatesPackageId.clear(); m_hasSecurityUpdates = false; m_updater->setProgressing(true); } void PackageKitBackend::addPackageArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { addPackage(info, packageId, summary, true); } void PackageKitBackend::addPackageNotArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { addPackage(info, packageId, summary, false); } void PackageKitBackend::addPackage(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary, bool arch) { const QString packageName = PackageKit::Daemon::packageName(packageId); QSet r = resourcesByPackageName(packageName); if (r.isEmpty()) { auto pk = new PackageKitResource(packageName, summary, this); r = { pk }; m_packagesToAdd.insert(pk); } foreach(auto res, r) static_cast(res)->addPackageId(info, packageId, arch); } void PackageKitBackend::getPackagesFinished() { for(auto it = m_packages.packages.cbegin(); it != m_packages.packages.cend(); ++it) { auto pkr = qobject_cast(it.value()); if (pkr->packages().isEmpty()) { // qWarning() << "Failed to find package for" << it.key(); m_packagesToDelete += pkr; } } includePackagesToAdd(); } void PackageKitBackend::includePackagesToAdd() { if (m_packagesToAdd.isEmpty() && m_packagesToDelete.isEmpty()) return; acquireFetching(true); foreach(PackageKitResource* res, m_packagesToAdd) { m_packages.packages[res->packageName()] = res; } foreach(PackageKitResource* res, m_packagesToDelete) { const auto pkgs = m_packages.packageToApp.value(res->packageName(), {res->packageName()}); foreach(const auto &pkg, pkgs) { auto res = m_packages.packages.take(pkg); if (res) { emit resourceRemoved(res); res->deleteLater(); } } } m_packagesToAdd.clear(); m_packagesToDelete.clear(); acquireFetching(false); } void PackageKitBackend::transactionError(PackageKit::Transaction::Error, const QString& message) { qWarning() << "Transaction error: " << message << sender(); Q_EMIT passiveMessage(message); } void PackageKitBackend::packageDetails(const PackageKit::Details& details) { const QSet resources = resourcesByPackageName(PackageKit::Daemon::packageName(details.packageId())); if (resources.isEmpty()) qWarning() << "couldn't find package for" << details.packageId(); foreach(AbstractResource* res, resources) { qobject_cast(res)->setDetails(details); } } QSet PackageKitBackend::resourcesByPackageName(const QString& name) const { return resourcesByPackageNames>({name}); } template T PackageKitBackend::resourcesByPackageNames(const QStringList &pkgnames) const { T ret; ret.reserve(pkgnames.size()); for(const QString &name : pkgnames) { const QStringList names = m_packages.packageToApp.value(name, QStringList(name)); foreach(const QString& name, names) { AbstractResource* res = m_packages.packages.value(name); if (res) ret += res; } } return ret; } void PackageKitBackend::checkForUpdates() { refreshDatabase(); } void PackageKitBackend::refreshDatabase() { if (!m_refresher) { acquireFetching(true); m_refresher = PackageKit::Daemon::refreshCache(false); connect(m_refresher.data(), &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); connect(m_refresher.data(), &PackageKit::Transaction::finished, this, [this]() { reloadPackageList(); acquireFetching(false); }); } else { qWarning() << "already resetting"; } } +QList PackageKitBackend::componentsById(const QString& id) const +{ + return m_appdata.componentsById(id); +} + ResultsStream* PackageKitBackend::search(const AbstractResourcesBackend::Filters& filter) { if (!filter.resourceUrl.isEmpty()) { return findResourceByPackageName(filter.resourceUrl); } else if (!filter.extends.isEmpty()) { const auto ext = kTransform>(m_packages.extendedBy[filter.extends], [](AppPackageKitResource* a){ return a; }); return new ResultsStream(QStringLiteral("PackageKitStream-extends"), ext); } else if (filter.search.isEmpty()) { return new ResultsStream(QStringLiteral("PackageKitStream-all"), kFilter>(m_packages.packages, [](AbstractResource* res) { return !res->isTechnical(); })); } else { const QList components = m_appdata.search(filter.search); const QStringList ids = kTransform(components, [](const AppStream::Component& comp) { return comp.id(); }); auto stream = new ResultsStream(QStringLiteral("PackageKitStream-search")); if (!ids.isEmpty()) { const auto resources = resourcesByPackageNames>(ids); QTimer::singleShot(0, this, [stream, resources] () { stream->resourcesFound(resources); }); } PackageKit::Transaction * tArch = PackageKit::Daemon::resolve(filter.search, PackageKit::Transaction::FilterArch); connect(tArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageArch); connect(tArch, &PackageKit::Transaction::package, stream, [stream](PackageKit::Transaction::Info /*info*/, const QString &packageId){ stream->setProperty("packageId", packageId); }); connect(tArch, &PackageKit::Transaction::finished, stream, [stream, ids, this](PackageKit::Transaction::Exit status) { getPackagesFinished(); if (status == PackageKit::Transaction::Exit::ExitSuccess) { const auto packageId = stream->property("packageId"); if (!packageId.isNull()) { const auto res = resourcesByPackageNames>({PackageKit::Daemon::packageName(packageId.toString())}); stream->resourcesFound(kFilter>(res, [ids](AbstractResource* res){ return !ids.contains(res->appstreamId()); })); } } stream->finish(); }, Qt::QueuedConnection); return stream; } } ResultsStream * PackageKitBackend::findResourceByPackageName(const QUrl& url) { AbstractResource* pkg = nullptr; if (url.host().isEmpty()) passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else if (url.scheme() == QLatin1String("appstream")) { static const QMap deprecatedAppstreamIds = { { QStringLiteral("org.kde.krita.desktop"), QStringLiteral("krita.desktop") }, { QStringLiteral("org.kde.digikam.desktop"), QStringLiteral("digikam.desktop") }, { QStringLiteral("org.kde.ktorrent.desktop"), QStringLiteral("ktorrent.desktop") }, { QStringLiteral("org.kde.gcompris.desktop"), QStringLiteral("gcompris.desktop") }, { QStringLiteral("org.kde.kmymoney.desktop"), QStringLiteral("kmymoney.desktop") }, { QStringLiteral("org.kde.kolourpaint.desktop"), QStringLiteral("kolourpaint.desktop") }, { QStringLiteral("org.blender.blender.desktop"), QStringLiteral("blender.desktop") }, }; const auto host = url.host(); if (host.isEmpty()) passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { const auto deprecatedHost = deprecatedAppstreamIds.value(url.host()); //try this as fallback for (auto it = m_packages.packages.constBegin(), itEnd = m_packages.packages.constEnd(); it != itEnd; ++it) { if (it.key().compare(host, Qt::CaseInsensitive) == 0 || it.key().compare(deprecatedHost, Qt::CaseInsensitive) == 0) { pkg = it.value(); break; } } if (!pkg) qWarning() << "could not find" << host << deprecatedHost; } } return new ResultsStream(QStringLiteral("PackageKitStream-url"), pkg ? QVector{pkg} : QVector{}); } bool PackageKitBackend::hasSecurityUpdates() const { return m_hasSecurityUpdates; } int PackageKitBackend::updatesCount() const { return m_updatesPackageId.count(); } Transaction* PackageKitBackend::installApplication(AbstractResource* app, const AddonList& addons) { Transaction* t = nullptr; if(!addons.addonsToInstall().isEmpty()) { QVector appsToInstall; if(!app->isInstalled()) appsToInstall << app; foreach(const QString& toInstall, addons.addonsToInstall()) { appsToInstall += m_packages.packages.value(toInstall); Q_ASSERT(appsToInstall.last()); } t = new PKTransaction(appsToInstall, Transaction::ChangeAddonsRole); } if (!addons.addonsToRemove().isEmpty()) { QVector appsToRemove = kTransform>(addons.addonsToRemove(), [this](const QString& toRemove){ return m_packages.packages.value(toRemove); }); t = new PKTransaction(appsToRemove, Transaction::RemoveRole); } if (!app->isInstalled()) t = installApplication(app); return t; } Transaction* PackageKitBackend::installApplication(AbstractResource* app) { return new PKTransaction({app}, Transaction::InstallRole); } Transaction* PackageKitBackend::removeApplication(AbstractResource* app) { Q_ASSERT(!isFetching()); return new PKTransaction({app}, Transaction::RemoveRole); } QSet PackageKitBackend::upgradeablePackages() const { QSet ret; ret.reserve(m_updatesPackageId.size()); Q_FOREACH (const QString& pkgid, m_updatesPackageId) { const QString pkgname = PackageKit::Daemon::packageName(pkgid); const auto pkgs = resourcesByPackageName(pkgname); if (pkgs.isEmpty()) { qWarning() << "couldn't find resource for" << pkgid; } ret.unite(pkgs); } return ret; } void PackageKitBackend::addPackageToUpdate(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary) { if (info == PackageKit::Transaction::InfoBlocked) { return; } if (info == PackageKit::Transaction::InfoSecurity) m_hasSecurityUpdates = true; m_updatesPackageId += packageId; addPackage(info, packageId, summary, true); } void PackageKitBackend::getUpdatesFinished(PackageKit::Transaction::Exit, uint) { if (!m_updatesPackageId.isEmpty()) { PackageKit::Transaction* transaction = PackageKit::Daemon::getDetails(m_updatesPackageId.toList()); connect(transaction, &PackageKit::Transaction::details, this, &PackageKitBackend::packageDetails); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); connect(transaction, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesDetailsFinished); } m_updater->setProgressing(false); includePackagesToAdd(); emit updatesCountChanged(); } void PackageKitBackend::getUpdatesDetailsFinished(PackageKit::Transaction::Exit exit, uint) { if (exit != PackageKit::Transaction::ExitSuccess) { qWarning() << "Couldn't figure out the updates on PackageKit backend" << exit; } } bool PackageKitBackend::isPackageNameUpgradeable(const PackageKitResource* res) const { return !upgradeablePackageId(res).isEmpty(); } QString PackageKitBackend::upgradeablePackageId(const PackageKitResource* res) const { QString name = res->packageName(); foreach (const QString& pkgid, m_updatesPackageId) { if (PackageKit::Daemon::packageName(pkgid) == name) return pkgid; } return QString(); } void PackageKitBackend::fetchDetails(const QString& pkgid) { if (!m_delayedDetailsFetch.isActive()) { m_delayedDetailsFetch.start(); } m_packageNamesToFetchDetails += pkgid; } void PackageKitBackend::performDetailsFetch() { Q_ASSERT(!m_packageNamesToFetchDetails.isEmpty()); const auto ids = m_packageNamesToFetchDetails.toList(); PackageKit::Transaction* transaction = PackageKit::Daemon::getDetails(ids); connect(transaction, &PackageKit::Transaction::details, this, &PackageKitBackend::packageDetails); connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); QSharedPointer> packageDependencies(new QMap); auto trans = PackageKit::Daemon::dependsOn(ids); connect(trans, &PackageKit::Transaction::package, this, [packageDependencies](PackageKit::Transaction::Info /*info*/, const QString &packageID, const QString &/*summary*/) { (*packageDependencies)[packageID] += 1; }); connect(trans, &PackageKit::Transaction::finished, this, [this, packageDependencies](PackageKit::Transaction::Exit /*status*/) { auto pkgDeps = (*packageDependencies); for (auto it = pkgDeps.constBegin(), itEnd = pkgDeps.constEnd(); it != itEnd; ++it) { const auto resources = resourcesByPackageName(PackageKit::Daemon::packageName(it.key())); for(auto resource : resources) { auto pkres = qobject_cast(resource); pkres->setDependenciesCount(it.value()); } } }); } void PackageKitBackend::checkDaemonRunning() { if (!PackageKit::Daemon::isRunning()) { qWarning() << "PackageKit stopped running!"; } } AbstractBackendUpdater* PackageKitBackend::backendUpdater() const { return m_updater; } QVector PackageKitBackend::extendedBy(const QString& id) const { return m_packages.extendedBy[id]; } AbstractReviewsBackend* PackageKitBackend::reviewsBackend() const { return m_reviews.data(); } AbstractResource * PackageKitBackend::resourceForFile(const QUrl& file) { QMimeDatabase db; const auto mime = db.mimeTypeForUrl(file); if ( mime.inherits(QLatin1String("application/vnd.debian.binary-package")) || mime.inherits(QLatin1String("application/x-rpm")) || mime.inherits(QLatin1String("application/x-tar")) || mime.inherits(QLatin1String("application/x-xz-compressed-tar")) ) { return new LocalFilePKResource(file, this); } return nullptr; } static QString readDistroName() { const QStringList osreleasenames = (QStringList() << QStringLiteral("/etc/os-release") << QStringLiteral("/usr/lib/os-release")); foreach (QString osrelease, osreleasenames) { QFile file(osrelease); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QByteArray line; while (!file.atEnd()) { line = file.readLine().trimmed(); if (line.startsWith("NAME=")) { auto output = line.right(line.length()-5); output = output.replace('\"',""); return QString::fromLocal8Bit(output); } } } } QProcess process; process.setEnvironment({QStringLiteral("LC_ALL=C")}); process.start(QStringLiteral("lsb_release"), {QStringLiteral("-sd")}); process.waitForFinished(); auto output = process.readAll().trimmed(); if (output.startsWith('\"') && output.endsWith('\"')) output = output.mid(1, output.length()-2); return QString::fromLocal8Bit(output); } QString PackageKitBackend::displayName() const { static const QString distro = readDistroName(); return distro; } #include "PackageKitBackend.moc" diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.h b/libdiscover/backends/PackageKitBackend/PackageKitBackend.h index f14403f5..e6252a69 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.h +++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.h @@ -1,125 +1,127 @@ /*************************************************************************** * Copyright © 2012 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 . * ***************************************************************************/ #ifndef PACKAGEKITBACKEND_H #define PACKAGEKITBACKEND_H #include "PackageKitResource.h" #include #include #include #include #include #include #include #include #include class AppPackageKitResource; class PackageKitUpdater; class PKTransaction; class OdrsReviewsBackend; class DISCOVERCOMMON_EXPORT PackageKitBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit PackageKitBackend(QObject* parent = nullptr); ~PackageKitBackend() override; AbstractBackendUpdater* backendUpdater() const override; AbstractReviewsBackend* reviewsBackend() const override; QSet resourcesByPackageName(const QString& name) const; ResultsStream* search(const AbstractResourcesBackend::Filters & search) override; ResultsStream* findResourceByPackageName(const QUrl& search) override; int updatesCount() const override; bool hasSecurityUpdates() const override; Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; Transaction* removeApplication(AbstractResource* app) override; bool isValid() const override { return true; } QSet upgradeablePackages() const; bool isFetching() const override; bool isPackageNameUpgradeable(const PackageKitResource* res) const; QString upgradeablePackageId(const PackageKitResource* res) const; QVector extendedBy(const QString& id) const; void clearPackages(const QStringList &packageNames); void resolvePackages(const QStringList &packageNames); void fetchDetails(const QString& pkgid); AbstractResource * resourceForFile(const QUrl & ) override; void checkForUpdates() override; QString displayName() const override; bool hasApplications() const override { return true; } static QString locateService(const QString &filename); + QList componentsById(const QString &id) const; + public Q_SLOTS: void reloadPackageList(); void refreshDatabase(); private Q_SLOTS: void getPackagesFinished(); void addPackage(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary, bool arch); void addPackageArch(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary); void addPackageNotArch(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary); void packageDetails(const PackageKit::Details& details); void transactionError(PackageKit::Transaction::Error, const QString& message); void addPackageToUpdate(PackageKit::Transaction::Info, const QString& pkgid, const QString& summary); void getUpdatesFinished(PackageKit::Transaction::Exit,uint); void getUpdatesDetailsFinished(PackageKit::Transaction::Exit,uint); private: template T resourcesByPackageNames(const QStringList& names) const; void fetchUpdates(); void checkDaemonRunning(); void acquireFetching(bool f); void includePackagesToAdd(); void performDetailsFetch(); AppPackageKitResource* addComponent(const AppStream::Component& component, const QStringList& pkgNames); AppStream::Pool m_appdata; PackageKitUpdater* m_updater; QPointer m_refresher; int m_isFetching; QSet m_updatesPackageId; bool m_hasSecurityUpdates = false; QSet m_packagesToAdd; QSet m_packagesToDelete; struct Packages { QHash packages; QHash packageToApp; QHash> extendedBy; void clear() { *this = {}; } }; QTimer m_delayedDetailsFetch; QSet m_packageNamesToFetchDetails; Packages m_packages; QSharedPointer m_reviews; }; #endif // PACKAGEKITBACKEND_H diff --git a/libdiscover/backends/PackageKitBackend/org.kde.discover.packagekit.appdata.xml b/libdiscover/backends/PackageKitBackend/org.kde.discover.packagekit.appdata.xml index 97cbd8e3..c329f728 100644 --- a/libdiscover/backends/PackageKitBackend/org.kde.discover.packagekit.appdata.xml +++ b/libdiscover/backends/PackageKitBackend/org.kde.discover.packagekit.appdata.xml @@ -1,80 +1,80 @@ org.kde.discover.packagekit PackageKit backend سَند عدّة الحزم Dorsal del PackageKit Dorsal del PackageKit Podpůrná vrstva PackageKit PackageKit-Backend PackageKit backend Motor PackageKit PackageKit-taustaosa Moteur PackageKit Infraestrutura de PackageKit Backend PackageKit Motore PackageKit PackageKit-backend PackageKit-motor Silnik PackageKit Infra-estrutura do PackageKit Модуль поддержки PackageKit Backend PackageKit Zaledje PackageKit Gränssnitt för PackageKit Модуль PackageKit xxPackageKit backendxx PackageKit 后端 Integrates distribution applications into Discover يُكامل تطبيقات ”عُدّة الحزم“ في «استكشف» Integra aplicacions de distribució al Discover Integra aplicacions de distribució al Discover - Integriert Distributions-Anwendungen in Discover + Integriert Distributions-Programme in Discover Integrates distribution applications into Discover Integra aplicaciones de la distribución en Discover Yhdistää jakelun sovellukset Discoveriin Intègre les applications de distribution au sein de Discover Integra aplicativos da distribución con Discover. Aplikasi distribusi terintegrasi ke dalam Discover Integra le applicazioni della distribuzione in Discover Integreert distributie-toepassingen in Discover Integrerer distribusjonsprogram i Discover Integruje aplikacje dystrybucji w Odkrywcy Integra as aplicações da distribuição no Discover Добавление поддержки приложений из дистрибутива ОС в центр программ Discover Integruje distribučné aplikácie do Discovera V Discover vgradi programe distribucije Integrerar distributionsprogram i Discover Інтегрує програми зі сховищ дистрибутива до Discover xxIntegrates distribution applications into Discoverxx 将发行版应用程序集成到发现者中 org.kde.discover.desktop CC0-1.0 GPL-2.0+ Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol González Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez xxAleix Pol Gonzalezxx Aleix Pol Gonzalez system-software-install diff --git a/libdiscover/backends/SnapBackend/org.kde.discover.snap.appdata.xml b/libdiscover/backends/SnapBackend/org.kde.discover.snap.appdata.xml index 2feb2d11..35700049 100644 --- a/libdiscover/backends/SnapBackend/org.kde.discover.snap.appdata.xml +++ b/libdiscover/backends/SnapBackend/org.kde.discover.snap.appdata.xml @@ -1,80 +1,80 @@ org.kde.discover.snap Snap backend سَند سناپ Dorsal de l'Snap Dorsal de l'Snap Podpůrná vrstva Snap Snap-Backend Snap backend Motor Snap Snap-taustaosa Moteur Snap Infraestrutura de Snap Backend Snap Motore Snap Snap-backend Snap-motor Silnik Snap Infra-estrutura do Snap Модуль поддержки формата Snap Backend Snap Zaledje Snap Gränssnitt för Snap Модуль Snap xxSnap backendxx Snap 后端 Integrates Snap applications into Discover يُكامل تطبيقات ”سناپ“ في «استكشف» Integra aplicacions de l'Snap al Discover Integra aplicacions de l'Snap al Discover - Integriert Snap-Anwendungen in Discover + Integriert Snap-Programme in Discover Integrates Snap applications into Discover Integra aplicaciones Snap en Discover Yhdistää Snap-sovellukset Discoveriin Intègre les applications Snap au sein de Discover Integra aplicativos de Snap con Discover. Aplikasi Snap terintegrasi ke dalam Discover Integra le applicazioni Snap in Discover Integreert Snap-toepassingen in Discover Integrerer Snap-program i Discover Integruje aplikacje Snap w Odkrywcy Integra as aplicações do Snap no Discover Добавление поддержки формата Snap в центр программ Discover Integruje aplikácie Snap do Discoveru V Discover vgradi programe Snap Integrerar Snap-program i Discover Інтегрує програми Snap до Discover xxIntegrates Snap applications into Discoverxx 将 Snap 应用程序集成到发现者中 org.kde.discover.desktop CC0-1.0 GPL-2.0+ Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol González Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez Aleix Pol Gonzalez xxAleix Pol Gonzalezxx Aleix Pol Gonzalez system-software-install diff --git a/libdiscover/resources/ResourcesUpdatesModel.cpp b/libdiscover/resources/ResourcesUpdatesModel.cpp index 12d893a0..fdd01eaf 100644 --- a/libdiscover/resources/ResourcesUpdatesModel.cpp +++ b/libdiscover/resources/ResourcesUpdatesModel.cpp @@ -1,263 +1,264 @@ /*************************************************************************** * Copyright © 2012 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 "ResourcesUpdatesModel.h" #include #include #include "ResourcesModel.h" #include "AbstractBackendUpdater.h" #include "AbstractResource.h" #include "utils.h" #include #include #include #include class UpdateTransaction : public Transaction { Q_OBJECT public: UpdateTransaction(ResourcesUpdatesModel* /*parent*/, const QVector &updaters) : Transaction(nullptr, nullptr, Transaction::InstallRole) , m_allUpdaters(updaters) { bool cancelable = false; foreach(auto updater, m_allUpdaters) { connect(updater, &AbstractBackendUpdater::progressingChanged, this, &UpdateTransaction::slotProgressingChanged); connect(updater, &AbstractBackendUpdater::progressChanged, this, &UpdateTransaction::slotUpdateProgress); connect(updater, &AbstractBackendUpdater::proceedRequest, this, &UpdateTransaction::processProceedRequest); + connect(updater, &AbstractBackendUpdater::cancelableChanged, this, [this](bool cancelable){ if (cancelable) setCancellable(true); }); cancelable |= updater->isCancelable(); } setCancellable(cancelable); } void processProceedRequest(const QString &title, const QString& message) { m_updatersWaitingForFeedback += qobject_cast(sender()); Q_EMIT proceedRequest(title, message); } void cancel() override { QVector toCancel = m_updatersWaitingForFeedback.isEmpty() ? m_allUpdaters : m_updatersWaitingForFeedback; - foreach(auto updater, m_updatersWaitingForFeedback) { + foreach(auto updater, toCancel) { updater->cancel(); } } void proceed() override { m_updatersWaitingForFeedback.takeFirst()->proceed(); } bool isProgressing() const { bool progressing = false; foreach(AbstractBackendUpdater* upd, m_allUpdaters) { progressing |= upd->isProgressing(); } return progressing; } void slotProgressingChanged() { if (status() < DoneStatus && !isProgressing()) { setStatus(Transaction::DoneStatus); Q_EMIT finished(); deleteLater(); } } void slotUpdateProgress() { qreal total = 0; foreach(AbstractBackendUpdater* updater, m_allUpdaters) { total += updater->progress(); } setProgress(total / m_allUpdaters.count()); } QVariant icon() const override { return QStringLiteral("update-low"); } QString name() const override { return i18n("Update"); } Q_SIGNALS: void finished(); private: QVector m_updatersWaitingForFeedback; const QVector m_allUpdaters; }; ResourcesUpdatesModel::ResourcesUpdatesModel(QObject* parent) : QStandardItemModel(parent) , m_lastIsProgressing(false) , m_transaction(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesUpdatesModel::init); init(); } void ResourcesUpdatesModel::init() { const QVector backends = ResourcesModel::global()->backends(); m_lastIsProgressing = false; foreach(AbstractResourcesBackend* b, backends) { AbstractBackendUpdater* updater = b->backendUpdater(); if(updater && !m_updaters.contains(updater)) { connect(updater, &AbstractBackendUpdater::statusMessageChanged, this, &ResourcesUpdatesModel::message); connect(updater, &AbstractBackendUpdater::statusDetailChanged, this, &ResourcesUpdatesModel::message); connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &ResourcesUpdatesModel::downloadSpeedChanged); connect(updater, &AbstractBackendUpdater::resourceProgressed, this, &ResourcesUpdatesModel::resourceProgressed); connect(updater, &AbstractBackendUpdater::passiveMessage, this, &ResourcesUpdatesModel::passiveMessage); connect(updater, &AbstractBackendUpdater::destroyed, this, &ResourcesUpdatesModel::updaterDestroyed); m_updaters += updater; m_lastIsProgressing |= updater->isProgressing(); } } auto tm = TransactionModel::global(); foreach(auto t, tm->transactions()) { auto updateTransaction = qobject_cast(t); if (updateTransaction) { setTransaction(updateTransaction); } } } void ResourcesUpdatesModel::updaterDestroyed(QObject* obj) { m_updaters.removeAll(static_cast(obj)); } void ResourcesUpdatesModel::message(const QString& msg) { if(msg.isEmpty()) return; appendRow(new QStandardItem(msg)); } void ResourcesUpdatesModel::prepare() { if(isProgressing()) { qWarning() << "trying to set up a running instance"; return; } foreach(AbstractBackendUpdater* upd, m_updaters) { upd->prepare(); } } void ResourcesUpdatesModel::updateAll() { if (!m_updaters.isEmpty()) { delete m_transaction; const auto updaters = kFilter>(m_updaters, [](AbstractBackendUpdater* u) {return u->hasUpdates(); }); m_transaction = new UpdateTransaction(this, updaters); setTransaction(m_transaction); TransactionModel::global()->addTransaction(m_transaction); Q_FOREACH (AbstractBackendUpdater* upd, updaters) { QMetaObject::invokeMethod(upd, "start", Qt::QueuedConnection); } } } bool ResourcesUpdatesModel::isProgressing() const { return m_transaction && m_transaction->status() < Transaction::DoneStatus; } QList ResourcesUpdatesModel::toUpdate() const { QList ret; foreach(AbstractBackendUpdater* upd, m_updaters) { ret += upd->toUpdate(); } return ret; } void ResourcesUpdatesModel::addResources(const QList& resources) { QHash > sortedResources; foreach(AbstractResource* res, resources) { sortedResources[res->backend()] += res; } for(auto it=sortedResources.constBegin(), itEnd=sortedResources.constEnd(); it!=itEnd; ++it) { it.key()->backendUpdater()->addResources(*it); } } void ResourcesUpdatesModel::removeResources(const QList< AbstractResource* >& resources) { QHash > sortedResources; foreach(AbstractResource* res, resources) { sortedResources[res->backend()] += res; } for(auto it=sortedResources.constBegin(), itEnd=sortedResources.constEnd(); it!=itEnd; ++it) { it.key()->backendUpdater()->removeResources(*it); } } QDateTime ResourcesUpdatesModel::lastUpdate() const { QDateTime ret; foreach(AbstractBackendUpdater* upd, m_updaters) { QDateTime current = upd->lastUpdate(); if(!ret.isValid() || (current.isValid() && current>ret)) { ret = current; } } return ret; } double ResourcesUpdatesModel::updateSize() const { double ret = 0.; for(AbstractBackendUpdater* upd: m_updaters) { ret += upd->updateSize(); } return ret; } qint64 ResourcesUpdatesModel::secsToLastUpdate() const { return lastUpdate().secsTo(QDateTime::currentDateTime()); } void ResourcesUpdatesModel::setTransaction(UpdateTransaction* transaction) { m_transaction = transaction; connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::finished); connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::progressingChanged); Q_EMIT progressingChanged(); } Transaction* ResourcesUpdatesModel::transaction() const { return m_transaction.data(); } #include "ResourcesUpdatesModel.moc" diff --git a/libdiscover/resources/StandardBackendUpdater.cpp b/libdiscover/resources/StandardBackendUpdater.cpp index 0b5cd740..fd6a979c 100644 --- a/libdiscover/resources/StandardBackendUpdater.cpp +++ b/libdiscover/resources/StandardBackendUpdater.cpp @@ -1,228 +1,238 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser 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. */ #include #include #include #include "ResourcesModel.h" #include #include #include #include #include #include #include StandardBackendUpdater::StandardBackendUpdater(AbstractResourcesBackend* parent) : AbstractBackendUpdater(parent) , m_backend(parent) , m_settingUp(false) , m_progress(0) , m_lastUpdate(QDateTime()) { connect(m_backend, &AbstractResourcesBackend::fetchingChanged, this, &StandardBackendUpdater::refreshUpdateable); connect(m_backend, &AbstractResourcesBackend::resourcesChanged, this, &StandardBackendUpdater::resourcesChanged); connect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, [this](AbstractResource* resource){ m_upgradeable.remove(resource); m_toUpgrade.remove(resource); }); connect(TransactionModel::global(), &TransactionModel::transactionRemoved, this, &StandardBackendUpdater::transactionRemoved); connect(TransactionModel::global(), &TransactionModel::transactionAdded, this, &StandardBackendUpdater::transactionAdded); m_timer.setSingleShot(true); m_timer.setInterval(10); connect(&m_timer, &QTimer::timeout, this, &StandardBackendUpdater::refreshUpdateable); } void StandardBackendUpdater::resourcesChanged(AbstractResource* res, const QVector& props) { if (props.contains("state") && (res->state() == AbstractResource::Upgradeable || m_upgradeable.contains(res))) m_timer.start(); } bool StandardBackendUpdater::hasUpdates() const { return !m_upgradeable.isEmpty(); } void StandardBackendUpdater::start() { m_settingUp = true; emit progressingChanged(true); setProgress(0); auto upgradeList = m_toUpgrade.toList(); qSort(upgradeList.begin(), upgradeList.end(), [](const AbstractResource* a, const AbstractResource* b){ return a->name() < b->name(); }); + const bool couldCancel = m_canCancel; foreach(AbstractResource* res, upgradeList) { m_pendingResources += res; auto t = m_backend->installApplication(res); t->setVisible(false); + connect(this, &StandardBackendUpdater::cancelTransaction, t, &Transaction::cancel); TransactionModel::global()->addTransaction(t); + m_canCancel |= t->isCancellable(); + } + if (m_canCancel != couldCancel) { + Q_EMIT cancelableChanged(m_canCancel); } m_settingUp = false; if(m_pendingResources.isEmpty()) { cleanup(); } else { setProgress(1); } } +void StandardBackendUpdater::cancel() +{ + Q_EMIT cancelTransaction(); +} + void StandardBackendUpdater::transactionAdded(Transaction* newTransaction) { if (!m_pendingResources.contains(newTransaction->resource())) return; connect(newTransaction, &Transaction::progressChanged, this, &StandardBackendUpdater::transactionProgressChanged); } void StandardBackendUpdater::transactionProgressChanged(int percentage) { Transaction* t = qobject_cast(sender()); Q_EMIT resourceProgressed(t->resource(), percentage); } void StandardBackendUpdater::transactionRemoved(Transaction* t) { const bool fromOurBackend = t->resource() && t->resource()->backend()==m_backend; if (!fromOurBackend) { return; } const bool found = fromOurBackend && m_pendingResources.remove(t->resource()); if(found && !m_settingUp) { qreal p = 1-(qreal(m_pendingResources.size())/m_toUpgrade.size()); setProgress(100*p); if(m_pendingResources.isEmpty()) { cleanup(); } } refreshUpdateable(); } void StandardBackendUpdater::refreshUpdateable() { if (m_backend->isFetching() || !m_backend->isValid()) { return; } if (isProgressing()) { m_timer.start(1000); return; } m_settingUp = true; Q_EMIT progressingChanged(true); AbstractResourcesBackend::Filters f; f.state = AbstractResource::Upgradeable; m_upgradeable.clear(); auto r = m_backend->search(f); connect(r, &ResultsStream::resourcesFound, this, [this](const QVector &resources){ for(auto res : resources) if (res->state() == AbstractResource::Upgradeable) m_upgradeable.insert(res); }); connect(r, &ResultsStream::destroyed, this, [this](){ m_settingUp = false; Q_EMIT updatesCountChanged(updatesCount()); Q_EMIT progressingChanged(false); }); } qreal StandardBackendUpdater::progress() const { return m_progress; } void StandardBackendUpdater::setProgress(qreal p) { if(p>m_progress || p<0) { m_progress = p; emit progressChanged(p); } } void StandardBackendUpdater::prepare() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade = m_upgradeable; } int StandardBackendUpdater::updatesCount() const { return m_upgradeable.count(); } void StandardBackendUpdater::addResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); m_toUpgrade += apps.toSet(); } void StandardBackendUpdater::removeResources(const QList< AbstractResource* >& apps) { Q_ASSERT(m_upgradeable.contains(apps.toSet())); Q_ASSERT(m_toUpgrade.contains(apps.toSet())); m_toUpgrade -= apps.toSet(); } void StandardBackendUpdater::cleanup() { m_lastUpdate = QDateTime::currentDateTime(); m_toUpgrade.clear(); refreshUpdateable(); emit progressingChanged(false); } QList StandardBackendUpdater::toUpdate() const { return m_toUpgrade.toList(); } bool StandardBackendUpdater::isMarked(AbstractResource* res) const { return m_toUpgrade.contains(res); } QDateTime StandardBackendUpdater::lastUpdate() const { return m_lastUpdate; } bool StandardBackendUpdater::isCancelable() const { - //We don't really know when we can cancel, so we never let - return false; + return m_canCancel; } bool StandardBackendUpdater::isProgressing() const { return m_settingUp || !m_pendingResources.isEmpty(); } double StandardBackendUpdater::updateSize() const { double ret = 0.; for(AbstractResource* res: m_toUpgrade) { ret += res->size(); } return ret; } diff --git a/libdiscover/resources/StandardBackendUpdater.h b/libdiscover/resources/StandardBackendUpdater.h index 0315e4c3..a15ea196 100644 --- a/libdiscover/resources/StandardBackendUpdater.h +++ b/libdiscover/resources/StandardBackendUpdater.h @@ -1,79 +1,82 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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 Library/Lesser 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. */ #ifndef STANDARDBACKENDUPDATER_H #define STANDARDBACKENDUPDATER_H #include "discovercommon_export.h" #include #include "AbstractResourcesBackend.h" #include #include #include class AbstractResourcesBackend; class DISCOVERCOMMON_EXPORT StandardBackendUpdater : public AbstractBackendUpdater { Q_OBJECT Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) public: explicit StandardBackendUpdater(AbstractResourcesBackend* parent = nullptr); bool hasUpdates() const override; qreal progress() const override; void start() override; QList toUpdate() const override; void addResources(const QList& apps) override; void removeResources(const QList& apps) override; void prepare() override; QDateTime lastUpdate() const override; bool isCancelable() const override; bool isProgressing() const override; bool isMarked(AbstractResource* res) const override; double updateSize() const override; void setProgress(qreal p); int updatesCount() const; + void cancel() override; Q_SIGNALS: + void cancelTransaction(); void updatesCountChanged(int updatesCount); public Q_SLOTS: void transactionRemoved(Transaction* t); void cleanup(); private: void resourcesChanged(AbstractResource* res, const QVector& props); void refreshUpdateable(); void transactionAdded(Transaction* newTransaction); void transactionProgressChanged(int percentage); QSet m_toUpgrade; QSet m_upgradeable; AbstractResourcesBackend * const m_backend; QSet m_pendingResources; bool m_settingUp; qreal m_progress; QDateTime m_lastUpdate; QTimer m_timer; + bool m_canCancel = false; }; #endif // STANDARDBACKENDUPDATER_H