diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index 951952b4..d5f31c35 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,366 +1,370 @@ 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: "" + property int footerProgress: 0 property bool isBusy: false 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) 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: updateAction.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() } } Label { Layout.fillWidth: true text: page.unselected === 0 ? i18n("All updates selected (%1)", updateModel.updateSize) : i18np("%1/%2 update selected (%3)", "%1/%2 updates selected (%3)", updateModel.toUpdateCount, updateModel.totalUpdatesCount, updateModel.updateSize) elide: Text.ElideRight } } } supportsRefreshing: true onRefreshingChanged: { - showPassiveNotification("Fetching Updates...") ResourcesModel.updateAction.triggered() refreshing = false } + + readonly property Item report: ColumnLayout { + parent: page + anchors.fill: parent + Item { + Layout.fillHeight: true + width: 1 + } + Kirigami.Heading { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + text: page.footerLabel + } + ProgressBar { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: Kirigami.Units.gridUnit * 20 + value: page.footerProgress + from: 0 + to: 100 + visible: page.isBusy + } + Kirigami.Icon { + Layout.alignment: Qt.AlignHCenter + visible: page.footerProgress === 0 && page.footerLabel !== "" + source: "update-none" + opacity: 0.3 + width: Kirigami.Units.gridUnit * 12 + height: width + } + Button { + Layout.alignment: Qt.AlignHCenter + text: i18n("Restart") + visible: resourcesUpdatesModel.needsReboot + onClicked: app.reboot() + } + Item { + Layout.fillHeight: true + width: 1 + } + } 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 - } - BusyIndicator { - id: indicator - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: Kirigami.Units.gridUnit * 12 - Layout.preferredHeight: Layout.preferredWidth - visible: page.isBusy - } - Kirigami.Icon { - Layout.alignment: Qt.AlignHCenter - visible: !indicator.visible && page.footerLabel !== "" - source: "update-none" - opacity: 0.3 - width: Kirigami.Units.gridUnit * 12 - height: width - } - Button { - Layout.alignment: Qt.AlignHCenter - text: i18n("Restart") - visible: resourcesUpdatesModel.needsReboot - onClicked: app.reboot() - } - 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: 2 text: section height: implicitHeight + Kirigami.Units.largeSpacing * 2 } } delegate: Kirigami.AbstractListItem { id: listItem backgroundColor: Kirigami.Theme.backgroundColor highlighted: ListView.isCurrentItem onEnabledChanged: if (!enabled) { layout.extended = false; } visible: resourceState < 3 //3=AbstractBackendUpdater.Done 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 { width: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: width source: decoration smooth: true } ColumnLayout { // App name Kirigami.Heading { Layout.fillWidth: true text: i18n("%1", display) level: 3 elide: Text.ElideRight } // Old and new version numbers; show when there's enough room Label { id: oldAndNewVersions Layout.fillWidth: true elide: Text.ElideRight text: i18n("%1 → %2", installedVersion, availableVersion) visible: installedVersion && !truncated opacity: listItem.hovered? 0.8 : 0.6 } // Available version only, for when old+new would be elided. // Use squeezey text to gain more room, and if it's still so // so long that it would be elided, elide from the left so // the most important part on the right is still visible // All of this is mostly for the benefit of KDE Neon users, // since the version strings there are really really long Label { Layout.fillWidth: true elide: Text.ElideLeft text: availableVersion visible: !oldAndNewVersions.visible font.letterSpacing: -0.5 opacity: listItem.hovered? 0.8 : 0.6 } } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: resourceState == 2 ? i18n("Installing") : size progress: resourceProgress/100 } } Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 LinkLabel { 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: ( resourcesUpdatesModel.isProgressing ? "progressing" : updateModel.hasUpdates ? "has-updates" : 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; footerLabel: i18nc("@info", "Fetching Updates...") } + PropertyChanges { target: page; footerProgress: ResourcesModel.fetchingUpdatesProgress } PropertyChanges { target: page; isBusy: true } }, State { name: "progressing" PropertyChanges { target: page; supportsRefreshing: false } PropertyChanges { target: page.actions; main: cancelUpdateAction } }, State { name: "has-updates" PropertyChanges { target: page; title: i18nc("@info", "Updates") } PropertyChanges { target: page.actions; main: updateAction } PropertyChanges { target: page.actions; left: refreshAction } }, State { name: "reboot" PropertyChanges { target: page; footerLabel: i18nc("@info", "The system requires a restart to apply updates") } }, State { name: "now-uptodate" PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "uptodate" PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "medium" PropertyChanges { target: page; title: i18nc("@info", "Up to Date") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "low" PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") } PropertyChanges { target: page.actions; main: refreshAction } }, State { name: "unknown" PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") } PropertyChanges { target: page.actions; main: refreshAction } } ] } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp index c8584071..82269aff 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp @@ -1,664 +1,681 @@ /*************************************************************************** * 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 #include #include #include "utils.h" #include "config-paths.h" DISCOVER_BACKEND_PLUGIN(PackageKitBackend) template static void setWhenAvailable(const QDBusPendingReply& pending, W func, QObject* parent) { QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, parent, [func](QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); QDBusPendingReply reply = *watcher; func(reply.value()); }); } QString PackageKitBackend::locateService(const QString &filename) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/")+filename); } PackageKitBackend::PackageKitBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_appdata(new AppStream::Pool) , 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::checkForUpdates); t->setInterval(60 * 60 * 1000); t->setSingleShot(false); t->start(); m_delayedDetailsFetch.setSingleShot(true); m_delayedDetailsFetch.setInterval(100); connect(&m_delayedDetailsFetch, &QTimer::timeout, this, &PackageKitBackend::performDetailsFetch); connect(PackageKit::Daemon::global(), &PackageKit::Daemon::restartScheduled, m_updater, &PackageKitUpdater::enableNeedsReboot); 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, [this] { m_reviews->emitRatingFetched(this, kTransform>(m_packages.packages.values(), [] (AbstractResource* r) { return r; })); }); auto proxyWatch = new QFileSystemWatcher(this); proxyWatch->addPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/kioslaverc")); connect(proxyWatch, &QFileSystemWatcher::fileChanged, this, [this](){ KProtocolManager::reparseConfiguration(); updateProxy(); }); SourcesModel::global()->addSourcesBackend(new PackageKitSourcesBackend(this)); reloadPackageList(); setWhenAvailable(PackageKit::Daemon::getTimeSinceAction(PackageKit::Transaction::RoleRefreshCache), [this](uint timeSince) { if (timeSince > 3600) checkForUpdates(); else fetchUpdates(); }, this); } PackageKitBackend::~PackageKitBackend() = default; void PackageKitBackend::updateProxy() { if (PackageKit::Daemon::isRunning()) { static bool everHad = KProtocolManager::useProxy(); if (!everHad && !KProtocolManager::useProxy()) return; everHad = KProtocolManager::useProxy(); PackageKit::Daemon::global()->setProxy(KProtocolManager::proxyFor(QStringLiteral("http")), KProtocolManager::proxyFor(QStringLiteral("https")), KProtocolManager::proxyFor(QStringLiteral("ftp")), KProtocolManager::proxyFor(QStringLiteral("socks")), {}, {}); } } 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); } QString error; m_appdata.reset(new AppStream::Pool); const bool b = m_appdata->load(&error); 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")); }); } 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()) { auto launchable = component.launchable(AppStream::Launchable::KindDesktopId); if (component.kind() == AppStream::Component::KindDesktopApp && !launchable.entries().isEmpty()) { const QString file = locateService(launchable.entries().constFirst()); if (!file.isEmpty()) { acquireFetching(true); 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(); auto res = addComponent(component, {PackageKit::Daemon::packageName(pkgid)}); res->clearPackageIds(); res->addPackageId(PackageKit::Transaction::InfoInstalled, pkgid, true); } acquireFetching(false); }); continue; } } qDebug() << "no packages for" << component.id(); continue; } neededPackages += pkgNames; addComponent(component, pkgNames); } acquireFetching(false); if (!neededPackages.isEmpty()) { neededPackages.removeDuplicates(); resolvePackages(neededPackages); } else { qDebug() << "empty appstream db"; if (PackageKit::Daemon::backendName() == QLatin1String("aptcc") || PackageKit::Daemon::backendName().isEmpty()) { checkForUpdates(); } } } AppPackageKitResource* PackageKitBackend::addComponent(const AppStream::Component& component, const QStringList& pkgNames) { Q_ASSERT(isFetching()); Q_ASSERT(!pkgNames.isEmpty()); AppPackageKitResource* res = qobject_cast(m_packages.packages[component.id()]); if (!res) { res = new AppPackageKitResource(component, pkgNames.at(0), this); m_packages.packages[component.id()] = res; } else { res->clearPackageIds(); } 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::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); } 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_getUpdatesTransaction = PackageKit::Daemon::getUpdates(); + connect(m_getUpdatesTransaction, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesFinished); + connect(m_getUpdatesTransaction, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageToUpdate); + connect(m_getUpdatesTransaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError); + connect(m_getUpdatesTransaction, &PackageKit::Transaction::percentageChanged, this, &PackageKitBackend::fetchingUpdatesProgressChanged); m_updatesPackageId.clear(); m_hasSecurityUpdates = false; m_updater->setProgressing(true); + + fetchingUpdatesProgressChanged(); } 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) { if(PackageKit::Daemon::packageArch(packageId) == QLatin1String("source")) { // We do not add source packages, they make little sense here. If source is needed, // we are going to have to consider that in some other way, some other time // If we do not ignore them here, e.g. openSuse entirely fails at installing applications return; } 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) { if (AppPackageKitResource* ares = qobject_cast(res)) { const auto extends = res->extends(); for(const auto &ext: extends) m_packages.extendedBy[ext].removeAll(ares); } 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() { if (PackageKit::Daemon::global()->offline()->updateTriggered()) { qDebug() << "Won't be checking for updates again, the system needs a reboot to apply the fetched offline updates."; return; } 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]() { m_refresher = nullptr; 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->type() != AbstractResource::Technical && !qobject_cast(res)->extendsItself(); })); } 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 = kFilter>(resourcesByPackageNames>(ids), [](AbstractResource* res){ return !qobject_cast(res)->extendsItself(); }); QTimer::singleShot(0, this, [stream, resources] () { Q_EMIT 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())}); Q_EMIT 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.isLocalFile()) { QMimeDatabase db; const auto mime = db.mimeTypeForUrl(url); if ( mime.inherits(QStringLiteral("application/vnd.debian.binary-package")) || mime.inherits(QStringLiteral("application/x-rpm")) || mime.inherits(QStringLiteral("application/x-tar")) || mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) ) { pkg = new LocalFilePKResource(url, this); } } else if (url.host().isEmpty()) Q_EMIT 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()) Q_EMIT passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { const auto deprecatedHost = deprecatedAppstreamIds.value(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 || (host.endsWith(QLatin1String(".desktop")) && host.compare(it.key()+QLatin1String(".desktop"), Qt::CaseInsensitive) == 0)) { pkg = it.value(); break; } } // if (!pkg) // qDebug() << "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 { if (PackageKit::Daemon::global()->offline()->updateTriggered()) return 0; int ret = 0; QSet packages; const auto toUpgrade = upgradeablePackages(); for(auto res: toUpgrade) { const auto packageName = res->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } return ret; } Transaction* PackageKitBackend::installApplication(AbstractResource* app, const AddonList& addons) { Transaction* t = nullptr; if(!addons.addonsToInstall().isEmpty()) { QVector appsToInstall = resourcesByPackageNames>(addons.addonsToInstall()); if(!app->isInstalled()) appsToInstall << app; t = new PKTransaction(appsToInstall, Transaction::ChangeAddonsRole); } else if (!app->isInstalled()) t = installApplication(app); if (!addons.addonsToRemove().isEmpty()) { const auto appsToRemove = resourcesByPackageNames>(addons.addonsToRemove()); t = new PKTransaction(appsToRemove, Transaction::RemoveRole); } 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 { if (isFetching() || !m_packagesToAdd.isEmpty()) { return {}; } 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()) { resolvePackages(kTransform(m_updatesPackageId, [](const QString &pkgid) { return PackageKit::Daemon::packageName(pkgid); })); fetchDetails(m_updatesPackageId); } m_updater->setProgressing(false); includePackagesToAdd(); emit updatesCountChanged(); } 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 QSet& 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); m_packageNamesToFetchDetails.clear(); } void PackageKitBackend::checkDaemonRunning() { if (!PackageKit::Daemon::isRunning()) { qWarning() << "PackageKit stopped running!"; } else updateProxy(); } 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(); } QString PackageKitBackend::displayName() const { return AppStreamIntegration::global()->osRelease()->prettyName(); } +int PackageKitBackend::fetchingUpdatesProgress() const +{ + if (!m_getUpdatesTransaction) + return 0; + + if (m_getUpdatesTransaction->status() == PackageKit::Transaction::StatusWait || m_getUpdatesTransaction->status() == PackageKit::Transaction::StatusUnknown) { + return m_getUpdatesTransaction->property("lastPercentage").toInt(); + } + int percentage = percentageWithStatus(m_getUpdatesTransaction->status(), m_getUpdatesTransaction->percentage()); + m_getUpdatesTransaction->setProperty("lastPercentage", percentage); + return percentage; +} + + #include "PackageKitBackend.moc" diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.h b/libdiscover/backends/PackageKitBackend/PackageKitBackend.h index 29af61d7..4dfd802b 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 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); 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 resolvePackages(const QStringList &packageNames); void fetchDetails(const QString& pkgid) { fetchDetails(QSet{pkgid}); } void fetchDetails(const QSet& pkgid); 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; void fetchUpdates(); + int fetchingUpdatesProgress() const override; public Q_SLOTS: void reloadPackageList(); void transactionError(PackageKit::Transaction::Error, const QString& message); 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 addPackageToUpdate(PackageKit::Transaction::Info, const QString& pkgid, const QString& summary); void getUpdatesFinished(PackageKit::Transaction::Exit,uint); private: friend class PackageKitResource; template T resourcesByPackageNames(const QStringList& names) const; void checkDaemonRunning(); void acquireFetching(bool f); void includePackagesToAdd(); void performDetailsFetch(); AppPackageKitResource* addComponent(const AppStream::Component& component, const QStringList& pkgNames); void updateProxy(); QScopedPointer 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; + QPointer m_getUpdatesTransaction; }; #endif // PACKAGEKITBACKEND_H diff --git a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp index 7e6894bd..2eae1852 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp +++ b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp @@ -1,449 +1,450 @@ /*************************************************************************** * 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 "PackageKitUpdater.h" #include "PackageKitMessages.h" #include #include #include #include #include #include #include #include int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage) { const auto was = percentage; if (status != PackageKit::Transaction::StatusUnknown) { static const QMap statuses = { { PackageKit::Transaction::Status::StatusDownload, 0 }, { PackageKit::Transaction::Status::StatusInstall, 1}, { PackageKit::Transaction::Status::StatusRemove, 1}, + { PackageKit::Transaction::Status::StatusLoadingCache, 1}, { PackageKit::Transaction::Status::StatusUpdate, 1} }; const auto idx = statuses.value(status, -1); if (idx < 0) { qDebug() << "Status not present" << status << "among" << statuses .keys() << percentage; return -1; } percentage = (idx * 100 + percentage) / 2 /*the maximum in statuses*/; } qDebug() << "reporting progress with status:" << status << percentage << was; return percentage; } PackageKitUpdater::PackageKitUpdater(PackageKitBackend * parent) : AbstractBackendUpdater(parent), m_transaction(nullptr), m_backend(parent), m_isCancelable(false), m_isProgressing(false), m_percentage(0), m_lastUpdate() { fetchLastUpdateTime(); } PackageKitUpdater::~PackageKitUpdater() { } void PackageKitUpdater::prepare() { if (PackageKit::Daemon::global()->offline()->updateTriggered()) { m_toUpgrade.clear(); m_allUpgradeable.clear(); enableNeedsReboot(); return; } Q_ASSERT(!m_transaction); m_toUpgrade = m_backend->upgradeablePackages(); m_allUpgradeable = m_toUpgrade; } void PackageKitUpdater::setupTransaction(PackageKit::Transaction::TransactionFlags flags) { m_packagesModified.clear(); auto pkgs = involvedPackages(m_toUpgrade).toList(); pkgs.sort(); m_transaction = PackageKit::Daemon::updatePackages(pkgs, flags); m_isCancelable = m_transaction->allowCancel(); connect(m_transaction.data(), &PackageKit::Transaction::finished, this, &PackageKitUpdater::finished); connect(m_transaction.data(), &PackageKit::Transaction::package, this, &PackageKitUpdater::packageResolved); connect(m_transaction.data(), &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound); connect(m_transaction.data(), &PackageKit::Transaction::mediaChangeRequired, this, &PackageKitUpdater::mediaChange); connect(m_transaction.data(), &PackageKit::Transaction::requireRestart, this, &PackageKitUpdater::requireRestart); connect(m_transaction.data(), &PackageKit::Transaction::eulaRequired, this, &PackageKitUpdater::eulaRequired); connect(m_transaction.data(), &PackageKit::Transaction::repoSignatureRequired, this, &PackageKitUpdater::repoSignatureRequired); connect(m_transaction.data(), &PackageKit::Transaction::allowCancelChanged, this, &PackageKitUpdater::cancellableChanged); connect(m_transaction.data(), &PackageKit::Transaction::percentageChanged, this, &PackageKitUpdater::percentageChanged); connect(m_transaction.data(), &PackageKit::Transaction::itemProgress, this, &PackageKitUpdater::itemProgress); connect(m_transaction.data(), &PackageKit::Transaction::speedChanged, this, [this] { Q_EMIT downloadSpeedChanged(downloadSpeed()); }); } QSet PackageKitUpdater::packagesForPackageId(const QSet& pkgids) const { QSet packages; packages.reserve(pkgids.size()); foreach(const QString& pkgid, pkgids) { packages += PackageKit::Daemon::packageName(pkgid); } QSet ret; foreach (AbstractResource * res, m_allUpgradeable) { PackageKitResource* pres = qobject_cast(res); if (packages.contains(pres->allPackageNames().toSet())) { ret.insert(res); } } return ret; } QSet PackageKitUpdater::involvedPackages(const QSet& packages) const { QSet packageIds; packageIds.reserve(packages.size()); foreach (AbstractResource * res, packages) { PackageKitResource * app = qobject_cast(res); QString pkgid = m_backend->upgradeablePackageId(app); if (pkgid.isEmpty()) { qWarning() << "no upgradeablePackageId for" << app; continue; } packageIds.insert(pkgid); } return packageIds; } void PackageKitUpdater::processProceedFunction() { auto t = m_proceedFunctions.takeFirst()(); connect(t, &PackageKit::Transaction::finished, this, [this](PackageKit::Transaction::Exit status) { if (status != PackageKit::Transaction::Exit::ExitSuccess) { qWarning() << "transaction failed" << sender() << status; cancel(); return; } if (!m_proceedFunctions.isEmpty()) { processProceedFunction(); } else { start(); } }); } void PackageKitUpdater::proceed() { if (!m_proceedFunctions.isEmpty()) processProceedFunction(); else if (useOfflineUpdates()) setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted | PackageKit::Transaction::TransactionFlagOnlyDownload); else setupTransaction(PackageKit::Transaction::TransactionFlagOnlyTrusted); } bool PackageKitUpdater::useOfflineUpdates() const { if (qEnvironmentVariableIsSet("PK_OFFLINE_UPDATE")) return true; KConfigGroup group(KSharedConfig::openConfig(), "Software"); return group.readEntry("UseOfflineUpdates", false); } void PackageKitUpdater::setUseOfflineUpdates(bool use) { // To enable from command line use: // kwriteconfig5 --file discoverrc --group Software --key UseOfflineUpdates true KConfigGroup group(KSharedConfig::openConfig(), "Software"); group.writeEntry("UseOfflineUpdates", use); } void PackageKitUpdater::start() { Q_ASSERT(!isProgressing()); setupTransaction(PackageKit::Transaction::TransactionFlagSimulate); setProgressing(true); } void PackageKitUpdater::finished(PackageKit::Transaction::Exit exit, uint /*time*/) { // qDebug() << "update finished!" << exit << time; if (!m_proceedFunctions.isEmpty()) return; const bool cancel = exit == PackageKit::Transaction::ExitCancelled; const bool simulate = m_transaction->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate; disconnect(m_transaction, nullptr, this, nullptr); m_transaction = nullptr; if (!cancel && simulate) { const auto toremove = m_packagesModified.value(PackageKit::Transaction::InfoRemoving); if (!toremove.isEmpty()) { const auto toinstall = QStringList() << m_packagesModified.value(PackageKit::Transaction::InfoInstalling) << m_packagesModified.value(PackageKit::Transaction::InfoUpdating); Q_EMIT proceedRequest(i18n("Packages to remove"), i18n("The following packages will be removed by the update:\n
  • %1
\nin order to install:\n
  • %2
", PackageKitResource::joinPackages(toremove, QStringLiteral("
  • "), {}), PackageKitResource::joinPackages(toinstall, QStringLiteral("
  • "), {}) )); } else { proceed(); } return; } setProgressing(false); m_backend->fetchUpdates(); fetchLastUpdateTime(); if (useOfflineUpdates()) { PackageKit::Daemon::global()->offline()->trigger(PackageKit::Offline::ActionReboot); Q_EMIT passiveMessage(i18n("Please restart the computer to finish the update")); } } void PackageKitUpdater::cancellableChanged() { if (m_isCancelable != m_transaction->allowCancel()) { m_isCancelable = m_transaction->allowCancel(); emit cancelableChanged(m_isCancelable); } } void PackageKitUpdater::percentageChanged() { const auto actualPercentage = percentageWithStatus(m_transaction->status(), m_transaction->percentage()); if (actualPercentage >= 0 && m_percentage != actualPercentage) { m_percentage = actualPercentage; emit progressChanged(m_percentage); } } bool PackageKitUpdater::hasUpdates() const { return m_backend->updatesCount() > 0; } qreal PackageKitUpdater::progress() const { return m_percentage; } void PackageKitUpdater::removeResources(const QList& apps) { QSet pkgs = involvedPackages(apps.toSet()); m_toUpgrade.subtract(packagesForPackageId(pkgs)); } void PackageKitUpdater::addResources(const QList& apps) { QSet pkgs = involvedPackages(apps.toSet()); m_toUpgrade.unite(packagesForPackageId(pkgs)); } QList PackageKitUpdater::toUpdate() const { return m_toUpgrade.toList(); } bool PackageKitUpdater::isMarked(AbstractResource* res) const { return m_toUpgrade.contains(res); } QDateTime PackageKitUpdater::lastUpdate() const { return m_lastUpdate; } bool PackageKitUpdater::isCancelable() const { return m_isCancelable; } bool PackageKitUpdater::isProgressing() const { return m_isProgressing; } void PackageKitUpdater::cancel() { if (m_transaction) m_transaction->cancel(); else setProgressing(false); } void PackageKitUpdater::errorFound(PackageKit::Transaction::Error err, const QString& error) { if (err == PackageKit::Transaction::ErrorNoLicenseAgreement) return; Q_EMIT passiveMessage(QStringLiteral("%1\n%2").arg(PackageKitMessages::errorMessage(err), error)); qWarning() << "Error happened" << err << error; } void PackageKitUpdater::mediaChange(PackageKit::Transaction::MediaType media, const QString& type, const QString& text) { Q_UNUSED(media) Q_EMIT passiveMessage(i18n("Media Change of type '%1' is requested.\n%2", type, text)); } void PackageKitUpdater::requireRestart(PackageKit::Transaction::Restart restart, const QString& pkgid) { Q_EMIT passiveMessage(PackageKitMessages::restartMessage(restart, pkgid)); } void PackageKitUpdater::eulaRequired(const QString& eulaID, const QString& packageID, const QString& vendor, const QString& licenseAgreement) { m_proceedFunctions << [eulaID](){ return PackageKit::Daemon::acceptEula(eulaID); }; Q_EMIT proceedRequest(i18n("Accept EULA"), i18n("The package %1 and its vendor %2 require that you accept their license:\n %3", PackageKit::Daemon::packageName(packageID), vendor, licenseAgreement)); } void PackageKitUpdater::setProgressing(bool progressing) { if (m_isProgressing != progressing) { m_isProgressing = progressing; emit progressingChanged(m_isProgressing); } } void PackageKitUpdater::fetchLastUpdateTime() { QDBusPendingReply transaction = PackageKit::Daemon::global()->getTimeSinceAction(PackageKit::Transaction::RoleGetUpdates); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(transaction, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PackageKitUpdater::lastUpdateTimeReceived); } void PackageKitUpdater::lastUpdateTimeReceived(QDBusPendingCallWatcher* w) { QDBusPendingReply reply = w->reply(); if (reply.isError()) { qWarning() << "Error when fetching the last update time" << reply.error(); } else { m_lastUpdate = QDateTime::currentDateTime().addSecs(-int(reply.value())); } w->deleteLater(); } AbstractBackendUpdater::State toUpdateState(PackageKit::Transaction::Status t) { switch(t) { case PackageKit::Transaction::StatusUnknown: case PackageKit::Transaction::StatusDownload: return AbstractBackendUpdater::Downloading; case PackageKit::Transaction::StatusDepResolve: case PackageKit::Transaction::StatusSigCheck: case PackageKit::Transaction::StatusTestCommit: case PackageKit::Transaction::StatusInstall: case PackageKit::Transaction::StatusCommit: return AbstractBackendUpdater::Installing; case PackageKit::Transaction::StatusFinished: case PackageKit::Transaction::StatusCancel: return AbstractBackendUpdater::Done; default: qDebug() << "unknown packagekit status" << t; return AbstractBackendUpdater::None; } Q_UNREACHABLE(); } void PackageKitUpdater::itemProgress(const QString& itemID, PackageKit::Transaction::Status status, uint percentage) { const auto res = packagesForPackageId({itemID}); foreach(auto r, res) { Q_EMIT resourceProgressed(r, percentage, toUpdateState(status)); } } void PackageKitUpdater::fetchChangelog() const { QStringList pkgids; foreach(AbstractResource* res, m_allUpgradeable) { pkgids += static_cast(res)->availablePackageId(); } Q_ASSERT(!pkgids.isEmpty()); PackageKit::Transaction* t = PackageKit::Daemon::getUpdatesDetails(pkgids); connect(t, &PackageKit::Transaction::updateDetail, this, &PackageKitUpdater::updateDetail); connect(t, &PackageKit::Transaction::errorCode, this, &PackageKitUpdater::errorFound); } void PackageKitUpdater::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) { auto res = packagesForPackageId({packageID}); foreach(auto r, res) { static_cast(r)->updateDetail(packageID, updates, obsoletes, vendorUrls, bugzillaUrls, cveUrls, restart, updateText, changelog, state, issued, updated); } } void PackageKitUpdater::packageResolved(PackageKit::Transaction::Info info, const QString& packageId) { m_packagesModified[info] << packageId; } void PackageKitUpdater::repoSignatureRequired(const QString& packageID, const QString& repoName, const QString& keyUrl, const QString& keyUserid, const QString& keyId, const QString& keyFingerprint, const QString& keyTimestamp, PackageKit::Transaction::SigType type) { Q_EMIT proceedRequest(i18n("Missing signature for %1 in %2", packageID, repoName), i18n("Do you trust the following key?\n\nUrl: %1\nUser: %2\nKey: %3\nFingerprint: %4\nTimestamp: %4\n", keyUrl, keyUserid, keyFingerprint, keyTimestamp)); m_proceedFunctions << [type, keyId, packageID](){ return PackageKit::Daemon::installSignature(type, keyId, packageID); }; } double PackageKitUpdater::updateSize() const { double ret = 0.; QSet donePkgs; for (AbstractResource * res : m_toUpgrade) { PackageKitResource * app = qobject_cast(res); QString pkgid = m_backend->upgradeablePackageId(app); if (!donePkgs.contains(pkgid)) { donePkgs.insert(pkgid); ret += app->size(); } } return ret; } quint64 PackageKitUpdater::downloadSpeed() const { return m_transaction ? m_transaction->speed() : 0; } diff --git a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.h b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.h index 213da70a..1188b2c7 100644 --- a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.h +++ b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.h @@ -1,105 +1,107 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef PACKAGEKITUPDATER_H #define PACKAGEKITUPDATER_H #include #include "PackageKitBackend.h" #include +int percentageWithStatus(PackageKit::Transaction::Status status, uint percentage); + class PackageKitUpdater : public AbstractBackendUpdater { Q_OBJECT public: explicit PackageKitUpdater(PackageKitBackend * parent = nullptr); ~PackageKitUpdater() override; void prepare() override; bool hasUpdates() const override; qreal progress() const override; void setProgressing(bool progressing); void removeResources(const QList& apps) override; void addResources(const QList& apps) override; QList toUpdate() const override; bool isMarked(AbstractResource* res) const override; QDateTime lastUpdate() const override; bool isCancelable() const override; bool isProgressing() const override; void fetchChangelog() const override; double updateSize() const override; quint64 downloadSpeed() const override; void proceed() override; public Q_SLOTS: ///must be implemented if ever isCancelable is true void cancel() override; void start() override; private Q_SLOTS: void errorFound(PackageKit::Transaction::Error err, const QString& error); void mediaChange(PackageKit::Transaction::MediaType media, const QString& type, const QString& text); void requireRestart(PackageKit::Transaction::Restart restart, const QString& p); void eulaRequired(const QString &eulaID, const QString &packageID, const QString &vendor, const QString &licenseAgreement); void finished(PackageKit::Transaction::Exit exit, uint); void cancellableChanged(); void percentageChanged(); void 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); void packageResolved(PackageKit::Transaction::Info info, const QString& packageId); void repoSignatureRequired(const QString &packageID, const QString &repoName, const QString &keyUrl, const QString &keyUserid, const QString &keyId, const QString &keyFingerprint, const QString &keyTimestamp, PackageKit::Transaction::SigType type); private: void processProceedFunction(); void itemProgress(const QString &itemID, PackageKit::Transaction::Status status, uint percentage); void fetchLastUpdateTime(); void lastUpdateTimeReceived(QDBusPendingCallWatcher* w); void setupTransaction(PackageKit::Transaction::TransactionFlags flags); bool useOfflineUpdates() const; void setUseOfflineUpdates(bool use); QSet involvedPackages(const QSet& packages) const; QSet packagesForPackageId(const QSet& packages) const; QPointer m_transaction; PackageKitBackend * const m_backend; QSet m_toUpgrade; QSet m_allUpgradeable; bool m_isCancelable; bool m_isProgressing; int m_percentage; QDateTime m_lastUpdate; QMap m_packagesModified; QVector> m_proceedFunctions; }; #endif diff --git a/libdiscover/resources/AbstractResourcesBackend.cpp b/libdiscover/resources/AbstractResourcesBackend.cpp index 44dafc15..c0d82e28 100644 --- a/libdiscover/resources/AbstractResourcesBackend.cpp +++ b/libdiscover/resources/AbstractResourcesBackend.cpp @@ -1,148 +1,155 @@ /*************************************************************************** * 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 "AbstractResourcesBackend.h" #include "AbstractResource.h" #include "Category/Category.h" #include #include #include #include "libdiscover_debug.h" #include QDebug operator<<(QDebug debug, const AbstractResourcesBackend::Filters& filters) { QDebugStateSaver saver(debug); debug.nospace() << "Filters("; if (filters.category) debug.nospace() << "category: " << filters.category << ','; if (filters.state) debug.nospace() << "state: " << filters.state << ','; if (!filters.mimetype.isEmpty()) debug.nospace() << "mimetype: " << filters.mimetype << ','; if (!filters.search.isEmpty()) debug.nospace() << "search: " << filters.search << ','; if (!filters.extends.isEmpty()) debug.nospace() << "extends:" << filters.extends << ','; if (!filters.origin.isEmpty()) debug.nospace() << "origin:" << filters.origin << ','; if (!filters.resourceUrl.isEmpty()) debug.nospace() << "resourceUrl:" << filters.resourceUrl << ','; debug.nospace() << ')'; return debug; } ResultsStream::ResultsStream(const QString &objectName, const QVector& resources) : ResultsStream(objectName) { Q_ASSERT(!resources.contains(nullptr)); QTimer::singleShot(0, this, [resources, this] () { if (!resources.isEmpty()) Q_EMIT resourcesFound(resources); finish(); }); } ResultsStream::ResultsStream(const QString &objectName) { setObjectName(objectName); QTimer::singleShot(5000, this, [objectName]() { qCDebug(LIBDISCOVER_LOG) << "stream took really long" << objectName; }); } ResultsStream::~ResultsStream() { } void ResultsStream::finish() { deleteLater(); } AbstractResourcesBackend::AbstractResourcesBackend(QObject* parent) : QObject(parent) { QTimer* fetchingChangedTimer = new QTimer(this); fetchingChangedTimer->setInterval(3000); fetchingChangedTimer->setSingleShot(true); connect(fetchingChangedTimer, &QTimer::timeout, this, [this]{ qDebug() << "took really long to fetch" << this; }); connect(this, &AbstractResourcesBackend::fetchingChanged, this, [this, fetchingChangedTimer]{ // Q_ASSERT(isFetching() != fetchingChangedTimer->isActive()); if (isFetching()) fetchingChangedTimer->start(); else fetchingChangedTimer->stop(); + + Q_EMIT fetchingUpdatesProgressChanged(); }); } Transaction* AbstractResourcesBackend::installApplication(AbstractResource* app) { return installApplication(app, AddonList()); } void AbstractResourcesBackend::setName(const QString& name) { m_name = name; } QString AbstractResourcesBackend::name() const { return m_name; } void AbstractResourcesBackend::emitRatingsReady() { emit allDataChanged({ "rating", "ratingPoints", "ratingCount", "sortableRating" }); } bool AbstractResourcesBackend::Filters::shouldFilter(AbstractResource* res) const { Q_ASSERT(res); if(!extends.isEmpty() && !res->extends().contains(extends)) { return false; } if(!resourceUrl.isEmpty() && res->url() != resourceUrl) { return false; } if(!origin.isEmpty() && res->origin() != origin) { return false; } if(filterMinimumState ? (res->state() < state) : (res->state() != state)) { return false; } if(!mimetype.isEmpty() && !res->mimetypes().contains(mimetype)) { return false; } return !category || res->categoryMatches(category); } void AbstractResourcesBackend::Filters::filterJustInCase(QVector& input) const { for(auto it = input.begin(); it != input.end();) { if (shouldFilter(*it)) ++it; else it = input.erase(it); } } QStringList AbstractResourcesBackend::extends() const { return {}; } + +int AbstractResourcesBackend::fetchingUpdatesProgress() const +{ + return isFetching() ? 42 : 100; +} diff --git a/libdiscover/resources/AbstractResourcesBackend.h b/libdiscover/resources/AbstractResourcesBackend.h index b6b848c1..8e7a3e48 100644 --- a/libdiscover/resources/AbstractResourcesBackend.h +++ b/libdiscover/resources/AbstractResourcesBackend.h @@ -1,256 +1,259 @@ /*************************************************************************** * 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 ABSTRACTRESOURCESBACKEND_H #define ABSTRACTRESOURCESBACKEND_H #include #include #include #include "AbstractResource.h" #include "Transaction/AddonList.h" #include "discovercommon_export.h" class Transaction; class Category; class AbstractReviewsBackend; class AbstractBackendUpdater; class DISCOVERCOMMON_EXPORT ResultsStream : public QObject { Q_OBJECT public: ResultsStream(const QString &objectName); /// assumes all the information is in @p resources ResultsStream(const QString &objectName, const QVector& resources); ~ResultsStream() override; void finish(); Q_SIGNALS: void resourcesFound(const QVector& resources); void fetchMore(); }; /** * \class AbstractResourcesBackend AbstractResourcesBackend.h "AbstractResourcesBackend.h" * * \brief This is the base class of all resource backends. * * For writing basic new resource backends, we need to implement two classes: this and the * AbstractResource one. Basic questions on how to build your plugin with those classes * can be answered by looking at the dummy plugin. * * As this is the base class of a backend, we save all the created resources here and also * accept calls to install and remove applications or to cancel transactions. * * To show resources in Muon, we need to initialize all resources we want to show beforehand, * we should not create resources in the search function. When we reload the resources * (e.g. when initializing), the backend needs change the fetching property throughout the * process. */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackend : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(AbstractReviewsBackend* reviewsBackend READ reviewsBackend CONSTANT) Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(bool hasApplications READ hasApplications CONSTANT) public: /** * Constructs an AbstractResourcesBackend * @param parent the parent of the class (the object will be deleted when the parent gets deleted) */ explicit AbstractResourcesBackend(QObject* parent = nullptr); /** * @returns true when the backend is in a valid state, which means it is able to work * You must return true here if you want the backend to be loaded. */ virtual bool isValid() const = 0; struct Filters { Category* category = nullptr; AbstractResource::State state = AbstractResource::Broken; QString mimetype; QString search; QString extends; QUrl resourceUrl; QString origin; bool allBackends = false; bool filterMinimumState = true; bool isEmpty() const { return !category && state == AbstractResource::Broken && mimetype.isEmpty() && search.isEmpty() && extends.isEmpty() && resourceUrl.isEmpty() && origin.isEmpty(); } bool shouldFilter(AbstractResource* res) const; void filterJustInCase(QVector& input) const; }; /** * @returns a stream that will provide elements that match the search */ virtual ResultsStream* search(const Filters &search) = 0;//FIXME: Probably provide a standard implementation?! /** * @returns the reviews backend of this AbstractResourcesBackend (which handles all ratings and reviews of resources) */ virtual AbstractReviewsBackend* reviewsBackend() const = 0;//FIXME: Have a standard impl which returns 0? /** * @returns the class which is used by muon to update the users system, if you are unsure what to do * just return the StandardBackendUpdater */ virtual AbstractBackendUpdater* backendUpdater() const = 0;//FIXME: Standard impl returning the standard updater? /** * @returns the number of resources for which an update is available, it should only count technical packages */ virtual int updatesCount() const = 0;//FIXME: Probably provide a standard implementation?! /** * @returns whether either of the updates contains a security fix */ virtual bool hasSecurityUpdates() const { return false; } /** * Tells whether the backend is fetching resources */ virtual bool isFetching() const = 0; /** * @returns the appstream ids that this backend extends */ virtual QStringList extends() const; /** @returns the plugin's name */ QString name() const; /** @internal only to be used by the factory */ void setName(const QString& name); virtual QString displayName() const = 0; /** * emits a change for all rating properties */ void emitRatingsReady(); /** * @returns the root category tree */ virtual QVector category() const { return {}; } virtual bool hasApplications() const { return false; } + virtual int fetchingUpdatesProgress() const; + public Q_SLOTS: /** * This gets called when the backend should install an application. * The AbstractResourcesBackend should create a Transaction object, is returned and * will be included in the TransactionModel * @param app the application to be installed * @param addons the addons which should be installed with the application * @returns the Transaction that keeps track of the installation process */ virtual Transaction* installApplication(AbstractResource *app, const AddonList& addons) = 0; /** * Overloaded function, which simply does the same, except not installing any addons. */ virtual Transaction* installApplication(AbstractResource *app); /** * This gets called when the backend should remove an application. * Like in the installApplication() method, we'll return the Transaction * responsible for the removal. * * @see installApplication * @param app the application to be removed * @returns the Transaction that keeps track of the removal process */ virtual Transaction* removeApplication(AbstractResource *app) = 0; /** * Notifies the backend that the user wants the information to be up to date */ virtual void checkForUpdates() = 0; Q_SIGNALS: /** * Notify of a change in the backend */ void fetchingChanged(); /** * This should be emitted when the number of upgradeable packages changed. */ void updatesCountChanged(); /** * This should be emitted when all data of the backends resources changed. Internally it will emit * a signal in the model to show the view that all data of a certain backend changed. */ void allDataChanged(const QVector &propertyNames); /** * Allows to notify some @p properties in @p resource have changed */ void resourcesChanged(AbstractResource* resource, const QVector &properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); + void fetchingUpdatesProgressChanged(); private: QString m_name; }; DISCOVERCOMMON_EXPORT QDebug operator<<(QDebug dbg, const AbstractResourcesBackend::Filters& filters); /** * @internal Workaround because QPluginLoader enforces 1 instance per plugin */ class DISCOVERCOMMON_EXPORT AbstractResourcesBackendFactory : public QObject { Q_OBJECT public: virtual QVector newInstance(QObject* parent, const QString &name) const = 0; }; #define DISCOVER_BACKEND_PLUGIN(ClassName)\ class ClassName##Factory : public AbstractResourcesBackendFactory {\ Q_OBJECT\ Q_PLUGIN_METADATA(IID "org.kde.muon.AbstractResourcesBackendFactory")\ Q_INTERFACES(AbstractResourcesBackendFactory)\ public:\ QVector newInstance(QObject* parent, const QString &name) const override {\ auto c = new ClassName(parent);\ c->setName(name);\ return {c};\ }\ }; Q_DECLARE_INTERFACE( AbstractResourcesBackendFactory, "org.kde.muon.AbstractResourcesBackendFactory" ) #endif // ABSTRACTRESOURCESBACKEND_H diff --git a/libdiscover/resources/ResourcesModel.cpp b/libdiscover/resources/ResourcesModel.cpp index fd17b093..ff004220 100644 --- a/libdiscover/resources/ResourcesModel.cpp +++ b/libdiscover/resources/ResourcesModel.cpp @@ -1,402 +1,417 @@ /*************************************************************************** * 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 "ResourcesModel.h" #include "AbstractResource.h" #include "resources/AbstractResourcesBackend.h" #include "resources/AbstractBackendUpdater.h" #include #include #include #include #include "Transaction/TransactionModel.h" #include "Category/CategoryModel.h" #include "utils.h" #include "libdiscover_debug.h" #include #include #include #include #include #include #include ResourcesModel *ResourcesModel::s_self = nullptr; ResourcesModel *ResourcesModel::global() { if(!s_self) s_self = new ResourcesModel; return s_self; } ResourcesModel::ResourcesModel(QObject* parent, bool load) : QObject(parent) , m_isFetching(false) , m_initializingBackends(0) , m_currentApplicationBackend(nullptr) , m_allInitializedEmitter(new QTimer(this)) { init(load); connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching); connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend); } void ResourcesModel::init(bool load) { Q_ASSERT(!s_self); Q_ASSERT(QCoreApplication::instance()->thread()==QThread::currentThread()); m_allInitializedEmitter->setSingleShot(true); m_allInitializedEmitter->setInterval(0); connect(m_allInitializedEmitter, &QTimer::timeout, this, [this](){ if (m_initializingBackends == 0) emit allInitialized(); }); if(load) QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection); m_updateAction = new QAction(this); m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update"))); m_updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates")); m_updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) { m_updateAction->setEnabled(!fetching); + fetchingUpdatesProgressChanged(); }); connect(m_updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); } ResourcesModel::ResourcesModel(const QString& backendName, QObject* parent) : ResourcesModel(parent, false) { s_self = this; registerBackendByName(backendName); } ResourcesModel::~ResourcesModel() { s_self = nullptr; qDeleteAll(m_backends); } void ResourcesModel::addResourcesBackend(AbstractResourcesBackend* backend) { Q_ASSERT(!m_backends.contains(backend)); if(!backend->isValid()) { qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); return; } m_backends += backend; if(!backend->isFetching()) { if (backend->updatesCount() > 0) emit updatesCountChanged(); } else { m_initializingBackends++; } connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged); connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller); connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged); connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, &ResourcesModel::updatesCountChanged); + connect(backend, &AbstractResourcesBackend::fetchingUpdatesProgressChanged, this, &ResourcesModel::fetchingUpdatesProgressChanged); connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved); connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage); connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching); // In case this is in fact the first backend to be added, and also happens to be // pre-filled, we still need for the rest of the backends to be added before trying // to send out the initialized signal. To ensure this happens, schedule it for the // start of the next run of the event loop. if(m_initializingBackends==0) { m_allInitializedEmitter->start(); } else { slotFetching(); } } void ResourcesModel::callerFetchingChanged() { AbstractResourcesBackend* backend = qobject_cast(sender()); if (!backend->isValid()) { qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); int idx = m_backends.indexOf(backend); Q_ASSERT(idx>=0); m_backends.removeAt(idx); Q_EMIT backendsChanged(); CategoryModel::global()->blacklistPlugin(backend->name()); backend->deleteLater(); slotFetching(); return; } if(backend->isFetching()) { m_initializingBackends++; slotFetching(); } else { m_initializingBackends--; if(m_initializingBackends==0) m_allInitializedEmitter->start(); else slotFetching(); } } void ResourcesModel::updateCaller(const QVector& properties) { AbstractResourcesBackend* backend = qobject_cast(sender()); Q_EMIT backendDataChanged(backend, properties); } QVector< AbstractResourcesBackend* > ResourcesModel::backends() const { return m_backends; } int ResourcesModel::updatesCount() const { int ret = 0; foreach(AbstractResourcesBackend* backend, m_backends) { ret += backend->updatesCount(); } return ret; } bool ResourcesModel::hasSecurityUpdates() const { bool ret = false; foreach(AbstractResourcesBackend* backend, m_backends) { ret |= backend->hasSecurityUpdates(); } return ret; } void ResourcesModel::installApplication(AbstractResource* app) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app)); } void ResourcesModel::installApplication(AbstractResource* app, const AddonList& addons) { TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons)); } void ResourcesModel::removeApplication(AbstractResource* app) { TransactionModel::global()->addTransaction(app->backend()->removeApplication(app)); } void ResourcesModel::registerAllBackends() { DiscoverBackendsFactory f; const auto backends = f.allBackends(); if(m_initializingBackends==0 && backends.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends"; m_allInitializedEmitter->start(); } else { foreach(AbstractResourcesBackend* b, backends) { addResourcesBackend(b); } emit backendsChanged(); } } void ResourcesModel::registerBackendByName(const QString& name) { DiscoverBackendsFactory f; const auto backends = f.backend(name); for(auto b : backends) addResourcesBackend(b); emit backendsChanged(); } bool ResourcesModel::isFetching() const { return m_isFetching; } void ResourcesModel::slotFetching() { bool newFetching = false; foreach(AbstractResourcesBackend* b, m_backends) { // isFetching should sort of be enough. However, sometimes the backend itself // will still be operating on things, which from a model point of view would // still mean something going on. So, interpret that as fetching as well, for // the purposes of this property. if(b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) { newFetching = true; break; } } if (newFetching != m_isFetching) { m_isFetching = newFetching; Q_EMIT fetchingChanged(m_isFetching); } } bool ResourcesModel::isBusy() const { return TransactionModel::global()->rowCount() > 0; } bool ResourcesModel::isExtended(const QString& id) { bool ret = true; foreach (AbstractResourcesBackend* backend, m_backends) { ret = backend->extends().contains(id); if (ret) break; } return ret; } AggregatedResultsStream::AggregatedResultsStream(const QSet& streams) : ResultsStream(QStringLiteral("AggregatedResultsStream")) { Q_ASSERT(!streams.contains(nullptr)); if (streams.isEmpty()) { qCWarning(LIBDISCOVER_LOG) << "no streams to aggregate!!"; QTimer::singleShot(0, this, &AggregatedResultsStream::clear); } for (auto stream: streams) { connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults); connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::destruction); connect(this, &ResultsStream::fetchMore, stream, &ResultsStream::fetchMore); m_streams << stream; } m_delayedEmission.setInterval(0); connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults); } AggregatedResultsStream::~AggregatedResultsStream() = default; void AggregatedResultsStream::addResults(const QVector& res) { for(auto r : res) connect(r, &QObject::destroyed, this, [this, r](){ m_results.removeAll(r); }); m_results += res; m_delayedEmission.start(); } void AggregatedResultsStream::emitResults() { if (!m_results.isEmpty()) { Q_EMIT resourcesFound(m_results); m_results.clear(); } m_delayedEmission.setInterval(m_delayedEmission.interval() + 100); m_delayedEmission.stop(); } void AggregatedResultsStream::destruction(QObject* obj) { m_streams.remove(obj); clear(); } void AggregatedResultsStream::clear() { if (m_streams.isEmpty()) { emitResults(); Q_EMIT finished(); deleteLater(); } } AggregatedResultsStream* ResourcesModel::search(const AbstractResourcesBackend::Filters& search) { if (search.isEmpty()) { return new AggregatedResultsStream ({new ResultsStream(QStringLiteral("emptysearch"), {})}); } auto streams = kTransform>(m_backends, [search](AbstractResourcesBackend* backend){ return backend->search(search); }); return new AggregatedResultsStream(streams); } void ResourcesModel::checkForUpdates() { for(auto backend: qAsConst(m_backends)) backend->checkForUpdates(); } QVector ResourcesModel::applicationBackends() const { return kFilter>(m_backends, [](AbstractResourcesBackend* b){ return b->hasApplications(); }); } QVariantList ResourcesModel::applicationBackendsVariant() const { return kTransform(applicationBackends(), [](AbstractResourcesBackend* b) {return QVariant::fromValue(b);}); } AbstractResourcesBackend* ResourcesModel::currentApplicationBackend() const { return m_currentApplicationBackend; } void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool write) { if (backend != m_currentApplicationBackend) { if (write) { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); if (backend) settings.writeEntry("currentApplicationBackend", backend->name()); else settings.deleteEntry("currentApplicationBackend"); } qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend" << backend; m_currentApplicationBackend = backend; Q_EMIT currentApplicationBackendChanged(backend); } } void ResourcesModel::initApplicationsBackend() { KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel"); const QString name = settings.readEntry("currentApplicationBackend", QStringLiteral("packagekit-backend")); const auto backends = applicationBackends(); auto idx = kIndexOf(backends, [name](AbstractResourcesBackend* b) { return b->name() == name; }); if (idx<0) { idx = kIndexOf(backends, [](AbstractResourcesBackend* b) { return b->hasApplications(); }); qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx; } setCurrentApplicationBackend(backends.value(idx, nullptr), false); } + + +int ResourcesModel::fetchingUpdatesProgress() const +{ + if (m_backends.isEmpty()) + return 0; + + int sum = 0; + for(auto backend: m_backends) { + sum += backend->fetchingUpdatesProgress(); + } + return sum / m_backends.count(); +} diff --git a/libdiscover/resources/ResourcesModel.h b/libdiscover/resources/ResourcesModel.h index 62d2b24e..d2550a2a 100644 --- a/libdiscover/resources/ResourcesModel.h +++ b/libdiscover/resources/ResourcesModel.h @@ -1,130 +1,133 @@ /*************************************************************************** * 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 RESOURCESMODEL_H #define RESOURCESMODEL_H #include #include #include #include "discovercommon_export.h" #include "AbstractResourcesBackend.h" class QAction; class DISCOVERCOMMON_EXPORT AggregatedResultsStream : public ResultsStream { Q_OBJECT public: AggregatedResultsStream(const QSet& streams); ~AggregatedResultsStream(); Q_SIGNALS: void finished(); private: void addResults(const QVector& res); void emitResults(); void destruction(QObject* obj); void clear(); QSet m_streams; QVector m_results; QTimer m_delayedEmission; }; class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject { Q_OBJECT Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged) Q_PROPERTY(bool hasSecurityUpdates READ hasSecurityUpdates NOTIFY updatesCountChanged) Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged) Q_PROPERTY(QVariantList applicationBackends READ applicationBackendsVariant NOTIFY backendsChanged) Q_PROPERTY(AbstractResourcesBackend* currentApplicationBackend READ currentApplicationBackend WRITE setCurrentApplicationBackend NOTIFY currentApplicationBackendChanged) Q_PROPERTY(QAction* updateAction READ updateAction CONSTANT) + Q_PROPERTY(int fetchingUpdatesProgress READ fetchingUpdatesProgress NOTIFY fetchingUpdatesProgressChanged) public: /** This constructor should be only used by unit tests. * @p backendName defines what backend will be loaded when the backend is constructed. */ explicit ResourcesModel(const QString& backendName, QObject* parent = nullptr); static ResourcesModel* global(); ~ResourcesModel() override; QVector< AbstractResourcesBackend* > backends() const; int updatesCount() const; bool hasSecurityUpdates() const; bool isBusy() const; bool isFetching() const; Q_SCRIPTABLE bool isExtended(const QString &id); AggregatedResultsStream* search(const AbstractResourcesBackend::Filters &search); void checkForUpdates(); QVariantList applicationBackendsVariant() const; QVector applicationBackends() const; void setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool writeConfig = true); AbstractResourcesBackend* currentApplicationBackend() const; QAction* updateAction() const { return m_updateAction; } + int fetchingUpdatesProgress() const; public Q_SLOTS: void installApplication(AbstractResource* app, const AddonList& addons); void installApplication(AbstractResource* app); void removeApplication(AbstractResource* app); Q_SIGNALS: void fetchingChanged(bool isFetching); void allInitialized(); void backendsChanged(); void updatesCountChanged(); void backendDataChanged(AbstractResourcesBackend* backend, const QVector& properties); void resourceDataChanged(AbstractResource* resource, const QVector& properties); void resourceRemoved(AbstractResource* resource); void passiveMessage(const QString &message); void currentApplicationBackendChanged(AbstractResourcesBackend* currentApplicationBackend); + void fetchingUpdatesProgressChanged(); private Q_SLOTS: void callerFetchingChanged(); void updateCaller(const QVector& properties); void registerAllBackends(); private: ///@p initialize tells if all backends load will be triggered on construction explicit ResourcesModel(QObject* parent=nullptr, bool load = true); void init(bool load); void addResourcesBackend(AbstractResourcesBackend* backend); void registerBackendByName(const QString& name); void initApplicationsBackend(); void slotFetching(); bool m_isFetching; QVector< AbstractResourcesBackend* > m_backends; int m_initializingBackends; QAction* m_updateAction = nullptr; AbstractResourcesBackend* m_currentApplicationBackend; QTimer* m_allInitializedEmitter; static ResourcesModel* s_self; }; #endif // RESOURCESMODEL_H diff --git a/libdiscover/resources/ResourcesUpdatesModel.h b/libdiscover/resources/ResourcesUpdatesModel.h index f2f2af1b..d3eeef91 100644 --- a/libdiscover/resources/ResourcesUpdatesModel.h +++ b/libdiscover/resources/ResourcesUpdatesModel.h @@ -1,85 +1,84 @@ /*************************************************************************** * 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 RESOURCESUPDATESMODEL_H #define RESOURCESUPDATESMODEL_H #include #include #include #include "discovercommon_export.h" #include "resources/AbstractBackendUpdater.h" class AbstractResource; class UpdateTransaction; class Transaction; class DISCOVERCOMMON_EXPORT ResourcesUpdatesModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(bool isProgressing READ isProgressing NOTIFY progressingChanged) Q_PROPERTY(QDateTime lastUpdate READ lastUpdate NOTIFY progressingChanged) Q_PROPERTY(qint64 secsToLastUpdate READ secsToLastUpdate NOTIFY progressingChanged) Q_PROPERTY(Transaction* transaction READ transaction NOTIFY progressingChanged) Q_PROPERTY(bool needsReboot READ needsReboot NOTIFY needsRebootChanged) public: explicit ResourcesUpdatesModel(QObject* parent = nullptr); - quint64 downloadSpeed() const; Q_SCRIPTABLE void prepare(); bool isProgressing() const; QList toUpdate() const; QDateTime lastUpdate() const; double updateSize() const; void addResources(const QList& resources); void removeResources(const QList& resources); qint64 secsToLastUpdate() const; QVector updaters() const { return m_updaters; } Transaction* transaction() const; bool needsReboot() const; Q_SIGNALS: void downloadSpeedChanged(); void progressingChanged(); void finished(); void resourceProgressed(AbstractResource* resource, qreal progress, AbstractBackendUpdater::State state); void passiveMessage(const QString &message); void needsRebootChanged(); + void fetchingUpdatesProgressChanged(int percent); public Q_SLOTS: void updateAll(); private Q_SLOTS: void updaterDestroyed(QObject* obj); void message(const QString& msg); private: void init(); - void updateFinished(); void setTransaction(UpdateTransaction* transaction); QVector m_updaters; bool m_lastIsProgressing; QPointer m_transaction; }; #endif // RESOURCESUPDATESMODEL_H