diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index 8c86ce5b..9b1a433f 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,333 +1,345 @@ import QtQuick.Controls 2.3 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.3 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 && !ResourcesModel.isFetching onTriggered: resourcesUpdatesModel.updateAll() } footer: ScrollView { id: scv width: parent.width height: visible ? Kirigami.Units.gridUnit * 10 : 0 visible: log.contents.length > 0 TextArea { readOnly: true text: log.contents cursorPosition: text.length - 1 font.family: "monospace" ReadFile { id: log filter: ".*ALPM-SCRIPTLET\\] .*" path: "/var/log/pacman.log" } } } 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: ToolBar { Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.inherit: false visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates RowLayout { anchors.fill: parent enabled: page.currentAction.enabled CheckBox { Layout.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing enabled: !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching tristate: true checkState: updateModel.toUpdateCount === 0 ? Qt.Unchecked : updateModel.toUpdateCount === updateModel.totalUpdatesCount ? Qt.Checked : Qt.PartiallyChecked onClicked: { if (updateModel.toUpdateCount === 0) updateModel.checkAll() else updateModel.uncheckAll() } } LabelBackground { text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")" } Label { text: i18n("updates selected") } LabelBackground { id: unselectedItem text: page.unselected visible: page.unselected>0 } Label { text: i18n("updates not selected") visible: unselectedItem.visible } Item { Layout.fillWidth: true } } } supportsRefreshing: true onRefreshingChanged: { showPassiveNotification("Fetching updates...") ResourcesModel.updateAction.triggered() refreshing = false } ListView { id: updatesView currentIndex: -1 displaced: Transition { YAnimator { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } 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: Kirigami.Units.gridUnit * 12 height: width } Item { visible: page.footerLabel === "" height: Kirigami.Units.gridUnit width: 1 } } model: QSortFilterProxyModel { sourceModel: updateModel sortRole: UpdateModel.SectionResourceProgressRole } section { property: "section" delegate: Kirigami.Heading { x: Kirigami.Units.gridUnit - level: 3 + level: 2 text: section height: implicitHeight + Kirigami.Units.largeSpacing * 2 } } delegate: Kirigami.AbstractListItem { backgroundColor: Kirigami.Theme.backgroundColor 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.fetchUpdateDetails(index) } RowLayout { Layout.fillWidth: true Layout.fillHeight: true CheckBox { id: itemChecked Layout.leftMargin: Kirigami.Units.gridUnit Layout.alignment: Qt.AlignVCenter checked: model.checked == Qt.Checked onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked) enabled: !resourcesUpdatesModel.isProgressing } Kirigami.Icon { Layout.fillHeight: true Layout.preferredWidth: height source: decoration smooth: true } - Label { - Layout.fillWidth: true - text: i18n("%1 (%2)", display, version) - elide: Text.ElideRight + ColumnLayout { + + // App name + Kirigami.Heading { + Layout.fillWidth: true + text: i18n("%1", display) + level: 3 + elide: Text.ElideRight + } + + // Old and new version numbers + Label { + Layout.fillWidth: true + elide: Text.ElideRight + text: i18n("%1 ⮕ %2", installedVersion, availableVersion) + } } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: size progress: resourceProgress/100 } } Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 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" : resourcesUpdatesModel.isProgressing ? "progressing" : ResourcesModel.isFetching ? "fetching" : resourcesUpdatesModel.needsReboot ? "reboot" : 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", "Checking 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: "reboot" PropertyChanges { target: page; title: i18nc("@info", "The system requires a reboot") } PropertyChanges { target: page; footerLabel: i18nc("@info", "Reboot") } }, 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/UpdateItem.cpp b/libdiscover/UpdateModel/UpdateItem.cpp index b9b492bd..c8e5fac9 100644 --- a/libdiscover/UpdateModel/UpdateItem.cpp +++ b/libdiscover/UpdateModel/UpdateItem.cpp @@ -1,93 +1,104 @@ /*************************************************************************** * 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 "UpdateItem.h" #include #include #include #include #include #include "libdiscover_debug.h" UpdateItem::UpdateItem(AbstractResource *app) : m_app(app) , m_progress(0) { } UpdateItem::~UpdateItem() { } AbstractResource *UpdateItem::app() const { return m_app; } QString UpdateItem::name() const { return m_app->name(); } +// Deprecated; use availableVersion() instead QString UpdateItem::version() const { return m_app->availableVersion(); } +QString UpdateItem::availableVersion() const +{ + return m_app->availableVersion(); +} + +QString UpdateItem::installedVersion() const +{ + return m_app->installedVersion(); +} + QVariant UpdateItem::icon() const { return m_app->icon(); } qint64 UpdateItem::size() const { return m_app->size(); } static bool isMarked(AbstractResource* res) { return res->backend()->backendUpdater()->isMarked(res); } Qt::CheckState UpdateItem::checked() const { return isMarked(app()) ? Qt::Checked : Qt::Unchecked; } qreal UpdateItem::progress() const { return m_progress; } void UpdateItem::setProgress(qreal progress) { m_progress = progress; } QString UpdateItem::changelog() const { return m_changelog; } void UpdateItem::setChangelog(const QString& changelog) { m_changelog = changelog; } diff --git a/libdiscover/UpdateModel/UpdateItem.h b/libdiscover/UpdateModel/UpdateItem.h index d7d7ef11..55b72958 100644 --- a/libdiscover/UpdateModel/UpdateItem.h +++ b/libdiscover/UpdateModel/UpdateItem.h @@ -1,63 +1,65 @@ /*************************************************************************** * 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 UPDATEITEM_H #define UPDATEITEM_H // Qt includes #include #include #include "discovercommon_export.h" #include class AbstractResource; class DISCOVERCOMMON_EXPORT UpdateItem { public: explicit UpdateItem(AbstractResource *app); ~UpdateItem(); void setProgress(qreal progress); qreal progress() const; QString changelog() const; void setChangelog(const QString &changelog); AbstractResource *app() const; QString name() const; - QString version() const; + QString version() const; // Deprecated; use availableVersion() instead + QString availableVersion() const; + QString installedVersion() const; QVariant icon() const; qint64 size() const; Qt::CheckState checked() const; AbstractResource* resource() const { return m_app; } private: AbstractResource * const m_app; const QString m_categoryName; const QIcon m_categoryIcon; qreal m_progress; QString m_changelog; }; #endif // UPDATEITEM_H diff --git a/libdiscover/UpdateModel/UpdateModel.cpp b/libdiscover/UpdateModel/UpdateModel.cpp index b21e6ab6..7a3ef61b 100644 --- a/libdiscover/UpdateModel/UpdateModel.cpp +++ b/libdiscover/UpdateModel/UpdateModel.cpp @@ -1,354 +1,357 @@ /*************************************************************************** * 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 #include "libdiscover_debug.h" // KDE includes #include #include // Own includes #include "UpdateItem.h" #include #include #include UpdateModel::UpdateModel(QObject *parent) : QAbstractListModel(parent) , m_updateSizeTimer(new QTimer(this)) , 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); m_updateSizeTimer->setInterval(100); m_updateSizeTimer->setSingleShot(true); connect(m_updateSizeTimer, &QTimer::timeout, this, &UpdateModel::updateSizeChanged); } UpdateModel::~UpdateModel() { qDeleteAll(m_updateItems); m_updateItems.clear(); } QHash UpdateModel::roleNames() const { return QAbstractItemModel::roleNames().unite({ { Qt::CheckStateRole, "checked" }, { ResourceProgressRole, "resourceProgress" }, { ResourceRole, "resource" }, { SizeRole, "size" }, - { VersionRole, "version" }, { SectionRole, "section" }, - { ChangelogRole, "changelog" } + { ChangelogRole, "changelog" }, + { InstalledVersionRole, "installedVersion" }, + { AvailableVersionRole, "availableVersion" } } ); } 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, SectionResourceProgressRole }); } void UpdateModel::activityChanged() { if (m_updates) { if (!m_updates->isProgressing()) { m_updates->prepare(); setResources(m_updates->toUpdate()); for(auto item : qAsConst(m_updateItems)) { item->setProgress(0); } } else 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 InstalledVersionRole: + return item->installedVersion(); + case AvailableVersionRole: + return item->availableVersion(); 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: { static const QString appUpdatesSection = i18nc("@item:inlistbox", "Application Updates"); static const QString systemUpdateSection = i18nc("@item:inlistbox", "System Updates"); static const QString addonsSection = i18nc("@item:inlistbox", "Addons"); switch(item->resource()->type()) { case AbstractResource::Application: return appUpdatesSection; case AbstractResource::Technical: return systemUpdateSection; case AbstractResource::Addon: return addonsSection; } Q_UNREACHABLE(); } case SectionResourceProgressRole: return (100-item->progress()) + (101 * item->resource()->type()); 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); //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::fetchUpdateDetails(int row) { UpdateItem *item = itemFromIndex(index(row, 0)); Q_ASSERT(item); if (!item) return; item->app()->fetchUpdateDetails(); } 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& resources) { if (resources == m_resources) { return; } m_resources = resources; beginResetModel(); qDeleteAll(m_updateItems); m_updateItems.clear(); QVector appItems, systemItems, addonItems; foreach(AbstractResource* res, resources) { connect(res, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection); UpdateItem *updateItem = new UpdateItem(res); switch(res->type()) { case AbstractResource::Technical: systemItems += updateItem; break; case AbstractResource::Application: appItems += updateItem; break; case AbstractResource::Addon: addonItems += updateItem; break; } } const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) { return a->name() < b->name(); }; qSort(appItems.begin(), appItems.end(), sortUpdateItems); qSort(systemItems.begin(), systemItems.end(), sortUpdateItems); qSort(addonItems.begin(), addonItems.end(), sortUpdateItems); m_updateItems = (QVector() << appItems << addonItems << 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; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += item->checked() != Qt::Unchecked ? 1 : 0; } return ret; } int UpdateModel::totalUpdatesCount() const { int ret = 0; QSet packages; foreach (UpdateItem* item, m_updateItems) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } 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")) - Q_EMIT dataChanged(index, index, {SizeRole, VersionRole}); + Q_EMIT dataChanged(index, index, {SizeRole, AvailableVersionRole}); else if (properties.contains("size")) { Q_EMIT dataChanged(index, index, {SizeRole}); m_updateSizeTimer->start(); } } void UpdateModel::checkAll() { for(int i=0, c=rowCount(); i * * * * 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 QTimer; 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, + SizeRole = Qt::UserRole + 1, ResourceRole, ResourceProgressRole, SectionResourceProgressRole, ChangelogRole, - SectionRole + SectionRole, + InstalledVersionRole, + AvailableVersionRole }; Q_ENUM(Roles) 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; ///packages marked to upgrade int toUpdateCount() const; Q_SCRIPTABLE void fetchUpdateDetails(int row); QString updateSize() const; ResourcesUpdatesModel* backend() const; public Q_SLOTS: void checkAll(); void uncheckAll(); 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(); QTimer* const m_updateSizeTimer; QVector m_updateItems; ResourcesUpdatesModel* m_updates; QList m_resources; }; #endif // UPDATEMODEL_H diff --git a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp index 92575e86..174691fa 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitResource.cpp @@ -1,337 +1,336 @@ /*************************************************************************** * 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 "PackageKitResource.h" #include "PackageKitBackend.h" #include "PackageKitMessages.h" #include #include #include #include #include #if defined(WITH_MARKDOWN) extern "C" { #include } #endif const QStringList PackageKitResource::m_objects({ QStringLiteral("qrc:/qml/DependenciesButton.qml") }); PackageKitResource::PackageKitResource(QString packageName, QString summary, PackageKitBackend* parent) : AbstractResource(parent) , m_summary(std::move(summary)) , m_name(std::move(packageName)) { setObjectName(m_name); connect(this, &PackageKitResource::dependenciesFound, this, [this](const QJsonObject& obj) { setDependenciesCount(obj.size()); }); } QString PackageKitResource::name() const { return m_name; } QString PackageKitResource::packageName() const { return m_name; } QStringList PackageKitResource::allPackageNames() const { return { m_name }; } QString PackageKitResource::availablePackageId() const { //First we check if it's upgradeable and use this version to display const QString pkgid = backend()->upgradeablePackageId(this); if (!pkgid.isEmpty()) return pkgid; QMap::const_iterator it = m_packages.constFind(PackageKit::Transaction::InfoAvailable); if (it != m_packages.constEnd()) return it->last(); return installedPackageId(); } QString PackageKitResource::installedPackageId() const { const auto installed = m_packages[PackageKit::Transaction::InfoInstalled]; return installed.isEmpty() ? QString() : installed.last(); } QString PackageKitResource::comment() { return m_summary; } QString PackageKitResource::longDescription() { fetchDetails(); return m_details.description(); } QUrl PackageKitResource::homepage() { fetchDetails(); return QUrl(m_details.url()); } QVariant PackageKitResource::icon() const { return QStringLiteral("applications-other"); } QString PackageKitResource::license() { fetchDetails(); return m_details.license().isEmpty() ? i18n("Unknown") : m_details.license(); } QList PackageKitResource::addonsInformation() { return QList(); } QString PackageKitResource::availableVersion() const { return PackageKit::Daemon::packageVersion(availablePackageId()); } QString PackageKitResource::installedVersion() const { return PackageKit::Daemon::packageVersion(installedPackageId()); } int PackageKitResource::size() { fetchDetails(); return m_details.size(); } QString PackageKitResource::origin() const { auto pkgid = availablePackageId(); return PackageKit::Daemon::packageData(pkgid); } QString PackageKitResource::section() { return QString(); } AbstractResource::State PackageKitResource::state() { if (backend()->isPackageNameUpgradeable(this)) return Upgradeable; else if(m_packages.contains(PackageKit::Transaction::InfoInstalled)) return Installed; else if(m_packages.contains(PackageKit::Transaction::InfoAvailable)) return None; else return Broken; } void PackageKitResource::addPackageId(PackageKit::Transaction::Info info, const QString &packageId, bool arch) { auto oldState = state(); if (arch) m_packages[info].append(packageId); else m_packages[info].prepend(packageId); if (oldState != state()) emit stateChanged(); } QStringList PackageKitResource::categories() { return { QStringLiteral("Unknown") }; } AbstractResource::Type PackageKitResource::type() const { return Technical; } void PackageKitResource::fetchDetails() { const QString pkgid = availablePackageId(); if (!m_details.isEmpty() || pkgid.isEmpty()) return; m_details.insert(QStringLiteral("fetching"), true);//we add an entry so it's not re-fetched. backend()->fetchDetails(pkgid); } void PackageKitResource::failedFetchingDetails(PackageKit::Transaction::Error error, const QString& msg) { qWarning() << "error fetching details" << error << msg; } void PackageKitResource::setDependenciesCount(int deps) { if (deps != m_dependenciesCount) { m_dependenciesCount = deps; Q_EMIT sizeChanged(); } } void PackageKitResource::setDetails(const PackageKit::Details & details) { const bool ourDetails = details.packageId() == availablePackageId(); if (!ourDetails) return; if (m_details != details) { m_details = details; emit stateChanged(); if (!backend()->isFetching()) Q_EMIT backend()->resourcesChanged(this, {"size", "homepage", "license"}); } } void PackageKitResource::fetchChangelog() { } void PackageKitResource::fetchUpdateDetails() { const auto pkgid = availablePackageId(); if (pkgid.isEmpty()) { connect(this, &PackageKitResource::stateChanged, this, &PackageKitResource::fetchUpdateDetails); return; } PackageKit::Transaction* t = PackageKit::Daemon::getUpdateDetail(availablePackageId()); connect(t, &PackageKit::Transaction::updateDetail, this, &PackageKitResource::updateDetail); connect(t, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error err, const QString & error) { qWarning() << "error fetching updates:" << err << error; emit changelogFetched(QString()); }); } static void addIfNotEmpty(const QString& title, const QString& content, QString& where) { if (!content.isEmpty()) where += QStringLiteral("

") + title + QStringLiteral(" ") + QString(content).replace(QStringLiteral("\n"), QStringLiteral("
")) + QStringLiteral("

"); } QString PackageKitResource::joinPackages(const QStringList& pkgids, const QString &_sep, const QString &shadowPackage) { QStringList ret; foreach(const QString& pkgid, pkgids) { const auto pkgname = PackageKit::Daemon::packageName(pkgid); if (pkgname == shadowPackage) ret += PackageKit::Daemon::packageVersion(pkgid); else ret += i18nc("package-name (version)", "%1 (%2)", pkgname, PackageKit::Daemon::packageVersion(pkgid)); } const QString sep = _sep.isEmpty() ? i18nc("comma separating package names", ", ") : _sep; return ret.join(sep); } static QStringList urlToLinks(const QStringList& urls) { QStringList ret; foreach(const QString& in, urls) ret += QStringLiteral("%1").arg(in); return ret; } void PackageKitResource::updateDetail(const QString& packageID, const QStringList& updates, const QStringList& obsoletes, const QStringList& vendorUrls, const QStringList& /*bugzillaUrls*/, const QStringList& /*cveUrls*/, PackageKit::Transaction::Restart restart, const QString &_updateText, const QString& /*changelog*/, PackageKit::Transaction::UpdateState state, const QDateTime& /*issued*/, const QDateTime& /*updated*/) { #if defined(WITH_MARKDOWN) const char* xx = _updateText.toUtf8().constData(); MMIOT *markdownHandle = mkd_string(xx, _updateText.size(), 0); QString updateText; if ( !mkd_compile( markdownHandle, MKD_FENCEDCODE | MKD_GITHUBTAGS | MKD_AUTOLINK ) ) { updateText = _updateText; } else { char *htmlDocument; const int size = mkd_document( markdownHandle, &htmlDocument ); updateText = QString::fromUtf8( htmlDocument, size ); } mkd_cleanup( markdownHandle ); #else const auto& updateText = _updateText; #endif const auto name = PackageKit::Daemon::packageName(packageID); QString info; - addIfNotEmpty(i18n("Current Version:"), joinPackages(updates, {}, name), info); addIfNotEmpty(i18n("Obsoletes:"), joinPackages(obsoletes, {}, name), info); - addIfNotEmpty(i18n("New Version:"), updateText, info); + addIfNotEmpty(i18n("Release Notes:"), updateText, info); addIfNotEmpty(i18n("Update State:"), PackageKitMessages::updateStateMessage(state), info); addIfNotEmpty(i18n("Restart:"), PackageKitMessages::restartMessage(restart), info); if (!vendorUrls.isEmpty()) addIfNotEmpty(i18n("Vendor:"), urlToLinks(vendorUrls).join(QStringLiteral(", ")), info); emit changelogFetched(changelog() + info); } PackageKitBackend* PackageKitResource::backend() const { return qobject_cast(parent()); } QString PackageKitResource::sizeDescription() { if (m_dependenciesCount < 0) { fetchDetails(); fetchDependencies(); } if (m_dependenciesCount <= 0) return AbstractResource::sizeDescription(); else return i18np("%2 (plus %1 dependency)", "%2 (plus %1 dependencies)", m_dependenciesCount, AbstractResource::sizeDescription()); } QString PackageKitResource::sourceIcon() const { return QStringLiteral("package-available"); } void PackageKitResource::fetchDependencies() { const auto id = isInstalled() ? installedPackageId() : availablePackageId(); if (id.isEmpty()) return; m_dependenciesCount = 0; QSharedPointer packageDependencies(new QJsonObject); auto trans = PackageKit::Daemon::dependsOn(id); connect(trans, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error, const QString& message) { qWarning() << "Transaction error: " << message << sender(); }); connect(trans, &PackageKit::Transaction::package, this, [packageDependencies](PackageKit::Transaction::Info /*info*/, const QString &packageID, const QString &summary) { (*packageDependencies)[PackageKit::Daemon::packageName(packageID)] = summary ; }); connect(trans, &PackageKit::Transaction::finished, this, [this, packageDependencies](PackageKit::Transaction::Exit /*status*/) { Q_EMIT dependenciesFound(*packageDependencies); }); }