aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBart Ribbers <bribbers@disroot.org>2021-05-13 21:30:15 +0200
committerBart Ribbers <bribbers@disroot.org>2021-06-08 16:12:29 +0000
commit89d271bd67cef81332d03e09a1f2a0789f795540 (patch)
tree6316672b4c101751f3551b84e18c03a1cf35836b
parented21ca30abc243e3725ae7da0723dc558906ea52 (diff)
downloadaports-89d271bd67cef81332d03e09a1f2a0789f795540.tar.gz
aports-89d271bd67cef81332d03e09a1f2a0789f795540.tar.bz2
aports-89d271bd67cef81332d03e09a1f2a0789f795540.tar.xz
community/plasma: upgrade to 5.22.0
-rw-r--r--community/bluedevil/APKBUILD4
-rw-r--r--community/breeze-grub/APKBUILD4
-rw-r--r--community/breeze-gtk/APKBUILD6
-rw-r--r--community/breeze-plymouth/APKBUILD4
-rw-r--r--community/breeze/APKBUILD4
-rw-r--r--community/discover/0001-Add-support-for-Alpine-Linux-apk-backend.patch9352
-rw-r--r--community/discover/0001-port-away-from-qaction.patch858
-rw-r--r--community/discover/0002-Add-support-for-Alpine-Linux-apk-backend.patch3330
-rw-r--r--community/discover/APKBUILD15
-rw-r--r--community/discover/alpine-linux-logo-icon.pngbin0 -> 896 bytes
-rw-r--r--community/drkonqi/APKBUILD4
-rw-r--r--community/kactivitymanagerd/APKBUILD4
-rw-r--r--community/kde-cli-tools/APKBUILD4
-rw-r--r--community/kde-gtk-config/APKBUILD4
-rw-r--r--community/kdecoration/APKBUILD4
-rw-r--r--community/kdeplasma-addons/APKBUILD4
-rw-r--r--community/kgamma5/APKBUILD4
-rw-r--r--community/khotkeys/APKBUILD4
-rw-r--r--community/kinfocenter/APKBUILD4
-rw-r--r--community/kmenuedit/APKBUILD4
-rw-r--r--community/kscreen/APKBUILD4
-rw-r--r--community/kscreenlocker/APKBUILD5
-rw-r--r--community/ksshaskpass/APKBUILD4
-rw-r--r--community/ksysguard/APKBUILD10
-rw-r--r--community/kwallet-pam/APKBUILD4
-rw-r--r--community/kwayland-integration/APKBUILD4
-rw-r--r--community/kwayland-server/APKBUILD4
-rw-r--r--community/kwin/APKBUILD4
-rw-r--r--community/kwrited/APKBUILD4
-rw-r--r--community/libkscreen/APKBUILD4
-rw-r--r--community/libksysguard/APKBUILD8
-rw-r--r--community/milou/APKBUILD4
-rw-r--r--community/oxygen/APKBUILD4
-rw-r--r--community/plasma-browser-integration/APKBUILD4
-rw-r--r--community/plasma-desktop/APKBUILD4
-rw-r--r--community/plasma-disks/APKBUILD4
-rw-r--r--community/plasma-firewall/APKBUILD4
-rw-r--r--community/plasma-integration/APKBUILD4
-rw-r--r--community/plasma-nano/APKBUILD4
-rw-r--r--community/plasma-nm/APKBUILD4
-rw-r--r--community/plasma-pa/APKBUILD4
-rw-r--r--community/plasma-phone-components/APKBUILD5
-rw-r--r--community/plasma-sdk/APKBUILD4
-rw-r--r--community/plasma-systemmonitor/APKBUILD4
-rw-r--r--community/plasma-thunderbolt/APKBUILD4
-rw-r--r--community/plasma-vault/APKBUILD4
-rw-r--r--community/plasma-workspace-wallpapers/APKBUILD4
-rw-r--r--community/plasma-workspace/APKBUILD11
-rw-r--r--community/plasma/APKBUILD4
-rw-r--r--community/plymouth-kcm/APKBUILD4
-rw-r--r--community/polkit-kde-agent-1/APKBUILD4
-rw-r--r--community/powerdevil/APKBUILD4
-rw-r--r--community/qqc2-breeze-style/APKBUILD4
-rw-r--r--community/sddm-kcm/APKBUILD4
-rw-r--r--community/systemsettings/APKBUILD4
-rw-r--r--community/xdg-desktop-portal-kde/APKBUILD4
56 files changed, 9476 insertions, 4304 deletions
diff --git a/community/bluedevil/APKBUILD b/community/bluedevil/APKBUILD
index 00386a6212..66eb458804 100644
--- a/community/bluedevil/APKBUILD
+++ b/community/bluedevil/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=bluedevil
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Integrate the Bluetooth technology within KDE workspace and applications"
# armhf blocked by qt5-qtdeclarative
@@ -57,5 +57,5 @@ package() {
}
sha512sums="
-9d2f06e767551b159433e87c2e0c7180152fd881d78dce59abe8af9268780e916378b2e3b3dd385d782893a530aa54f0b178762c18169230caf3975a6798fc52 bluedevil-5.21.5.tar.xz
+2fd736347f6144da38c9e53f1bdcb3946b9a6950f8ee2ee3e2663b306fa87fe02e8472266bb7830090835c71a3d14b5965753eadd049dc1b431993ddffd8736a bluedevil-5.22.0.tar.xz
"
diff --git a/community/breeze-grub/APKBUILD b/community/breeze-grub/APKBUILD
index 0d2aaf173c..93b33b70bd 100644
--- a/community/breeze-grub/APKBUILD
+++ b/community/breeze-grub/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=breeze-grub
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Breeze theme for GRUB"
arch="noarch !s390x !armhf" # armhf blocked by extra-cmake-modules
@@ -30,5 +30,5 @@ package() {
cp -r breeze "$pkgdir"/usr/share/grub/themes
}
sha512sums="
-c2f16338ad330004c961a9fda6ed48d9670e956bd8562ad05b5f804e3c45ce05c243933028caedb2de981d9fa4f6d66a627aaeb04652fabddebd71fc09b08f5f breeze-grub-5.21.5.tar.xz
+7acbac9b242211d82a0d9f0e2922d0be7bfdebce0749a22fc0488cf9bd77bbfc4f8e1eff072aad2c507c7454b4c920b3d14eab3215590bb23b5f854e04136d78 breeze-grub-5.22.0.tar.xz
"
diff --git a/community/breeze-gtk/APKBUILD b/community/breeze-gtk/APKBUILD
index efdbe4e94b..fdbf64dadd 100644
--- a/community/breeze-gtk/APKBUILD
+++ b/community/breeze-gtk/APKBUILD
@@ -1,12 +1,12 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=breeze-gtk
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="A GTK Theme Built to Match KDE's Breeze"
# armhf blocked by extra-cmake-modules
# s390x blocked by breeze
-arch="all !armhf !s390x !mips64"
+arch="noarch !armhf !s390x !mips64"
url="https://kde.org/plasma-desktop/"
license="LGPL-2.1-only"
depends="gtk-engines"
@@ -41,5 +41,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-afbd9348b2cfd55412df50ef4f2732d6f2fbd58356579766187ea79d20b86127b6e140caa14132edb940fb5d72b5da981869cdeca3abb1c17352782973bf0aae breeze-gtk-5.21.5.tar.xz
+61ce15452173577d397e79ca131cf6cf150388d3972cac91f73e1aff9209a1a9475feccfb6dbc0e5a877e539e1c2a44cc7635e4f874e031e76f09cc4b49120c5 breeze-gtk-5.22.0.tar.xz
"
diff --git a/community/breeze-plymouth/APKBUILD b/community/breeze-plymouth/APKBUILD
index fa8aa7428f..b739709a84 100644
--- a/community/breeze-plymouth/APKBUILD
+++ b/community/breeze-plymouth/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=breeze-plymouth
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
arch="all !armhf" # armhf blocked by extra-cmake-modules
url="https://kde.org/"
@@ -34,5 +34,5 @@ package() {
}
sha512sums="
-7b562a9b86baf4f362e3cd97722bbe3940be4232910e68ff1f029f2de5d1e362d930dde03a3adbed738bda559f9f6b5b812ae7e1451a145edc37e60b5af66285 breeze-plymouth-5.21.5.tar.xz
+a6c58e4f303767af201397279b48dddb3addc4c6f793ed22eb3af50d0e99bafa387410f86444c45f61e12618153bce321eb32f40288d7e478953357ecab47a32 breeze-plymouth-5.22.0.tar.xz
"
diff --git a/community/breeze/APKBUILD b/community/breeze/APKBUILD
index 08de8dd29f..9584cfc057 100644
--- a/community/breeze/APKBUILD
+++ b/community/breeze/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=breeze
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Artwork, styles and assets for the Breeze visual style for the Plasma Desktop"
# armhf blocked by qt5-qtdeclarative
@@ -46,5 +46,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-5e449624543e2bd437bd4311e7ff091ffae33c4da34da2b9968f9551970b66350d337e6aee974f9e2cd82eff877a4ab5c6f52ec52d4f2724fe93b747f021e9eb breeze-5.21.5.tar.xz
+6ac6557dba04ea8546ba19a535e6209967d96f0ad6d1f4b653c1eee020db8af47787f510f885570b2c19a9b419e43fa4a4c9d06ed03d353d0e99d877b77def0b breeze-5.22.0.tar.xz
"
diff --git a/community/discover/0001-Add-support-for-Alpine-Linux-apk-backend.patch b/community/discover/0001-Add-support-for-Alpine-Linux-apk-backend.patch
new file mode 100644
index 0000000000..a555380d3e
--- /dev/null
+++ b/community/discover/0001-Add-support-for-Alpine-Linux-apk-backend.patch
@@ -0,0 +1,9352 @@
+From 18813d11b907ed81bfe1ba79ee3efb93fa53a6e1 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 12 Jan 2020 01:02:39 +0300
+Subject: [PATCH 01/62] Initial support for AlpineAPK backend
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 191 +++++++++++++++
+ .../AlpineApkBackend/AlpineApkBackend.h | 66 ++++++
+ .../AlpineApkBackend/AlpineApkResource.cpp | 222 ++++++++++++++++++
+ .../AlpineApkBackend/AlpineApkResource.h | 77 ++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 28 +++
+ libdiscover/backends/CMakeLists.txt | 11 +
+ 6 files changed, 595 insertions(+)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+ create mode 100644 libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+new file mode 100644
+index 00000000..aa1aacab
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -0,0 +1,191 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkBackend.h"
++#include "AlpineApkResource.h"
++//#include "DummyReviewsBackend.h"
++//#include "DummyTransaction.h"
++//#include "DummySourcesBackend.h"
++#include <resources/StandardBackendUpdater.h>
++#include <resources/SourcesModel.h>
++#include <Transaction/Transaction.h>
++
++#include <KAboutData>
++#include <KLocalizedString>
++#include <KPluginFactory>
++#include <KConfigGroup>
++#include <KSharedConfig>
++#include <QDebug>
++#include <QThread>
++#include <QTimer>
++#include <QAction>
++
++DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
++
++AlpineApkBackend::AlpineApkBackend(QObject* parent)
++ : AbstractResourcesBackend(parent)
++ , m_updater(new StandardBackendUpdater(this))
++ //, m_reviews(new DummyReviewsBackend(this))
++ , m_fetching(true)
++ , m_startElements(120)
++{
++ QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
++ //connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady);
++ connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &AlpineApkBackend::updatesCountChanged);
++
++ populate(QStringLiteral("Dummy"));
++ //if (!m_fetching)
++ // m_reviews->initialize();
++
++ //SourcesModel::global()->addSourcesBackend(new DummySourcesBackend(this));
++}
++
++void AlpineApkBackend::populate(const QString& n)
++{
++ const int start = m_resources.count();
++ for(int i=start; i<start+m_startElements; i++) {
++ const QString name = n+QLatin1Char(' ')+QString::number(i);
++ AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Application, this);
++ res->setSize(100+(m_startElements-i));
++ res->setState(AbstractResource::State(1+(i%3)));
++ m_resources.insert(name.toLower(), res);
++ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ }
++
++ for(int i=start; i<start+m_startElements; i++) {
++ const QString name = QLatin1String("addon")+QString::number(i);
++ AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Addon, this);
++ res->setState(AbstractResource::State(1+(i%3)));
++ res->setSize(300+(m_startElements-i));
++ m_resources.insert(name, res);
++ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ }
++
++ for(int i=start; i<start+m_startElements; i++) {
++ const QString name = QLatin1String("techie")+QString::number(i);
++ AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Technical, this);
++ res->setState(AbstractResource::State(1+(i%3)));
++ res->setSize(300+(m_startElements-i));
++ m_resources.insert(name, res);
++ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ }
++}
++
++void AlpineApkBackend::toggleFetching()
++{
++ m_fetching = !m_fetching;
++// qDebug() << "fetching..." << m_fetching;
++ emit fetchingChanged();
++ //if (!m_fetching)
++ // m_reviews->initialize();
++}
++
++int AlpineApkBackend::updatesCount() const
++{
++ return m_updater->updatesCount();
++}
++
++ResultsStream* AlpineApkBackend::search(const AbstractResourcesBackend::Filters& filter)
++{
++ QVector<AbstractResource*> ret;
++ if (!filter.resourceUrl.isEmpty())
++ return findResourceByPackageName(filter.resourceUrl);
++ else foreach(AbstractResource* r, m_resources) {
++ if (r->type() == AbstractResource::Technical && filter.state != AbstractResource::Upgradeable) {
++ continue;
++ }
++
++ if (r->state() < filter.state)
++ continue;
++
++ if(r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive))
++ ret += r;
++ }
++ return new ResultsStream(QStringLiteral("DummyStream"), ret);
++}
++
++ResultsStream * AlpineApkBackend::findResourceByPackageName(const QUrl& search)
++{
++ if(search.isLocalFile()) {
++ AlpineApkResource* res = new AlpineApkResource(search.fileName(), AbstractResource::Technical, this);
++ res->setSize(666);
++ res->setState(AbstractResource::None);
++ m_resources.insert(res->packageName(), res);
++ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ return new ResultsStream(QStringLiteral("DummyStream-local"), { res });
++ }
++
++ auto res = search.scheme() == QLatin1String("dummy") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr;
++ if (!res) {
++ return new ResultsStream(QStringLiteral("DummyStream"), {});
++ } else
++ return new ResultsStream(QStringLiteral("DummyStream"), { res });
++}
++
++AbstractBackendUpdater* AlpineApkBackend::backendUpdater() const
++{
++ return m_updater;
++}
++
++AbstractReviewsBackend* AlpineApkBackend::reviewsBackend() const
++{
++ //return m_reviews;
++ return nullptr;
++}
++
++Transaction* AlpineApkBackend::installApplication(AbstractResource* app, const AddonList& addons)
++{
++ //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), addons, Transaction::InstallRole);
++ return nullptr;
++}
++
++Transaction* AlpineApkBackend::installApplication(AbstractResource* app)
++{
++ //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::InstallRole);
++ return nullptr;
++}
++
++Transaction* AlpineApkBackend::removeApplication(AbstractResource* app)
++{
++ //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::RemoveRole);
++ return nullptr;
++}
++
++void AlpineApkBackend::checkForUpdates()
++{
++ if(m_fetching)
++ return;
++ toggleFetching();
++ populate(QStringLiteral("Moar"));
++ QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
++ qDebug() << "AlpineApkBackend::checkForUpdates";
++}
++
++QString AlpineApkBackend::displayName() const
++{
++ return QStringLiteral("Dummy");
++}
++
++bool AlpineApkBackend::hasApplications() const
++{
++ return true;
++}
++
++#include "AlpineApkBackend.moc"
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+new file mode 100644
+index 00000000..96fe234d
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -0,0 +1,66 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef AlpineApkBackend_H
++#define AlpineApkBackend_H
++
++#include <resources/AbstractResourcesBackend.h>
++#include <QVariantList>
++
++class DummyReviewsBackend;
++class StandardBackendUpdater;
++class AlpineApkResource;
++class AlpineApkBackend : public AbstractResourcesBackend
++{
++Q_OBJECT
++Q_PROPERTY(int startElements MEMBER m_startElements)
++public:
++ explicit AlpineApkBackend(QObject* parent = nullptr);
++
++ int updatesCount() const override;
++ AbstractBackendUpdater* backendUpdater() const override;
++ AbstractReviewsBackend* reviewsBackend() const override;
++ ResultsStream* search(const AbstractResourcesBackend::Filters & search) override;
++ ResultsStream * findResourceByPackageName(const QUrl& search);
++ QHash<QString, AlpineApkResource*> resources() const { return m_resources; }
++ bool isValid() const override { return true; } // No external file dependencies that could cause runtime errors
++
++ Transaction* installApplication(AbstractResource* app) override;
++ Transaction* installApplication(AbstractResource* app, const AddonList& addons) override;
++ Transaction* removeApplication(AbstractResource* app) override;
++ bool isFetching() const override { return m_fetching; }
++ void checkForUpdates() override;
++ QString displayName() const override;
++ bool hasApplications() const override;
++
++public Q_SLOTS:
++ void toggleFetching();
++
++private:
++ void populate(const QString& name);
++
++ QHash<QString, AlpineApkResource*> m_resources;
++ StandardBackendUpdater* m_updater;
++ DummyReviewsBackend* m_reviews;
++ bool m_fetching;
++ int m_startElements;
++};
++
++#endif // AlpineApkBackend_H
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+new file mode 100644
+index 00000000..d1cceaaa
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -0,0 +1,222 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkResource.h"
++#include <Transaction/AddonList.h>
++#include <krandom.h>
++#include <QDesktopServices>
++#include <QStringList>
++#include <QTimer>
++
++Q_GLOBAL_STATIC_WITH_ARGS(QVector<QString>, s_icons,
++ ({ QLatin1String("kdevelop"),
++ QLatin1String("kalgebra"),
++ QLatin1String("kmail"),
++ QLatin1String("akregator"),
++ QLatin1String("korganizer") }))
++
++AlpineApkResource::AlpineApkResource(
++ QString name,
++ AbstractResource::Type type,
++ AbstractResourcesBackend* parent)
++ : AbstractResource(parent)
++ , m_name(std::move(name))
++ , m_state(State::Broken)
++ , m_iconName((*s_icons)[KRandom::random() % s_icons->size()])
++ , m_addons({ PackageState(QStringLiteral("a"), QStringLiteral("aaaaaa"), false),
++ PackageState(QStringLiteral("b"), QStringLiteral("aaaaaa"), false),
++ PackageState(QStringLiteral("c"), QStringLiteral("aaaaaa"), false)})
++ , m_type(type)
++{
++ const int nofScreenshots = KRandom::random() % 5;
++ m_screenshots = QList<QUrl>{
++ QUrl(QStringLiteral("https://screenshots.debian.net/screenshots/000/014/863/large.png")),
++ QUrl(QStringLiteral("https://c1.staticflickr.com/9/8479/8166397343_b78106f353_k.jpg")),
++ QUrl(QStringLiteral("https://c2.staticflickr.com/4/3685/9954407993_dad10a6943_k.jpg")),
++ QUrl(QStringLiteral("https://c1.staticflickr.com/1/653/22527103378_8ce572e1de_k.jpg")),
++ QUrl(QStringLiteral("https://images.unsplash.com/photo-1528744598421-b7b93e12df15?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60")),
++ QUrl(QStringLiteral("https://images.unsplash.com/photo-1552385430-53e6f2028760?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80")),
++ QUrl(QStringLiteral("https://images.unsplash.com/photo-1506810172640-8a9f77cb1472?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80")),
++
++ }.mid(nofScreenshots);
++ m_screenshotThumbnails = m_screenshots;
++}
++
++QList<PackageState> AlpineApkResource::addonsInformation()
++{
++ return m_addons;
++}
++
++QString AlpineApkResource::availableVersion() const
++{
++ return QStringLiteral("3.0");
++}
++
++QStringList AlpineApkResource::categories()
++{
++ return { QStringLiteral("dummy"),
++ m_name.endsWith(QLatin1Char('3'))
++ ? QStringLiteral("three")
++ : QStringLiteral("notthree") };
++}
++
++QString AlpineApkResource::comment()
++{
++ return QStringLiteral("A reasonably short comment ") + name();
++}
++
++int AlpineApkResource::size()
++{
++ return m_size;
++}
++
++QUrl AlpineApkResource::homepage()
++{
++ return QUrl(QStringLiteral("https://kde.org"));
++}
++
++QUrl AlpineApkResource::helpURL()
++{
++ return QUrl(QStringLiteral("http://very-very-excellent-docs.lol"));
++}
++
++QUrl AlpineApkResource::bugURL()
++{
++ return QUrl(QStringLiteral("file:///dev/null"));
++}
++
++QUrl AlpineApkResource::donationURL()
++{
++ return QUrl(QStringLiteral("https://youtu.be/0o8XMlL8rqY"));
++}
++
++QVariant AlpineApkResource::icon() const
++{
++ static const QVector<QVariant> icons = {
++ QStringLiteral("device-notifier"),
++ QStringLiteral("media-floppy"),
++ QStringLiteral("drink-beer")
++ };
++ return icons[type()];
++}
++
++QString AlpineApkResource::installedVersion() const
++{
++ return QStringLiteral("2.3");
++}
++
++QJsonArray AlpineApkResource::licenses()
++{
++ return {
++ QJsonObject {
++ { QStringLiteral("name"), QStringLiteral("GPL") },
++ { QStringLiteral("url"), QStringLiteral("https://kde.org") }
++ }
++ };
++}
++
++QString AlpineApkResource::longDescription()
++{
++ return QStringLiteral("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies consequat nulla, ut vulputate nulla ultricies ac. Suspendisse lacinia commodo lacus, non tristique mauris dictum vitae. Sed adipiscing augue nec nisi aliquet viverra. Etiam sit amet nulla in tellus consectetur feugiat. Cras in sem tortor. Fusce a nulla at justo accumsan gravida. Maecenas dui felis, lacinia at ornare sed, aliquam et purus. Sed ut sagittis lacus. Etiam dictum pharetra rhoncus. Suspendisse auctor orci ipsum. Pellentesque vitae urna nec felis consequat lobortis dictum in urna. Phasellus a mi ac leo adipiscing varius eget a felis. Cras magna augue, commodo sed placerat vel, tempus vel ligula. In feugiat quam quis est lobortis sed accumsan nunc malesuada. Mauris quis massa sit amet felis tempus suscipit a quis diam.\n\n"
++
++ "Aenean quis nulla erat, vel sagittis sem. Praesent vitae mauris arcu. Cras porttitor, ante at scelerisque sodales, nibh felis consectetur orci, ut hendrerit urna urna non urna. Duis eu magna id mi scelerisque adipiscing. Aliquam sed quam in eros sodales accumsan. Phasellus tempus sagittis suscipit. Aliquam rutrum dictum justo ut viverra. Nulla felis sem, molestie sed scelerisque non, consequat vitae nulla. Aliquam ullamcorper malesuada mi, vel vestibulum magna vulputate eget. In hac habitasse platea dictumst. Cras sed lacus dui, vel semper sem. Aenean sodales porta leo vel fringilla.\n\n"
++
++ "Ut tempus massa et urna porta non mollis metus ultricies. Duis nec nulla ac metus auctor porta id et mi. Mauris aliquam nibh a ligula malesuada sed tincidunt nibh varius. Sed felis metus, porta et adipiscing non, faucibus id leo. Donec ipsum nibh, hendrerit eget aliquam nec, tempor ut mauris. Suspendisse potenti. Vestibulum scelerisque adipiscing libero tristique eleifend. Donec quis tortor eget elit mollis iaculis ac sit amet nisi. Proin non massa sed nunc rutrum pellentesque. Sed dui lectus, laoreet sed condimentum id, commodo sed urna.\n\n"
++
++ "Praesent tincidunt mattis massa mattis porta. Nullam posuere neque at mauris vestibulum vitae elementum leo sodales. Quisque condimentum lectus in libero luctus egestas. Fusce tempor neque ac dui tincidunt eget viverra quam suscipit. In hac habitasse platea dictumst. Etiam metus mi, adipiscing nec suscipit id, aliquet sed sem. Duis urna ligula, ornare sed vestibulum vel, molestie ac nisi. Morbi varius iaculis ligula. Nunc in augue leo, sit amet aliquam elit. Suspendisse rutrum sem diam. Proin eu orci nisl. Praesent porttitor dignissim est, id fermentum arcu venenatis vitae.\n\n"
++
++ "Integer in sapien eget quam vulputate lobortis. Morbi nibh elit, elementum vitae vehicula sed, consequat nec erat. Donec placerat porttitor est ut dapibus. Fusce augue orci, dictum et convallis vel, blandit eu tortor. Phasellus non eros nulla. In iaculis nulla fermentum nulla gravida eu mattis purus consectetur. Integer dui nunc, sollicitudin ac tincidunt nec, hendrerit bibendum nunc. Proin sit amet augue ac velit egestas varius. Sed eu ante quis orci vestibulum sagittis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus vitae urna odio, at molestie leo. In convallis neque vel mi dictum convallis lobortis turpis sagittis.\n\n");
++}
++
++QString AlpineApkResource::name() const
++{
++ return m_name;
++}
++
++QString AlpineApkResource::origin() const
++{
++ return QStringLiteral("DummySource1");
++}
++
++QString AlpineApkResource::packageName() const
++{
++ return m_name;
++}
++
++QString AlpineApkResource::section()
++{
++ return QStringLiteral("dummy");
++}
++
++AbstractResource::State AlpineApkResource::state()
++{
++ return m_state;
++}
++
++void AlpineApkResource::fetchChangelog()
++{
++ QString log = longDescription();
++ log.replace(QLatin1Char('\n'), QLatin1String("<br />"));
++
++ emit changelogFetched(log);
++}
++
++void AlpineApkResource::fetchScreenshots()
++{
++ Q_EMIT screenshotsFetched(m_screenshotThumbnails, m_screenshots);
++}
++
++void AlpineApkResource::setState(AbstractResource::State state)
++{
++ m_state = state;
++ emit stateChanged();
++}
++
++void AlpineApkResource::setAddons(const AddonList& addons)
++{
++ Q_FOREACH (const QString& toInstall, addons.addonsToInstall()) {
++ setAddonInstalled(toInstall, true);
++ }
++ Q_FOREACH (const QString& toRemove, addons.addonsToRemove()) {
++ setAddonInstalled(toRemove, false);
++ }
++}
++
++void AlpineApkResource::setAddonInstalled(const QString& addon, bool installed)
++{
++ for(auto & elem : m_addons) {
++ if(elem.name() == addon) {
++ elem.setInstalled(installed);
++ }
++ }
++}
++
++
++void AlpineApkResource::invokeApplication() const
++{
++ QDesktopServices d;
++ d.openUrl(QUrl(QStringLiteral("https://projects.kde.org/projects/extragear/sysadmin/muon")));
++}
++
++QUrl AlpineApkResource::url() const
++{
++ return QUrl(QLatin1String("dummy://")
++ + packageName().replace(QLatin1Char(' '), QLatin1Char('.')));
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+new file mode 100644
+index 00000000..f4ecee4a
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -0,0 +1,77 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef ALPINEAPKRESOURCE_H
++#define ALPINEAPKRESOURCE_H
++
++#include <resources/AbstractResource.h>
++
++class AddonList;
++class AlpineApkResource : public AbstractResource
++{
++Q_OBJECT
++public:
++ explicit AlpineApkResource(QString name, AbstractResource::Type type, AbstractResourcesBackend* parent);
++
++ QList<PackageState> addonsInformation() override;
++ QString section() override;
++ QString origin() const override;
++ QString longDescription() override;
++ QString availableVersion() const override;
++ QString installedVersion() const override;
++ QJsonArray licenses() override;
++ int size() override;
++ QUrl homepage() override;
++ QUrl helpURL() override;
++ QUrl bugURL() override;
++ QUrl donationURL() override;
++ QStringList categories() override;
++ AbstractResource::State state() override;
++ QVariant icon() const override;
++ QString comment() override;
++ QString name() const override;
++ QString packageName() const override;
++ AbstractResource::Type type() const override { return m_type; }
++ bool canExecute() const override { return true; }
++ void invokeApplication() const override;
++ void fetchChangelog() override;
++ void fetchScreenshots() override;
++ QUrl url() const override;
++ QString author() const override { return QStringLiteral("BananaPerson"); }
++ void setState(State state);
++ void setSize(int size) { m_size = size; }
++ void setAddons(const AddonList& addons);
++
++ void setAddonInstalled(const QString& addon, bool installed);
++ QString sourceIcon() const override { return QStringLiteral("player-time"); }
++ QDate releaseDate() const override { return {}; }
++
++public:
++ const QString m_name;
++ AbstractResource::State m_state;
++ QList<QUrl> m_screenshots;
++ QList<QUrl> m_screenshotThumbnails;
++ QString m_iconName;
++ QList<PackageState> m_addons;
++ const AbstractResource::Type m_type;
++ int m_size;
++};
++
++#endif // ALPINEAPKRESOURCE_H
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+new file mode 100644
+index 00000000..8d1f9a57
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -0,0 +1,28 @@
++# add_subdirectory(tests) # no tests yet
++
++set(alpineapk-backend_SRCS
++ AlpineApkResource.cpp
++ AlpineApkBackend.cpp
++)
++
++add_library(alpineapk-backend MODULE ${alpineapk-backend_SRCS})
++
++target_link_libraries(alpineapk-backend PRIVATE
++ Qt5::Core
++ Qt5::Widgets
++ KF5::CoreAddons
++ KF5::ConfigCore
++ Discover::Common
++)
++
++install(TARGETS alpineapk-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
++install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
++
++
++#add_library(DummyNotifier MODULE DummyNotifier.cpp)
++
++#target_link_libraries(DummyNotifier Discover::Notifiers)
++
++#set_target_properties(DummyNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
++
++#install(TARGETS DummyNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
+diff --git a/libdiscover/backends/CMakeLists.txt b/libdiscover/backends/CMakeLists.txt
+index 75bafa17..0feb87b7 100644
+--- a/libdiscover/backends/CMakeLists.txt
++++ b/libdiscover/backends/CMakeLists.txt
+@@ -50,4 +50,15 @@ if(BUILD_RpmOstreeBackend)
+ add_subdirectory(RpmOstreeBackend)
+ endif()
+
++# Optional library
++find_package(ApkQt CONFIG)
++set_package_properties(ApkQt PROPERTIES
++ DESCRIPTION "C++/Qt interface library for Alpine package keeper"
++ URL "https://gitlab.com/postmarketOS/libapk-qt"
++ PURPOSE "Required to build the Alpine APK backend"
++ TYPE OPTIONAL)
+
++option(BUILD_AlpineApkBackend "Build Alpine APK support." "ON")
++if(BUILD_AlpineApkBackend AND ApkQt_FOUND)
++ add_subdirectory(AlpineApkBackend)
++endif()
+--
+GitLab
+
+
+From 32cbca7002ea7f02e2b9febd6f9b27feb13e8f0d Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 14 Jan 2020 15:20:35 +0300
+Subject: [PATCH 02/62] don't install categories file
+
+---
+ libdiscover/backends/AlpineApkBackend/CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 8d1f9a57..118f3db7 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -16,7 +16,7 @@ target_link_libraries(alpineapk-backend PRIVATE
+ )
+
+ install(TARGETS alpineapk-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
+-install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
++#install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
+
+
+ #add_library(DummyNotifier MODULE DummyNotifier.cpp)
+--
+GitLab
+
+
+From 08bbf82119f443e009e2ed4dc7ef3524272f1c2f Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 14 Jan 2020 15:30:43 +0300
+Subject: [PATCH 03/62] tidy up cmakelists and add more sources
+
+---
+ .../AlpineApkSourcesBackend.cpp | 94 +++++++++++++++++++
+ .../AlpineApkSourcesBackend.h | 49 ++++++++++
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 88 +++++++++++++++++
+ .../AlpineApkBackend/AlpineApkTransaction.h | 46 +++++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 45 +++++----
+ 5 files changed, 305 insertions(+), 17 deletions(-)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+new file mode 100644
+index 00000000..3f492185
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -0,0 +1,94 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkSourcesBackend.h"
++#include <QDebug>
++#include <QAction>
++
++AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend * parent)
++ : AbstractSourcesBackend(parent)
++ , m_sources(new QStandardItemModel(this))
++ , m_testAction(new QAction(QIcon::fromTheme(QStringLiteral("kalgebra")), QStringLiteral("DummyAction"), this))
++{
++ for (int i = 0; i<10; ++i)
++ addSource(QStringLiteral("DummySource%1").arg(i));
++
++ connect(m_testAction, &QAction::triggered, [](){ qDebug() << "action triggered!"; });
++ connect(m_sources, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) { qDebug() << "DummySource changed" << item << item->checkState(); });
++}
++
++QAbstractItemModel* AlpineApkSourcesBackend::sources()
++{
++ return m_sources;
++}
++
++bool AlpineApkSourcesBackend::addSource(const QString& id)
++{
++ if (id.isEmpty())
++ return false;
++
++ QStandardItem* it = new QStandardItem(id);
++ it->setData(id, AbstractSourcesBackend::IdRole);
++ it->setData(QVariant(id + QLatin1Char(' ') + id), Qt::ToolTipRole);
++ it->setCheckable(true);
++ it->setCheckState(Qt::Checked);
++ m_sources->appendRow(it);
++ return true;
++}
++
++QStandardItem * AlpineApkSourcesBackend::sourceForId(const QString& id) const
++{
++ for (int i=0, c=m_sources->rowCount(); i<c; ++i) {
++ const auto it = m_sources->item(i, 0);
++ if (it->text() == id)
++ return it;
++ }
++ return nullptr;
++}
++
++bool AlpineApkSourcesBackend::removeSource(const QString& id)
++{
++ const auto it = sourceForId(id);
++ if (!it) {
++ qWarning() << "couldn't find " << id;
++ return false;
++ }
++ return m_sources->removeRow(it->row());
++}
++
++QVariantList AlpineApkSourcesBackend::actions() const
++{
++ return QVariantList() << QVariant::fromValue<QObject*>(m_testAction);
++}
++
++bool AlpineApkSourcesBackend::moveSource(const QString& sourceId, int delta)
++{
++ int row = sourceForId(sourceId)->row();
++ auto prevRow = m_sources->takeRow(row);
++ Q_ASSERT(!prevRow.isEmpty());
++
++ const auto destRow = row + delta;
++ m_sources->insertRow(destRow, prevRow);
++ if (destRow == 0 || row == 0)
++ Q_EMIT firstSourceIdChanged();
++ if (destRow == m_sources->rowCount() - 1 || row == m_sources->rowCount() - 1)
++ Q_EMIT lastSourceIdChanged();
++ return true;
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+new file mode 100644
+index 00000000..48a0d829
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+@@ -0,0 +1,49 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef ALPINEAPKSOURCESBACKEND_H
++#define ALPINEAPKSOURCESBACKEND_H
++
++#include <resources/AbstractSourcesBackend.h>
++#include <QStandardItemModel>
++
++class AlpineApkSourcesBackend : public AbstractSourcesBackend
++{
++public:
++ explicit AlpineApkSourcesBackend(AbstractResourcesBackend *parent);
++
++ QAbstractItemModel *sources() override;
++ bool addSource(const QString &id) override;
++ bool removeSource(const QString &id) override;
++ QString idDescription() override { return QStringLiteral("Random weird text"); }
++ QVariantList actions() const override;
++ bool supportsAdding() const override { return true; }
++
++ bool canMoveSources() const override { return true; }
++ bool moveSource(const QString &sourceId, int delta) override;
++
++private:
++ QStandardItem* sourceForId(const QString &id) const;
++
++ QStandardItemModel* m_sources;
++ QAction* m_testAction;
++};
++
++#endif // ALPINEAPKSOURCESBACKEND_H
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+new file mode 100644
+index 00000000..8b6b72a5
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -0,0 +1,88 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkTransaction.h"
++#include "AlpineApkBackend.h"
++#include "AlpineApkResource.h"
++#include <QTimer>
++#include <QDebug>
++#include <KRandom>
++
++// #define TEST_PROCEED
++
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource* app, Role role)
++ : AlpineApkTransaction(app, {}, role)
++{
++}
++
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource* app, const AddonList& addons, Transaction::Role role)
++ : Transaction(app->backend(), app, role, addons)
++ , m_app(app)
++{
++ setCancellable(true);
++ setStatus(DownloadingStatus);
++ iterateTransaction();
++}
++
++void AlpineApkTransaction::iterateTransaction()
++{
++ if (!m_iterate)
++ return;
++
++ if(progress()<100) {
++ setProgress(qBound(0, progress()+(KRandom::random()%30), 100));
++ QTimer::singleShot(/*KRandom::random()%*/100, this, &AlpineApkTransaction::iterateTransaction);
++ } else if (status() == DownloadingStatus) {
++ setStatus(CommittingStatus);
++ QTimer::singleShot(/*KRandom::random()%*/100, this, &AlpineApkTransaction::iterateTransaction);
++ } else {
++ finishTransaction();
++ }
++}
++
++void AlpineApkTransaction::proceed()
++{
++ finishTransaction();
++}
++
++void AlpineApkTransaction::cancel()
++{
++ m_iterate = false;
++
++ setStatus(CancelledStatus);
++}
++
++void AlpineApkTransaction::finishTransaction()
++{
++ AbstractResource::State newState;
++ switch(role()) {
++ case InstallRole:
++ case ChangeAddonsRole:
++ newState = AbstractResource::Installed;
++ break;
++ case RemoveRole:
++ newState = AbstractResource::None;
++ break;
++ }
++ m_app->setAddons(addons());
++ m_app->setState(newState);
++ setStatus(DoneStatus);
++ deleteLater();
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+new file mode 100644
+index 00000000..49102dd5
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+@@ -0,0 +1,46 @@
++/***************************************************************************
++ * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef ALPINEAPKTRANSACTION_H
++#define ALPINEAPKTRANSACTION_H
++
++#include <Transaction/Transaction.h>
++
++class AlpineApkResource;
++class AlpineApkTransaction : public Transaction
++{
++ Q_OBJECT
++ public:
++ AlpineApkTransaction(AlpineApkResource* app, Role role);
++ AlpineApkTransaction(AlpineApkResource* app, const AddonList& list, Role role);
++
++ void cancel() override;
++ void proceed() override;
++
++ private Q_SLOTS:
++ void iterateTransaction();
++ void finishTransaction();
++
++ private:
++ bool m_iterate = true;
++ AlpineApkResource* m_app;
++};
++
++#endif // ALPINEAPKTRANSACTION_H
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 118f3db7..635009ab 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -1,28 +1,39 @@
+ # add_subdirectory(tests) # no tests yet
+
+-set(alpineapk-backend_SRCS
+- AlpineApkResource.cpp
+- AlpineApkBackend.cpp
++add_library(
++ alpineapk-backend
++ MODULE
++ AlpineApkBackend.cpp
++ AlpineApkBackend.h
++ AlpineApkResource.cpp
++ AlpineApkResource.h
++ AlpineApkSourcesBackend.cpp
++ AlpineApkSourcesBackend.h
++ AlpineApkTransaction.cpp
++ AlpineApkTransaction.h
+ )
+
+-add_library(alpineapk-backend MODULE ${alpineapk-backend_SRCS})
+-
+-target_link_libraries(alpineapk-backend PRIVATE
+- Qt5::Core
+- Qt5::Widgets
+- KF5::CoreAddons
+- KF5::ConfigCore
+- Discover::Common
++target_link_libraries(
++ alpineapk-backend
++ PRIVATE
++ Qt5::Core
++ Qt5::Widgets
++ KF5::CoreAddons
++ KF5::ConfigCore
++ Discover::Common
+ )
+
+-install(TARGETS alpineapk-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover)
+-#install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
++install(
++ TARGETS alpineapk-backend
++ DESTINATION ${PLUGIN_INSTALL_DIR}/discover
++)
+
++# install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
+
+-#add_library(DummyNotifier MODULE DummyNotifier.cpp)
++# add_library(AlpineApkNotifier MODULE AlpineApkNotifier.cpp)
+
+-#target_link_libraries(DummyNotifier Discover::Notifiers)
++# target_link_libraries(AlpineApkNotifier Discover::Notifiers)
+
+-#set_target_properties(DummyNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
++# set_target_properties(AlpineApkNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
+
+-#install(TARGETS DummyNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
++# install(TARGETS AlpineApkNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
+--
+GitLab
+
+
+From 64f2f887e35e1f8ca389f9766918a36778fad983 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 14 Jan 2020 15:34:19 +0300
+Subject: [PATCH 04/62] Make use of new sources/classes
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 18 ++++++++----------
+ .../AlpineApkBackend/AlpineApkBackend.h | 9 +++++----
+ 2 files changed, 13 insertions(+), 14 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index aa1aacab..1df00d72 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -21,8 +21,9 @@
+ #include "AlpineApkBackend.h"
+ #include "AlpineApkResource.h"
+ //#include "DummyReviewsBackend.h"
+-//#include "DummyTransaction.h"
+-//#include "DummySourcesBackend.h"
++#include "AlpineApkTransaction.h"
++#include "AlpineApkSourcesBackend.h"
++
+ #include <resources/StandardBackendUpdater.h>
+ #include <resources/SourcesModel.h>
+ #include <Transaction/Transaction.h>
+@@ -54,7 +55,7 @@ AlpineApkBackend::AlpineApkBackend(QObject* parent)
+ //if (!m_fetching)
+ // m_reviews->initialize();
+
+- //SourcesModel::global()->addSourcesBackend(new DummySourcesBackend(this));
++ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+ }
+
+ void AlpineApkBackend::populate(const QString& n)
+@@ -152,20 +153,17 @@ AbstractReviewsBackend* AlpineApkBackend::reviewsBackend() const
+
+ Transaction* AlpineApkBackend::installApplication(AbstractResource* app, const AddonList& addons)
+ {
+- //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), addons, Transaction::InstallRole);
+- return nullptr;
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), addons, Transaction::InstallRole);
+ }
+
+ Transaction* AlpineApkBackend::installApplication(AbstractResource* app)
+ {
+- //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::InstallRole);
+- return nullptr;
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::InstallRole);
+ }
+
+ Transaction* AlpineApkBackend::removeApplication(AbstractResource* app)
+ {
+- //return new DummyTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::RemoveRole);
+- return nullptr;
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::RemoveRole);
+ }
+
+ void AlpineApkBackend::checkForUpdates()
+@@ -180,7 +178,7 @@ void AlpineApkBackend::checkForUpdates()
+
+ QString AlpineApkBackend::displayName() const
+ {
+- return QStringLiteral("Dummy");
++ return QStringLiteral("Alpine APK");
+ }
+
+ bool AlpineApkBackend::hasApplications() const
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 96fe234d..38602348 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -24,13 +24,14 @@
+ #include <resources/AbstractResourcesBackend.h>
+ #include <QVariantList>
+
+-class DummyReviewsBackend;
++//class DummyReviewsBackend;
+ class StandardBackendUpdater;
+ class AlpineApkResource;
+ class AlpineApkBackend : public AbstractResourcesBackend
+ {
+-Q_OBJECT
+-Q_PROPERTY(int startElements MEMBER m_startElements)
++ Q_OBJECT
++ Q_PROPERTY(int startElements MEMBER m_startElements)
++
+ public:
+ explicit AlpineApkBackend(QObject* parent = nullptr);
+
+@@ -58,7 +59,7 @@ private:
+
+ QHash<QString, AlpineApkResource*> m_resources;
+ StandardBackendUpdater* m_updater;
+- DummyReviewsBackend* m_reviews;
++ //DummyReviewsBackend* m_reviews;
+ bool m_fetching;
+ int m_startElements;
+ };
+--
+GitLab
+
+
+From d427c0a4243d73fff07b169a354d2d24c4fe84fe Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 14 Jan 2020 16:11:46 +0300
+Subject: [PATCH 05/62] Setup debug logging category
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 8 ++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 28 +++++++++++++------
+ 2 files changed, 28 insertions(+), 8 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 1df00d72..2f7ac2f9 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -23,6 +23,7 @@
+ //#include "DummyReviewsBackend.h"
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkSourcesBackend.h"
++#include "alpineapk_backend_logging.h" // generated by ECM
+
+ #include <resources/StandardBackendUpdater.h>
+ #include <resources/SourcesModel.h>
+@@ -34,6 +35,7 @@
+ #include <KConfigGroup>
+ #include <KSharedConfig>
+ #include <QDebug>
++#include <QLoggingCategory>
+ #include <QThread>
+ #include <QTimer>
+ #include <QAction>
+@@ -47,6 +49,12 @@ AlpineApkBackend::AlpineApkBackend(QObject* parent)
+ , m_fetching(true)
+ , m_startElements(120)
+ {
++#ifndef QT_DEBUG
++ const_cast<QLoggingCategory &>(LOG_ALPINEAPK()).setEnabled(QtDebugMsg, false);
++#endif
++
++ qCDebug(LOG_ALPINEAPK) << "constructing backend!";
++
+ QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
+ //connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady);
+ connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &AlpineApkBackend::updatesCountChanged);
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 635009ab..2bf3da97 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -1,16 +1,28 @@
+ # add_subdirectory(tests) # no tests yet
+
++set(alpineapkbackend_SRCS
++ AlpineApkBackend.cpp
++ AlpineApkBackend.h
++ AlpineApkResource.cpp
++ AlpineApkResource.h
++ AlpineApkSourcesBackend.cpp
++ AlpineApkSourcesBackend.h
++ AlpineApkTransaction.cpp
++ AlpineApkTransaction.h
++)
++
++ecm_qt_declare_logging_category(
++ alpineapkbackend_SRCS # sources_var
++ HEADER alpineapk_backend_logging.h
++ IDENTIFIER LOG_ALPINEAPK
++ CATEGORY_NAME org.kde.plasma.discover.alpineapk
++ DEFAULT_SEVERITY Debug
++)
++
+ add_library(
+ alpineapk-backend
+ MODULE
+- AlpineApkBackend.cpp
+- AlpineApkBackend.h
+- AlpineApkResource.cpp
+- AlpineApkResource.h
+- AlpineApkSourcesBackend.cpp
+- AlpineApkSourcesBackend.h
+- AlpineApkTransaction.cpp
+- AlpineApkTransaction.h
++ ${alpineapkbackend_SRCS}
+ )
+
+ target_link_libraries(
+--
+GitLab
+
+
+From d705787a1c29c01fcbce04404dd1fc81cef598a7 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 14 Jan 2020 20:33:28 +0300
+Subject: [PATCH 06/62] Tidy up backend code and add debug prints
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 58 ++++++++++++-------
+ .../AlpineApkBackend/AlpineApkResource.cpp | 1 +
+ .../AlpineApkSourcesBackend.cpp | 1 +
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 1 +
+ 4 files changed, 40 insertions(+), 21 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 2f7ac2f9..56d01e61 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -68,6 +68,8 @@ AlpineApkBackend::AlpineApkBackend(QObject* parent)
+
+ void AlpineApkBackend::populate(const QString& n)
+ {
++ qCDebug(LOG_ALPINEAPK) << "populate! (" << n << ")";
++
+ const int start = m_resources.count();
+ for(int i=start; i<start+m_startElements; i++) {
+ const QString name = n+QLatin1Char(' ')+QString::number(i);
+@@ -100,7 +102,7 @@ void AlpineApkBackend::populate(const QString& n)
+ void AlpineApkBackend::toggleFetching()
+ {
+ m_fetching = !m_fetching;
+-// qDebug() << "fetching..." << m_fetching;
++ qCDebug(LOG_ALPINEAPK) << "fetching..." << m_fetching;
+ emit fetchingChanged();
+ //if (!m_fetching)
+ // m_reviews->initialize();
+@@ -108,44 +110,54 @@ void AlpineApkBackend::toggleFetching()
+
+ int AlpineApkBackend::updatesCount() const
+ {
++ qCDebug(LOG_ALPINEAPK) << "updatesCount(): " << m_updater->updatesCount();
+ return m_updater->updatesCount();
+ }
+
+ ResultsStream* AlpineApkBackend::search(const AbstractResourcesBackend::Filters& filter)
+ {
+ QVector<AbstractResource*> ret;
+- if (!filter.resourceUrl.isEmpty())
++ if (!filter.resourceUrl.isEmpty()) {
+ return findResourceByPackageName(filter.resourceUrl);
+- else foreach(AbstractResource* r, m_resources) {
+- if (r->type() == AbstractResource::Technical && filter.state != AbstractResource::Upgradeable) {
+- continue;
++ } else {
++ for (AbstractResource* r: qAsConst(m_resources)) {
++ if (r->type() == AbstractResource::Technical
++ && filter.state != AbstractResource::Upgradeable) {
++ continue;
++ }
++
++ if (r->state() < filter.state) {
++ continue;
++ }
++
++ if(r->name().contains(filter.search, Qt::CaseInsensitive)
++ || r->comment().contains(filter.search, Qt::CaseInsensitive)) {
++ ret += r;
++ }
+ }
+-
+- if (r->state() < filter.state)
+- continue;
+-
+- if(r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive))
+- ret += r;
+ }
+- return new ResultsStream(QStringLiteral("DummyStream"), ret);
++ return new ResultsStream(QStringLiteral("AlpineApkStream"), ret);
+ }
+
+ ResultsStream * AlpineApkBackend::findResourceByPackageName(const QUrl& search)
+ {
+- if(search.isLocalFile()) {
+- AlpineApkResource* res = new AlpineApkResource(search.fileName(), AbstractResource::Technical, this);
++ if (search.isLocalFile()) {
++ AlpineApkResource* res = new AlpineApkResource(
++ search.fileName(), AbstractResource::Technical, this);
+ res->setSize(666);
+ res->setState(AbstractResource::None);
+ m_resources.insert(res->packageName(), res);
+ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
+- return new ResultsStream(QStringLiteral("DummyStream-local"), { res });
++ return new ResultsStream(QStringLiteral("AlpineApkStream-local"), { res });
+ }
+
+- auto res = search.scheme() == QLatin1String("dummy") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr;
++ AlpineApkResource *res = search.scheme() == QLatin1String("dummy")
++ ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' ')))
++ : nullptr;
+ if (!res) {
+ return new ResultsStream(QStringLiteral("DummyStream"), {});
+- } else
+- return new ResultsStream(QStringLiteral("DummyStream"), { res });
++ }
++ return new ResultsStream(QStringLiteral("DummyStream"), { res });
+ }
+
+ AbstractBackendUpdater* AlpineApkBackend::backendUpdater() const
+@@ -155,7 +167,8 @@ AbstractBackendUpdater* AlpineApkBackend::backendUpdater() const
+
+ AbstractReviewsBackend* AlpineApkBackend::reviewsBackend() const
+ {
+- //return m_reviews;
++ qCDebug(LOG_ALPINEAPK) << "reviewsBbackend(): we don't support reviews (";
++ // return m_reviews;
+ return nullptr;
+ }
+
+@@ -176,12 +189,14 @@ Transaction* AlpineApkBackend::removeApplication(AbstractResource* app)
+
+ void AlpineApkBackend::checkForUpdates()
+ {
+- if(m_fetching)
++ if(m_fetching) {
++ qCDebug(LOG_ALPINEAPK) << "checkForUpdates(): already fetching";
+ return;
++ }
++ qCDebug(LOG_ALPINEAPK) << "checkForUpdates()!";
+ toggleFetching();
+ populate(QStringLiteral("Moar"));
+ QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
+- qDebug() << "AlpineApkBackend::checkForUpdates";
+ }
+
+ QString AlpineApkBackend::displayName() const
+@@ -191,6 +206,7 @@ QString AlpineApkBackend::displayName() const
+
+ bool AlpineApkBackend::hasApplications() const
+ {
++ qCDebug(LOG_ALPINEAPK) << "hasApplications(), returning true";
+ return true;
+ }
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index d1cceaaa..5b4fd15c 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -19,6 +19,7 @@
+ ***************************************************************************/
+
+ #include "AlpineApkResource.h"
++#include "alpineapk_backend_logging.h" // generated by ECM
+ #include <Transaction/AddonList.h>
+ #include <krandom.h>
+ #include <QDesktopServices>
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 3f492185..60faa396 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -19,6 +19,7 @@
+ ***************************************************************************/
+
+ #include "AlpineApkSourcesBackend.h"
++#include "alpineapk_backend_logging.h" // generated by ECM
+ #include <QDebug>
+ #include <QAction>
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index 8b6b72a5..e3f8d634 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -21,6 +21,7 @@
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkBackend.h"
+ #include "AlpineApkResource.h"
++#include "alpineapk_backend_logging.h" // generated by ECM
+ #include <QTimer>
+ #include <QDebug>
+ #include <KRandom>
+--
+GitLab
+
+
+From a30bdd843e68f7ace16fbbb5ea84a28afc1a8f78 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jan 2020 18:11:31 +0300
+Subject: [PATCH 07/62] CMake: link Apk backend with apk-qt
+
+---
+ libdiscover/backends/AlpineApkBackend/CMakeLists.txt | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 2bf3da97..45a56eb6 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -33,6 +33,7 @@ target_link_libraries(
+ KF5::CoreAddons
+ KF5::ConfigCore
+ Discover::Common
++ apk-qt
+ )
+
+ install(
+--
+GitLab
+
+
+From d0f38b3148432a99f0129cfc0c7afe0c302e0f04 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jan 2020 18:12:11 +0300
+Subject: [PATCH 08/62] Fix copyright years
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.h | 2 +-
+ .../backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h | 2 +-
+ 8 files changed, 8 insertions(+), 8 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 56d01e61..778093aa 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 38602348..aa520f96 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index 5b4fd15c..9348f1ed 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+index f4ecee4a..a1393ea8 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 60faa396..9ca9862d 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+index 48a0d829..d085f087 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index e3f8d634..18ba5209 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+index 49102dd5..8739eca1 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+@@ -1,5 +1,5 @@
+ /***************************************************************************
+- * Copyright © 2019 Alexey Min <alexey.min@gmail.com> *
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+--
+GitLab
+
+
+From 132fbd9d51a04d5bb1506c379693fa282506e6e1 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jan 2020 18:21:21 +0300
+Subject: [PATCH 09/62] Apk sources backend: show real repos URLs (read-only
+ now)
+
+---
+ .../AlpineApkSourcesBackend.cpp | 116 +++++++++++++-----
+ .../AlpineApkSourcesBackend.h | 15 +--
+ 2 files changed, 92 insertions(+), 39 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 9ca9862d..09a86bdf 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -22,74 +22,126 @@
+ #include "alpineapk_backend_logging.h" // generated by ECM
+ #include <QDebug>
+ #include <QAction>
++#include <QVector>
+
+-AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend * parent)
++// libapk-qt
++#include <QtApk.h>
++
++AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *parent)
+ : AbstractSourcesBackend(parent)
+- , m_sources(new QStandardItemModel(this))
+- , m_testAction(new QAction(QIcon::fromTheme(QStringLiteral("kalgebra")), QStringLiteral("DummyAction"), this))
++ , m_sourcesModel(new QStandardItemModel(this))
++ , m_refreshAction(new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")),
++ QStringLiteral("Refresh"), this))
+ {
+- for (int i = 0; i<10; ++i)
+- addSource(QStringLiteral("DummySource%1").arg(i));
++ loadSources();
++ QObject::connect(m_refreshAction, &QAction::triggered,
++ this, &AlpineApkSourcesBackend::loadSources);
++
++ // can be used to track enabling/disabling repo source
++ // QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) {
++ // qCDebug(LOG_ALPINEAPK) << "DummySource changed" << item << item->checkState();
++ // });
++}
+
+- connect(m_testAction, &QAction::triggered, [](){ qDebug() << "action triggered!"; });
+- connect(m_sources, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) { qDebug() << "DummySource changed" << item << item->checkState(); });
++QAbstractItemModel *AlpineApkSourcesBackend::sources()
++{
++ return m_sourcesModel;
++}
++
++bool AlpineApkSourcesBackend::addSource(const QString &id)
++{
++ return addSourceFull(id, QString(), true);
+ }
+
+-QAbstractItemModel* AlpineApkSourcesBackend::sources()
++QStandardItem *AlpineApkSourcesBackend::sourceForId(const QString& id) const
+ {
+- return m_sources;
++ for (int i = 0, c = m_sourcesModel->rowCount(); i < c; ++i) {
++ QStandardItem *it = m_sourcesModel->item(i, 0);
++ if (it->text() == id) {
++ return it;
++ }
++ }
++ return nullptr;
+ }
+
+-bool AlpineApkSourcesBackend::addSource(const QString& id)
++bool AlpineApkSourcesBackend::addSourceFull(const QString &id, const QString &comment, bool enabled)
+ {
+- if (id.isEmpty())
++ if (id.isEmpty()) {
+ return false;
++ }
+
+- QStandardItem* it = new QStandardItem(id);
++ qCDebug(LOG_ALPINEAPK) << "AlpineApkSourcesBackend: Adding source:" << id;
++
++ QStandardItem *it = new QStandardItem(id);
+ it->setData(id, AbstractSourcesBackend::IdRole);
+- it->setData(QVariant(id + QLatin1Char(' ') + id), Qt::ToolTipRole);
++ it->setData(comment, Qt::ToolTipRole);
+ it->setCheckable(true);
+- it->setCheckState(Qt::Checked);
+- m_sources->appendRow(it);
++ it->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
++ // for now, disable editing sources
++ it->setFlags(it->flags() & ~Qt::ItemIsEnabled);
++ m_sourcesModel->appendRow(it);
+ return true;
+ }
+
+-QStandardItem * AlpineApkSourcesBackend::sourceForId(const QString& id) const
++void AlpineApkSourcesBackend::loadSources()
+ {
+- for (int i=0, c=m_sources->rowCount(); i<c; ++i) {
+- const auto it = m_sources->item(i, 0);
+- if (it->text() == id)
+- return it;
++ QVector<QtApk::Repository> repos = QtApk::Database::getRepositories();
++ m_sourcesModel->clear();
++ for (const QtApk::Repository &repo: repos) {
++ addSourceFull(repo.url, repo.comment, repo.enabled);
+ }
+- return nullptr;
+ }
+
+-bool AlpineApkSourcesBackend::removeSource(const QString& id)
++bool AlpineApkSourcesBackend::removeSource(const QString &id)
+ {
+- const auto it = sourceForId(id);
++ const QStandardItem *it = sourceForId(id);
+ if (!it) {
+- qWarning() << "couldn't find " << id;
++ qCWarning(LOG_ALPINEAPK) << "AlpineApkSourcesBackend: couldn't find " << id;
+ return false;
+ }
+- return m_sources->removeRow(it->row());
++ return m_sourcesModel->removeRow(it->row());
++}
++
++QString AlpineApkSourcesBackend::idDescription()
++{
++ return QStringLiteral("Enter apk repository URL, for example: "
++ "http://dl-cdn.alpinelinux.org/alpine/edge/testing/");
+ }
+
+ QVariantList AlpineApkSourcesBackend::actions() const
+ {
+- return QVariantList() << QVariant::fromValue<QObject*>(m_testAction);
++ static const QVariantList s_actions {
++ QVariant::fromValue<QObject *>(m_refreshAction),
++ };
++ return s_actions;
++}
++
++bool AlpineApkSourcesBackend::supportsAdding() const
++{
++ return false; // for now, disable editing sources
++}
++
++bool AlpineApkSourcesBackend::canMoveSources() const
++{
++ return false; // for now, disable editing sources
+ }
+
+ bool AlpineApkSourcesBackend::moveSource(const QString& sourceId, int delta)
+ {
+ int row = sourceForId(sourceId)->row();
+- auto prevRow = m_sources->takeRow(row);
+- Q_ASSERT(!prevRow.isEmpty());
++ QList<QStandardItem *> prevRow = m_sourcesModel->takeRow(row);
++ if (prevRow.isEmpty()) {
++ return false;
++ }
+
+- const auto destRow = row + delta;
+- m_sources->insertRow(destRow, prevRow);
+- if (destRow == 0 || row == 0)
++ const int destRow = row + delta;
++ m_sourcesModel->insertRow(destRow, prevRow);
++ if (destRow == 0 || row == 0) {
+ Q_EMIT firstSourceIdChanged();
+- if (destRow == m_sources->rowCount() - 1 || row == m_sources->rowCount() - 1)
++ }
++ if (destRow == (m_sourcesModel->rowCount() - 1)
++ || row == (m_sourcesModel->rowCount() - 1)) {
+ Q_EMIT lastSourceIdChanged();
++ }
+ return true;
+ }
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+index d085f087..57894591 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+@@ -32,18 +32,19 @@ public:
+ QAbstractItemModel *sources() override;
+ bool addSource(const QString &id) override;
+ bool removeSource(const QString &id) override;
+- QString idDescription() override { return QStringLiteral("Random weird text"); }
++ QString idDescription() override;
+ QVariantList actions() const override;
+- bool supportsAdding() const override { return true; }
+-
+- bool canMoveSources() const override { return true; }
++ bool supportsAdding() const override;
++ bool canMoveSources() const override;
+ bool moveSource(const QString &sourceId, int delta) override;
+
+ private:
+- QStandardItem* sourceForId(const QString &id) const;
++ QStandardItem *sourceForId(const QString &id) const;
++ bool addSourceFull(const QString &id, const QString &comment, bool enabled);
++ void loadSources();
+
+- QStandardItemModel* m_sources;
+- QAction* m_testAction;
++ QStandardItemModel *m_sourcesModel = nullptr;
++ QAction *m_refreshAction = nullptr;
+ };
+
+ #endif // ALPINEAPKSOURCESBACKEND_H
+--
+GitLab
+
+
+From 6539ebc4b400d5116d89bd2f19b68a3656fbde0b Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jan 2020 20:27:31 +0300
+Subject: [PATCH 10/62] Cleanup of AlpipneApkResource.{h,cpp}
+
+---
+ .../AlpineApkBackend/AlpineApkResource.cpp | 153 ++++++++----------
+ .../AlpineApkBackend/AlpineApkResource.h | 34 ++--
+ 2 files changed, 88 insertions(+), 99 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index 9348f1ed..d827e7b3 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -18,46 +18,18 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
++#include <KRandom>
+ #include "AlpineApkResource.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+-#include <Transaction/AddonList.h>
+-#include <krandom.h>
+-#include <QDesktopServices>
+-#include <QStringList>
+-#include <QTimer>
+-
+-Q_GLOBAL_STATIC_WITH_ARGS(QVector<QString>, s_icons,
+- ({ QLatin1String("kdevelop"),
+- QLatin1String("kalgebra"),
+- QLatin1String("kmail"),
+- QLatin1String("akregator"),
+- QLatin1String("korganizer") }))
+-
+-AlpineApkResource::AlpineApkResource(
+- QString name,
+- AbstractResource::Type type,
+- AbstractResourcesBackend* parent)
++#include "Transaction/AddonList.h"
++
++AlpineApkResource::AlpineApkResource(const QtApk::Package &apkPkg,
++ AbstractResourcesBackend *parent)
+ : AbstractResource(parent)
+- , m_name(std::move(name))
+- , m_state(State::Broken)
+- , m_iconName((*s_icons)[KRandom::random() % s_icons->size()])
+- , m_addons({ PackageState(QStringLiteral("a"), QStringLiteral("aaaaaa"), false),
+- PackageState(QStringLiteral("b"), QStringLiteral("aaaaaa"), false),
+- PackageState(QStringLiteral("c"), QStringLiteral("aaaaaa"), false)})
+- , m_type(type)
+-{
+- const int nofScreenshots = KRandom::random() % 5;
+- m_screenshots = QList<QUrl>{
+- QUrl(QStringLiteral("https://screenshots.debian.net/screenshots/000/014/863/large.png")),
+- QUrl(QStringLiteral("https://c1.staticflickr.com/9/8479/8166397343_b78106f353_k.jpg")),
+- QUrl(QStringLiteral("https://c2.staticflickr.com/4/3685/9954407993_dad10a6943_k.jpg")),
+- QUrl(QStringLiteral("https://c1.staticflickr.com/1/653/22527103378_8ce572e1de_k.jpg")),
+- QUrl(QStringLiteral("https://images.unsplash.com/photo-1528744598421-b7b93e12df15?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60")),
+- QUrl(QStringLiteral("https://images.unsplash.com/photo-1552385430-53e6f2028760?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80")),
+- QUrl(QStringLiteral("https://images.unsplash.com/photo-1506810172640-8a9f77cb1472?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80")),
+-
+- }.mid(nofScreenshots);
+- m_screenshotThumbnails = m_screenshots;
++ , m_state(AbstractResource::State::None)
++ , m_type(Application)
++ , m_pkg(apkPkg)
++{
+ }
+
+ QList<PackageState> AlpineApkResource::addonsInformation()
+@@ -67,103 +39,87 @@ QList<PackageState> AlpineApkResource::addonsInformation()
+
+ QString AlpineApkResource::availableVersion() const
+ {
+- return QStringLiteral("3.0");
++ return m_pkg.version;
+ }
+
+ QStringList AlpineApkResource::categories()
+ {
+- return { QStringLiteral("dummy"),
+- m_name.endsWith(QLatin1Char('3'))
+- ? QStringLiteral("three")
+- : QStringLiteral("notthree") };
++ return { m_category };
+ }
+
+ QString AlpineApkResource::comment()
+ {
+- return QStringLiteral("A reasonably short comment ") + name();
++ return m_pkg.description;
+ }
+
+ int AlpineApkResource::size()
+ {
+- return m_size;
++ return static_cast<int>(m_pkg.size);
+ }
+
+ QUrl AlpineApkResource::homepage()
+ {
+- return QUrl(QStringLiteral("https://kde.org"));
++ return QUrl::fromUserInput(m_pkg.url);
+ }
+
+ QUrl AlpineApkResource::helpURL()
+ {
+- return QUrl(QStringLiteral("http://very-very-excellent-docs.lol"));
++ return QUrl();
+ }
+
+ QUrl AlpineApkResource::bugURL()
+ {
+- return QUrl(QStringLiteral("file:///dev/null"));
++ return QUrl();
+ }
+
+ QUrl AlpineApkResource::donationURL()
+ {
+- return QUrl(QStringLiteral("https://youtu.be/0o8XMlL8rqY"));
++ return QUrl();
+ }
+
+ QVariant AlpineApkResource::icon() const
+ {
+- static const QVector<QVariant> icons = {
+- QStringLiteral("device-notifier"),
+- QStringLiteral("media-floppy"),
+- QStringLiteral("drink-beer")
+- };
+- return icons[type()];
++ return QStringLiteral("package-x-generic");
+ }
+
+ QString AlpineApkResource::installedVersion() const
+ {
+- return QStringLiteral("2.3");
++ return m_pkg.version;
+ }
+
+ QJsonArray AlpineApkResource::licenses()
+ {
+ return {
+ QJsonObject {
+- { QStringLiteral("name"), QStringLiteral("GPL") },
+- { QStringLiteral("url"), QStringLiteral("https://kde.org") }
++ { QStringLiteral("name"), m_pkg.license },
++ { QStringLiteral("url"), QStringLiteral("https://spdx.org/license-list") },
+ }
+ };
+ }
+
+ QString AlpineApkResource::longDescription()
+ {
+- return QStringLiteral("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ultricies consequat nulla, ut vulputate nulla ultricies ac. Suspendisse lacinia commodo lacus, non tristique mauris dictum vitae. Sed adipiscing augue nec nisi aliquet viverra. Etiam sit amet nulla in tellus consectetur feugiat. Cras in sem tortor. Fusce a nulla at justo accumsan gravida. Maecenas dui felis, lacinia at ornare sed, aliquam et purus. Sed ut sagittis lacus. Etiam dictum pharetra rhoncus. Suspendisse auctor orci ipsum. Pellentesque vitae urna nec felis consequat lobortis dictum in urna. Phasellus a mi ac leo adipiscing varius eget a felis. Cras magna augue, commodo sed placerat vel, tempus vel ligula. In feugiat quam quis est lobortis sed accumsan nunc malesuada. Mauris quis massa sit amet felis tempus suscipit a quis diam.\n\n"
+-
+- "Aenean quis nulla erat, vel sagittis sem. Praesent vitae mauris arcu. Cras porttitor, ante at scelerisque sodales, nibh felis consectetur orci, ut hendrerit urna urna non urna. Duis eu magna id mi scelerisque adipiscing. Aliquam sed quam in eros sodales accumsan. Phasellus tempus sagittis suscipit. Aliquam rutrum dictum justo ut viverra. Nulla felis sem, molestie sed scelerisque non, consequat vitae nulla. Aliquam ullamcorper malesuada mi, vel vestibulum magna vulputate eget. In hac habitasse platea dictumst. Cras sed lacus dui, vel semper sem. Aenean sodales porta leo vel fringilla.\n\n"
+-
+- "Ut tempus massa et urna porta non mollis metus ultricies. Duis nec nulla ac metus auctor porta id et mi. Mauris aliquam nibh a ligula malesuada sed tincidunt nibh varius. Sed felis metus, porta et adipiscing non, faucibus id leo. Donec ipsum nibh, hendrerit eget aliquam nec, tempor ut mauris. Suspendisse potenti. Vestibulum scelerisque adipiscing libero tristique eleifend. Donec quis tortor eget elit mollis iaculis ac sit amet nisi. Proin non massa sed nunc rutrum pellentesque. Sed dui lectus, laoreet sed condimentum id, commodo sed urna.\n\n"
+-
+- "Praesent tincidunt mattis massa mattis porta. Nullam posuere neque at mauris vestibulum vitae elementum leo sodales. Quisque condimentum lectus in libero luctus egestas. Fusce tempor neque ac dui tincidunt eget viverra quam suscipit. In hac habitasse platea dictumst. Etiam metus mi, adipiscing nec suscipit id, aliquet sed sem. Duis urna ligula, ornare sed vestibulum vel, molestie ac nisi. Morbi varius iaculis ligula. Nunc in augue leo, sit amet aliquam elit. Suspendisse rutrum sem diam. Proin eu orci nisl. Praesent porttitor dignissim est, id fermentum arcu venenatis vitae.\n\n"
+-
+- "Integer in sapien eget quam vulputate lobortis. Morbi nibh elit, elementum vitae vehicula sed, consequat nec erat. Donec placerat porttitor est ut dapibus. Fusce augue orci, dictum et convallis vel, blandit eu tortor. Phasellus non eros nulla. In iaculis nulla fermentum nulla gravida eu mattis purus consectetur. Integer dui nunc, sollicitudin ac tincidunt nec, hendrerit bibendum nunc. Proin sit amet augue ac velit egestas varius. Sed eu ante quis orci vestibulum sagittis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus vitae urna odio, at molestie leo. In convallis neque vel mi dictum convallis lobortis turpis sagittis.\n\n");
++ return m_pkg.description;
+ }
+
+ QString AlpineApkResource::name() const
+ {
+- return m_name;
++ return m_pkg.name;
+ }
+
+ QString AlpineApkResource::origin() const
+ {
+- return QStringLiteral("DummySource1");
++ return m_originSoruce;
+ }
+
+ QString AlpineApkResource::packageName() const
+ {
+- return m_name;
++ return m_pkg.name;
+ }
+
+ QString AlpineApkResource::section()
+ {
+- return QStringLiteral("dummy");
++ return m_sectionName;
+ }
+
+ AbstractResource::State AlpineApkResource::state()
+@@ -173,15 +129,13 @@ AbstractResource::State AlpineApkResource::state()
+
+ void AlpineApkResource::fetchChangelog()
+ {
+- QString log = longDescription();
+- log.replace(QLatin1Char('\n'), QLatin1String("<br />"));
+-
+- emit changelogFetched(log);
++ // QString log = longDescription();
++ // Q_EMIT changelogFetched(log);
+ }
+
+ void AlpineApkResource::fetchScreenshots()
+ {
+- Q_EMIT screenshotsFetched(m_screenshotThumbnails, m_screenshots);
++ // Q_EMIT screenshotsFetched(m_screenshotThumbnails, m_screenshots);
+ }
+
+ void AlpineApkResource::setState(AbstractResource::State state)
+@@ -190,19 +144,36 @@ void AlpineApkResource::setState(AbstractResource::State state)
+ emit stateChanged();
+ }
+
+-void AlpineApkResource::setAddons(const AddonList& addons)
++void AlpineApkResource::setCategoryName(const QString &categoryName)
++{
++ m_category = categoryName;
++}
++
++void AlpineApkResource::setOriginSource(const QString &originSource)
++{
++ m_originSoruce = originSource;
++}
++
++void AlpineApkResource::setSection(const QString &sectionName)
++{
++ m_sectionName = sectionName;
++}
++
++void AlpineApkResource::setAddons(const AddonList &addons)
+ {
+- Q_FOREACH (const QString& toInstall, addons.addonsToInstall()) {
++ const QStringList addonsToInstall = addons.addonsToInstall();
++ for (const QString &toInstall : addonsToInstall) {
+ setAddonInstalled(toInstall, true);
+ }
+- Q_FOREACH (const QString& toRemove, addons.addonsToRemove()) {
++ const QStringList addonsToRemove = addons.addonsToRemove();
++ for (const QString &toRemove : addonsToRemove) {
+ setAddonInstalled(toRemove, false);
+ }
+ }
+
+-void AlpineApkResource::setAddonInstalled(const QString& addon, bool installed)
++void AlpineApkResource::setAddonInstalled(const QString &addon, bool installed)
+ {
+- for(auto & elem : m_addons) {
++ for(PackageState &elem : m_addons) {
+ if(elem.name() == addon) {
+ elem.setInstalled(installed);
+ }
+@@ -212,12 +183,26 @@ void AlpineApkResource::setAddonInstalled(const QString& addon, bool installed)
+
+ void AlpineApkResource::invokeApplication() const
+ {
+- QDesktopServices d;
+- d.openUrl(QUrl(QStringLiteral("https://projects.kde.org/projects/extragear/sysadmin/muon")));
++ // QDesktopServices d;
++ // d.openUrl(QUrl(QStringLiteral("https://projects.kde.org/projects/extragear/sysadmin/muon")));
+ }
+
+ QUrl AlpineApkResource::url() const
+ {
+- return QUrl(QLatin1String("dummy://")
+- + packageName().replace(QLatin1Char(' '), QLatin1Char('.')));
++ return QUrl(QLatin1String("apk://") + packageName());
++}
++
++QString AlpineApkResource::author() const
++{
++ return m_pkg.maintainer;
++}
++
++QString AlpineApkResource::sourceIcon() const
++{
++ return QStringLiteral("player-time");
++}
++
++QDate AlpineApkResource::releaseDate() const
++{
++ return m_pkg.buildTime.date();
+ }
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+index a1393ea8..79b100e1 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -22,13 +22,16 @@
+ #define ALPINEAPKRESOURCE_H
+
+ #include <resources/AbstractResource.h>
++#include <QtApkPackage.h>
+
+ class AddonList;
++
+ class AlpineApkResource : public AbstractResource
+ {
+-Q_OBJECT
++ Q_OBJECT
++
+ public:
+- explicit AlpineApkResource(QString name, AbstractResource::Type type, AbstractResourcesBackend* parent);
++ explicit AlpineApkResource(const QtApk::Package &apkPkg, AbstractResourcesBackend *parent);
+
+ QList<PackageState> addonsInformation() override;
+ QString section() override;
+@@ -54,24 +57,25 @@ public:
+ void fetchChangelog() override;
+ void fetchScreenshots() override;
+ QUrl url() const override;
+- QString author() const override { return QStringLiteral("BananaPerson"); }
+- void setState(State state);
+- void setSize(int size) { m_size = size; }
+- void setAddons(const AddonList& addons);
++ QString author() const override;
++ QString sourceIcon() const override;
++ QDate releaseDate() const override;
+
+- void setAddonInstalled(const QString& addon, bool installed);
+- QString sourceIcon() const override { return QStringLiteral("player-time"); }
+- QDate releaseDate() const override { return {}; }
++ void setState(State state);
++ void setCategoryName(const QString &categoryName);
++ void setOriginSource(const QString &originSource);
++ void setSection(const QString &sectionName);
++ void setAddons(const AddonList &addons);
++ void setAddonInstalled(const QString &addon, bool installed);
+
+ public:
+- const QString m_name;
+ AbstractResource::State m_state;
+- QList<QUrl> m_screenshots;
+- QList<QUrl> m_screenshotThumbnails;
+- QString m_iconName;
+- QList<PackageState> m_addons;
+ const AbstractResource::Type m_type;
+- int m_size;
++ QtApk::Package m_pkg;
++ QString m_category;
++ QString m_originSoruce;
++ QString m_sectionName;
++ QList<PackageState> m_addons;
+ };
+
+ #endif // ALPINEAPKRESOURCE_H
+--
+GitLab
+
+
+From 276455939f8cf00fa556fafdf44337aa11ae011c Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jan 2020 20:32:46 +0300
+Subject: [PATCH 11/62] Cleanup of AlpineApkBackend.{h,cpp}
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 157 ++++++++++--------
+ .../AlpineApkBackend/AlpineApkBackend.h | 38 +++--
+ 2 files changed, 114 insertions(+), 81 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 778093aa..f5aa5477 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -25,9 +25,10 @@
+ #include "AlpineApkSourcesBackend.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+
+-#include <resources/StandardBackendUpdater.h>
+-#include <resources/SourcesModel.h>
+-#include <Transaction/Transaction.h>
++#include "resources/StandardBackendUpdater.h"
++#include "resources/SourcesModel.h"
++#include "Transaction/Transaction.h"
++#include "Category/Category.h"
+
+ #include <KAboutData>
+ #include <KLocalizedString>
+@@ -42,11 +43,9 @@
+
+ DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
+
+-AlpineApkBackend::AlpineApkBackend(QObject* parent)
++AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ : AbstractResourcesBackend(parent)
+ , m_updater(new StandardBackendUpdater(this))
+- //, m_reviews(new DummyReviewsBackend(this))
+- , m_fetching(true)
+ , m_startElements(120)
+ {
+ #ifndef QT_DEBUG
+@@ -59,43 +58,59 @@ AlpineApkBackend::AlpineApkBackend(QObject* parent)
+ //connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady);
+ connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &AlpineApkBackend::updatesCountChanged);
+
+- populate(QStringLiteral("Dummy"));
++ populate();
+ //if (!m_fetching)
+ // m_reviews->initialize();
+
+ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+ }
+
+-void AlpineApkBackend::populate(const QString& n)
++QVector<Category *> AlpineApkBackend::category() const
+ {
+- qCDebug(LOG_ALPINEAPK) << "populate! (" << n << ")";
+-
+- const int start = m_resources.count();
+- for(int i=start; i<start+m_startElements; i++) {
+- const QString name = n+QLatin1Char(' ')+QString::number(i);
+- AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Application, this);
+- res->setSize(100+(m_startElements-i));
+- res->setState(AbstractResource::State(1+(i%3)));
+- m_resources.insert(name.toLower(), res);
+- connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
+- }
++ static Category *cat = new Category(
++ QStringLiteral("All applications"), // displayName
++ QStringLiteral("applications-other"), // icon
++ { }, // orFilters
++ { displayName() }, // pluginName
++ { }, // subCategories
++ QUrl(), // decoration (what is it?)
++ false // isAddons
++ );
++ return { cat };
++}
++
++void AlpineApkBackend::populate()
++{
++ qCDebug(LOG_ALPINEAPK) << "populating resources...";
+
+- for(int i=start; i<start+m_startElements; i++) {
+- const QString name = QLatin1String("addon")+QString::number(i);
+- AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Addon, this);
+- res->setState(AbstractResource::State(1+(i%3)));
+- res->setSize(300+(m_startElements-i));
+- m_resources.insert(name, res);
+- connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ if (m_apkdb.open(QtApk::Database::QTAPK_OPENF_READONLY)) {
++ m_availablePackages = m_apkdb.getAvailablePackages();
++ m_installedPackages = m_apkdb.getInstalledPackages();
++ m_apkdb.close();
+ }
+
+- for(int i=start; i<start+m_startElements; i++) {
+- const QString name = QLatin1String("techie")+QString::number(i);
+- AlpineApkResource* res = new AlpineApkResource(name, AbstractResource::Technical, this);
+- res->setState(AbstractResource::State(1+(i%3)));
+- res->setSize(300+(m_startElements-i));
+- m_resources.insert(name, res);
+- connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ if (m_availablePackages.size() > 0) {
++ for (const QtApk::Package &pkg: m_availablePackages) {
++ AlpineApkResource *res = new AlpineApkResource(pkg, this);
++ res->setCategoryName(QStringLiteral("all"));
++ res->setOriginSource(QStringLiteral("apk"));
++ res->setSection(QStringLiteral("dummy"));
++ const QString key = pkg.name.toLower();
++ m_resources.insert(key, res);
++ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ }
++ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
++ << "packages";
++ }
++ if (m_installedPackages.size() > 0) {
++ for (const QtApk::Package &pkg: m_installedPackages) {
++ const QString key = pkg.name.toLower();
++ if (m_resources.contains(key)) {
++ m_resources.value(key)->setState(AbstractResource::Installed);
++ }
++ }
++ qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
++ << "packages";
+ }
+ }
+
+@@ -114,22 +129,20 @@ int AlpineApkBackend::updatesCount() const
+ return m_updater->updatesCount();
+ }
+
+-ResultsStream* AlpineApkBackend::search(const AbstractResourcesBackend::Filters& filter)
++ResultsStream *AlpineApkBackend::search(const AbstractResourcesBackend::Filters &filter)
+ {
+ QVector<AbstractResource*> ret;
+ if (!filter.resourceUrl.isEmpty()) {
+ return findResourceByPackageName(filter.resourceUrl);
+ } else {
+- for (AbstractResource* r: qAsConst(m_resources)) {
++ for (AbstractResource *r: qAsConst(m_resources)) {
+ if (r->type() == AbstractResource::Technical
+ && filter.state != AbstractResource::Upgradeable) {
+ continue;
+ }
+-
+ if (r->state() < filter.state) {
+ continue;
+ }
+-
+ if(r->name().contains(filter.search, Qt::CaseInsensitive)
+ || r->comment().contains(filter.search, Qt::CaseInsensitive)) {
+ ret += r;
+@@ -139,52 +152,67 @@ ResultsStream* AlpineApkBackend::search(const AbstractResourcesBackend::Filters&
+ return new ResultsStream(QStringLiteral("AlpineApkStream"), ret);
+ }
+
+-ResultsStream * AlpineApkBackend::findResourceByPackageName(const QUrl& search)
++ResultsStream *AlpineApkBackend::findResourceByPackageName(const QUrl &searchUrl)
+ {
+- if (search.isLocalFile()) {
+- AlpineApkResource* res = new AlpineApkResource(
+- search.fileName(), AbstractResource::Technical, this);
+- res->setSize(666);
+- res->setState(AbstractResource::None);
+- m_resources.insert(res->packageName(), res);
+- connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
+- return new ResultsStream(QStringLiteral("AlpineApkStream-local"), { res });
++// if (search.isLocalFile()) {
++// AlpineApkResource* res = new AlpineApkResource(
++// search.fileName(), AbstractResource::Technical, this);
++// res->setSize(666);
++// res->setState(AbstractResource::None);
++// m_resources.insert(res->packageName(), res);
++// connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++// return new ResultsStream(QStringLiteral("AlpineApkStream-local"), { res });
++// }
++
++ AlpineApkResource *result = nullptr;
++
++ // QUrl("appstream://org.kde.krita.desktop")
++ // smart workaround for appstream
++ if (searchUrl.scheme() == QLatin1String("appstream")) {
++ // remove leading "org.kde."
++ QString pkgName = searchUrl.host();
++ if (pkgName.startsWith(QLatin1String("org.kde."))) {
++ pkgName = pkgName.mid(8);
++ }
++ // remove trailing ".desktop"
++ if (pkgName.endsWith(QLatin1String(".desktop"))) {
++ pkgName = pkgName.left(pkgName.length() - 8);
++ }
++ // now we can search for "krita" package
++ result = m_resources.value(pkgName);
+ }
+
+- AlpineApkResource *res = search.scheme() == QLatin1String("dummy")
+- ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' ')))
+- : nullptr;
+- if (!res) {
+- return new ResultsStream(QStringLiteral("DummyStream"), {});
++ if (!result) {
++ return new ResultsStream(QStringLiteral("AlpineApkStream"), {});
+ }
+- return new ResultsStream(QStringLiteral("DummyStream"), { res });
++ return new ResultsStream(QStringLiteral("AlpineApkStream"), { result });
+ }
+
+-AbstractBackendUpdater* AlpineApkBackend::backendUpdater() const
++AbstractBackendUpdater *AlpineApkBackend::backendUpdater() const
+ {
+ return m_updater;
+ }
+
+-AbstractReviewsBackend* AlpineApkBackend::reviewsBackend() const
++AbstractReviewsBackend *AlpineApkBackend::reviewsBackend() const
+ {
+- qCDebug(LOG_ALPINEAPK) << "reviewsBbackend(): we don't support reviews (";
++ // qCDebug(LOG_ALPINEAPK) << "reviewsBbackend(): we don't support reviews (";
+ // return m_reviews;
+ return nullptr;
+ }
+
+-Transaction* AlpineApkBackend::installApplication(AbstractResource* app, const AddonList& addons)
++Transaction* AlpineApkBackend::installApplication(AbstractResource *app, const AddonList &addons)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), addons, Transaction::InstallRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), addons, Transaction::InstallRole);
+ }
+
+-Transaction* AlpineApkBackend::installApplication(AbstractResource* app)
++Transaction* AlpineApkBackend::installApplication(AbstractResource *app)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::InstallRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), Transaction::InstallRole);
+ }
+
+-Transaction* AlpineApkBackend::removeApplication(AbstractResource* app)
++Transaction* AlpineApkBackend::removeApplication(AbstractResource *app)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource*>(app), Transaction::RemoveRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), Transaction::RemoveRole);
+ }
+
+ void AlpineApkBackend::checkForUpdates()
+@@ -195,18 +223,17 @@ void AlpineApkBackend::checkForUpdates()
+ }
+ qCDebug(LOG_ALPINEAPK) << "checkForUpdates()!";
+ toggleFetching();
+- populate(QStringLiteral("Moar"));
+- QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
++ // populate(QStringLiteral("Moar"));
++ QTimer::singleShot(1000, this, &AlpineApkBackend::toggleFetching);
+ }
+
+ QString AlpineApkBackend::displayName() const
+ {
+- return QStringLiteral("Alpine APK");
++ return QStringLiteral("Alpine APK backend");
+ }
+
+ bool AlpineApkBackend::hasApplications() const
+ {
+- qCDebug(LOG_ALPINEAPK) << "hasApplications(), returning true";
+ return true;
+ }
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index aa520f96..624df1c9 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -24,7 +24,9 @@
+ #include <resources/AbstractResourcesBackend.h>
+ #include <QVariantList>
+
+-//class DummyReviewsBackend;
++#include <QtApk.h>
++
++// class DummyReviewsBackend;
+ class StandardBackendUpdater;
+ class AlpineApkResource;
+ class AlpineApkBackend : public AbstractResourcesBackend
+@@ -33,19 +35,20 @@ class AlpineApkBackend : public AbstractResourcesBackend
+ Q_PROPERTY(int startElements MEMBER m_startElements)
+
+ public:
+- explicit AlpineApkBackend(QObject* parent = nullptr);
++ explicit AlpineApkBackend(QObject *parent = nullptr);
+
++ QVector<Category *> category() const override;
+ int updatesCount() const override;
+- AbstractBackendUpdater* backendUpdater() const override;
+- AbstractReviewsBackend* reviewsBackend() const override;
+- ResultsStream* search(const AbstractResourcesBackend::Filters & search) override;
+- ResultsStream * findResourceByPackageName(const QUrl& search);
+- QHash<QString, AlpineApkResource*> resources() const { return m_resources; }
++ AbstractBackendUpdater *backendUpdater() const override;
++ AbstractReviewsBackend *reviewsBackend() const override;
++ ResultsStream *search(const AbstractResourcesBackend::Filters &filter) override;
++ ResultsStream *findResourceByPackageName(const QUrl &search);
++ QHash<QString, AlpineApkResource *> resources() const { return m_resources; }
+ bool isValid() const override { return true; } // No external file dependencies that could cause runtime errors
+
+- Transaction* installApplication(AbstractResource* app) override;
+- Transaction* installApplication(AbstractResource* app, const AddonList& addons) override;
+- Transaction* removeApplication(AbstractResource* app) override;
++ Transaction *installApplication(AbstractResource *app) override;
++ Transaction *installApplication(AbstractResource *app, const AddonList &addons) override;
++ Transaction *removeApplication(AbstractResource *app) override;
+ bool isFetching() const override { return m_fetching; }
+ void checkForUpdates() override;
+ QString displayName() const override;
+@@ -55,13 +58,16 @@ public Q_SLOTS:
+ void toggleFetching();
+
+ private:
+- void populate(const QString& name);
++ void populate();
+
+- QHash<QString, AlpineApkResource*> m_resources;
+- StandardBackendUpdater* m_updater;
+- //DummyReviewsBackend* m_reviews;
+- bool m_fetching;
+- int m_startElements;
++ QHash<QString, AlpineApkResource *> m_resources;
++ StandardBackendUpdater *m_updater;
++ // DummyReviewsBackend* m_reviews;
++ QtApk::Database m_apkdb;
++ QVector<QtApk::Package> m_availablePackages;
++ QVector<QtApk::Package> m_installedPackages;
++ bool m_fetching = true;
++ int m_startElements = 0;
+ };
+
+ #endif // AlpineApkBackend_H
+--
+GitLab
+
+
+From beb4fd446747223357f1763e777ca5db44e32929 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 16:52:32 +0300
+Subject: [PATCH 12/62] WIP on AlpineApkTransaction.{h,cpp}
+
+---
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 11 ++++++-----
+ .../backends/AlpineApkBackend/AlpineApkTransaction.h | 6 +++---
+ 2 files changed, 9 insertions(+), 8 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index 18ba5209..b4f90df5 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -28,12 +28,12 @@
+
+ // #define TEST_PROCEED
+
+-AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource* app, Role role)
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, Role role)
+ : AlpineApkTransaction(app, {}, role)
+ {
+ }
+
+-AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource* app, const AddonList& addons, Transaction::Role role)
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, const AddonList &addons, Transaction::Role role)
+ : Transaction(app->backend(), app, role, addons)
+ , m_app(app)
+ {
+@@ -44,11 +44,12 @@ AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource* app, const AddonLi
+
+ void AlpineApkTransaction::iterateTransaction()
+ {
+- if (!m_iterate)
++ if (!m_iterate) {
+ return;
++ }
+
+- if(progress()<100) {
+- setProgress(qBound(0, progress()+(KRandom::random()%30), 100));
++ if(progress() < 100) {
++ setProgress(qBound(0, progress() + (KRandom::random() % 30), 100));
+ QTimer::singleShot(/*KRandom::random()%*/100, this, &AlpineApkTransaction::iterateTransaction);
+ } else if (status() == DownloadingStatus) {
+ setStatus(CommittingStatus);
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+index 8739eca1..63aeef8d 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+@@ -28,8 +28,8 @@ class AlpineApkTransaction : public Transaction
+ {
+ Q_OBJECT
+ public:
+- AlpineApkTransaction(AlpineApkResource* app, Role role);
+- AlpineApkTransaction(AlpineApkResource* app, const AddonList& list, Role role);
++ AlpineApkTransaction(AlpineApkResource *app, Role role);
++ AlpineApkTransaction(AlpineApkResource *app, const AddonList &list, Role role);
+
+ void cancel() override;
+ void proceed() override;
+@@ -40,7 +40,7 @@ class AlpineApkTransaction : public Transaction
+
+ private:
+ bool m_iterate = true;
+- AlpineApkResource* m_app;
++ AlpineApkResource *m_app;
+ };
+
+ #endif // ALPINEAPKTRANSACTION_H
+--
+GitLab
+
+
+From 38d4444f1a4e5b3c8e76271fdec6273e3c59bc72 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 17:35:17 +0300
+Subject: [PATCH 13/62] CMake: Add AlpineApkReviewsBackend + its files
+
+---
+ .../AlpineApkReviewsBackend.cpp | 35 +++++++++++++
+ .../AlpineApkReviewsBackend.h | 52 +++++++++++++++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 2 +
+ 3 files changed, 89 insertions(+)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
+new file mode 100644
+index 00000000..fd7ad47f
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
+@@ -0,0 +1,35 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkReviewsBackend.h"
++#include "AlpineApkBackend.h"
++#include "resources/AbstractResource.h"
++
++AlpineApkReviewsBackend::AlpineApkReviewsBackend(AlpineApkBackend *parent)
++ : AbstractReviewsBackend(parent)
++{
++}
++
++void AlpineApkReviewsBackend::fetchReviews(AbstractResource *app, int page)
++{
++ Q_UNUSED(page)
++ static const QVector<ReviewPtr> reviews;
++ Q_EMIT reviewsReady(app, reviews, false);
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
+new file mode 100644
+index 00000000..435f845b
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
+@@ -0,0 +1,52 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef ALPINEAPKREVIEWSBACKEND_H
++#define ALPINEAPKREVIEWSBACKEND_H
++
++#include "ReviewsBackend/AbstractReviewsBackend.h"
++
++class AlpineApkBackend;
++
++class AlpineApkReviewsBackend : public AbstractReviewsBackend
++{
++ Q_OBJECT
++
++public:
++ explicit AlpineApkReviewsBackend(AlpineApkBackend *parent = nullptr);
++
++ QString userName() const override { return QStringLiteral("dummy"); }
++ void login() override {}
++ void logout() override {}
++ void registerAndLogin() override {}
++
++ Rating *ratingForApplication(AbstractResource *) const override { return nullptr; }
++ bool hasCredentials() const override { return false; }
++ void deleteReview(Review *) override {}
++ void fetchReviews(AbstractResource *app, int page = 1) override;
++ bool isFetching() const override { return false; }
++ bool isReviewable() const override { return false; }
++ void submitReview(AbstractResource *, const QString &, const QString &, const QString &) override {}
++ void flagReview(Review *, const QString&, const QString&) override {}
++ void submitUsefulness(Review *, bool) override {}
++ bool isResourceSupported(AbstractResource *) const override { return false; }
++};
++
++#endif // ALPINEAPKREVIEWSBACKEND_H
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 45a56eb6..7d2b86e8 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -5,6 +5,8 @@ set(alpineapkbackend_SRCS
+ AlpineApkBackend.h
+ AlpineApkResource.cpp
+ AlpineApkResource.h
++ AlpineApkReviewsBackend.cpp
++ AlpineApkReviewsBackend.h
+ AlpineApkSourcesBackend.cpp
+ AlpineApkSourcesBackend.h
+ AlpineApkTransaction.cpp
+--
+GitLab
+
+
+From bf1e9e0abc37b45c9a05c5dac8bc66050329666b Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 17:35:43 +0300
+Subject: [PATCH 14/62] AlpineApkBackend: use own reviews backend
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 15 +++++----------
+ .../backends/AlpineApkBackend/AlpineApkBackend.h | 4 ++--
+ 2 files changed, 7 insertions(+), 12 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index f5aa5477..de93609a 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -20,7 +20,7 @@
+
+ #include "AlpineApkBackend.h"
+ #include "AlpineApkResource.h"
+-//#include "DummyReviewsBackend.h"
++#include "AlpineApkReviewsBackend.h"
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkSourcesBackend.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+@@ -46,6 +46,7 @@ DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
+ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ : AbstractResourcesBackend(parent)
+ , m_updater(new StandardBackendUpdater(this))
++ , m_reviews(new AlpineApkReviewsBackend(this))
+ , m_startElements(120)
+ {
+ #ifndef QT_DEBUG
+@@ -55,12 +56,9 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ qCDebug(LOG_ALPINEAPK) << "constructing backend!";
+
+ QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
+- //connect(m_reviews, &DummyReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady);
+ connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &AlpineApkBackend::updatesCountChanged);
+
+ populate();
+- //if (!m_fetching)
+- // m_reviews->initialize();
+
+ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+ }
+@@ -117,10 +115,9 @@ void AlpineApkBackend::populate()
+ void AlpineApkBackend::toggleFetching()
+ {
+ m_fetching = !m_fetching;
++
+ qCDebug(LOG_ALPINEAPK) << "fetching..." << m_fetching;
+ emit fetchingChanged();
+- //if (!m_fetching)
+- // m_reviews->initialize();
+ }
+
+ int AlpineApkBackend::updatesCount() const
+@@ -195,9 +192,7 @@ AbstractBackendUpdater *AlpineApkBackend::backendUpdater() const
+
+ AbstractReviewsBackend *AlpineApkBackend::reviewsBackend() const
+ {
+- // qCDebug(LOG_ALPINEAPK) << "reviewsBbackend(): we don't support reviews (";
+- // return m_reviews;
+- return nullptr;
++ return m_reviews;
+ }
+
+ Transaction* AlpineApkBackend::installApplication(AbstractResource *app, const AddonList &addons)
+@@ -221,7 +216,7 @@ void AlpineApkBackend::checkForUpdates()
+ qCDebug(LOG_ALPINEAPK) << "checkForUpdates(): already fetching";
+ return;
+ }
+- qCDebug(LOG_ALPINEAPK) << "checkForUpdates()!";
++ qCDebug(LOG_ALPINEAPK) << "checkForUpdates() start!";
+ toggleFetching();
+ // populate(QStringLiteral("Moar"));
+ QTimer::singleShot(1000, this, &AlpineApkBackend::toggleFetching);
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 624df1c9..f8d3aa69 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -26,7 +26,7 @@
+
+ #include <QtApk.h>
+
+-// class DummyReviewsBackend;
++class AlpineApkReviewsBackend;
+ class StandardBackendUpdater;
+ class AlpineApkResource;
+ class AlpineApkBackend : public AbstractResourcesBackend
+@@ -62,7 +62,7 @@ private:
+
+ QHash<QString, AlpineApkResource *> m_resources;
+ StandardBackendUpdater *m_updater;
+- // DummyReviewsBackend* m_reviews;
++ AlpineApkReviewsBackend *m_reviews;
+ QtApk::Database m_apkdb;
+ QVector<QtApk::Package> m_availablePackages;
+ QVector<QtApk::Package> m_installedPackages;
+--
+GitLab
+
+
+From 66568f954f32ce86674b72b42095c784c12ae67a Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 19:53:39 +0300
+Subject: [PATCH 15/62] AlpineApkBackend: some cleanup on updates checking
+ functions
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 41 ++++++++++++-------
+ .../AlpineApkBackend/AlpineApkBackend.h | 5 ++-
+ 2 files changed, 30 insertions(+), 16 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index de93609a..41b2e053 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -55,8 +55,10 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ qCDebug(LOG_ALPINEAPK) << "constructing backend!";
+
+- QTimer::singleShot(500, this, &AlpineApkBackend::toggleFetching);
+- connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &AlpineApkBackend::updatesCountChanged);
++ QTimer::singleShot(1000, this, &AlpineApkBackend::startCheckForUpdates);
++
++ QObject::connect(m_updater, &StandardBackendUpdater::updatesCountChanged,
++ this, &AlpineApkBackend::updatesCountChanged);
+
+ populate();
+
+@@ -65,12 +67,14 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ QVector<Category *> AlpineApkBackend::category() const
+ {
++ // single root category
++ // we could add more, but Alpine apk does not have this concept
+ static Category *cat = new Category(
+- QStringLiteral("All applications"), // displayName
+- QStringLiteral("applications-other"), // icon
+- { }, // orFilters
++ QStringLiteral("All packages"), // displayName
++ QStringLiteral("package-x-generic"), // icon
++ {}, // orFilters
+ { displayName() }, // pluginName
+- { }, // subCategories
++ {}, // subCategories
+ QUrl(), // decoration (what is it?)
+ false // isAddons
+ );
+@@ -112,11 +116,23 @@ void AlpineApkBackend::populate()
+ }
+ }
+
+-void AlpineApkBackend::toggleFetching()
++void AlpineApkBackend::startCheckForUpdates()
+ {
+- m_fetching = !m_fetching;
++ if (m_fetching) {
++ return;
++ }
++ qCDebug(LOG_ALPINEAPK) << "startCheckForUpdates()";
++
++ m_fetching = true;
++ emit fetchingChanged();
++
++ // temporary hack - finish updates check in 5 seconds
++ QTimer::singleShot(5000, this, &AlpineApkBackend::finishCheckForUpdates);
++}
+
+- qCDebug(LOG_ALPINEAPK) << "fetching..." << m_fetching;
++void AlpineApkBackend::finishCheckForUpdates()
++{
++ m_fetching = false;
+ emit fetchingChanged();
+ }
+
+@@ -212,14 +228,11 @@ Transaction* AlpineApkBackend::removeApplication(AbstractResource *app)
+
+ void AlpineApkBackend::checkForUpdates()
+ {
+- if(m_fetching) {
++ if (m_fetching) {
+ qCDebug(LOG_ALPINEAPK) << "checkForUpdates(): already fetching";
+ return;
+ }
+- qCDebug(LOG_ALPINEAPK) << "checkForUpdates() start!";
+- toggleFetching();
+- // populate(QStringLiteral("Moar"));
+- QTimer::singleShot(1000, this, &AlpineApkBackend::toggleFetching);
++ startCheckForUpdates();
+ }
+
+ QString AlpineApkBackend::displayName() const
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index f8d3aa69..68a47bc9 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -55,7 +55,8 @@ public:
+ bool hasApplications() const override;
+
+ public Q_SLOTS:
+- void toggleFetching();
++ void startCheckForUpdates();
++ void finishCheckForUpdates();
+
+ private:
+ void populate();
+@@ -66,7 +67,7 @@ private:
+ QtApk::Database m_apkdb;
+ QVector<QtApk::Package> m_availablePackages;
+ QVector<QtApk::Package> m_installedPackages;
+- bool m_fetching = true;
++ bool m_fetching = false;
+ int m_startElements = 0;
+ };
+
+--
+GitLab
+
+
+From a1bdcdee0dba5da980732c07cdd576929935e5da Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 19:54:04 +0300
+Subject: [PATCH 16/62] Add KAuth helper process for privileged operations
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 56 +++++++++++++++++++
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 35 ++++++++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 26 ++++++++-
+ .../org.kde.discover.alpineapkbackend.actions | 5 ++
+ 4 files changed, 120 insertions(+), 2 deletions(-)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+ create mode 100644 libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+new file mode 100644
+index 00000000..6783f35c
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -0,0 +1,56 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include <QProcess>
++#include <QDebug>
++#include <QJsonDocument>
++#include <QJsonObject>
++#include <QJsonArray>
++#include <QFile>
++#include <KAuthHelperSupport>
++
++#include "AlpineApkAuthHelper.h"
++
++using namespace KAuth;
++
++AlpineApkAuthHelper::AlpineApkAuthHelper() {}
++
++ActionReply AlpineApkAuthHelper::test_action(const QVariantMap &args)
++{
++ const QString txt = args[QStringLiteral("txt")].toString();
++
++ ActionReply reply = ActionReply::HelperErrorReply();
++ QByteArray replyData(QByteArrayLiteral("ok"));
++
++ QFile f(QStringLiteral("/lol.txt"));
++ if (f.open(QIODevice::ReadWrite | QIODevice::Text)) {
++ f.write(txt.toUtf8());
++ f.close();
++
++ reply = ActionReply::SuccessReply();
++ reply.setData({
++ { QStringLiteral("reply"), replyData },
++ });
++ }
++
++ return reply;
++}
++
++KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+new file mode 100644
+index 00000000..86f7d3dc
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -0,0 +1,35 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include <QObject>
++#include <QVariant>
++#include <KAuthActionReply>
++
++using namespace KAuth;
++
++class AlpineApkAuthHelper : public QObject
++{
++ Q_OBJECT
++public:
++ AlpineApkAuthHelper();
++
++public Q_SLOTS:
++ ActionReply test_action(const QVariantMap &args);
++};
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 7d2b86e8..f67cf84f 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -1,4 +1,4 @@
+-# add_subdirectory(tests) # no tests yet
++find_package(KF5Auth CONFIG REQUIRED) # Probably should be moved to top CMakeLists
+
+ set(alpineapkbackend_SRCS
+ AlpineApkBackend.cpp
+@@ -38,12 +38,34 @@ target_link_libraries(
+ apk-qt
+ )
+
++# KAuth helper exe
++add_executable(alpineapk_kauth_helper
++ AlpineApkAuthHelper.cpp
++ AlpineApkAuthHelper.h
++ org.kde.discover.alpineapkbackend.actions
++)
++set_source_files_properties(
++ org.kde.discover.alpineapkbackend.actions
++ PROPERTIES HEADER_FILE_ONLY ON
++)
++target_link_libraries(alpineapk_kauth_helper
++ Qt5::Core
++ KF5::AuthCore
++ apk-qt
++)
++
++kauth_install_actions(org.kde.discover.alpineapkbackend org.kde.discover.alpineapkbackend.actions)
++kauth_install_helper_files(alpineapk_kauth_helper org.kde.discover.alpineapkbackend root)
++
+ install(
+ TARGETS alpineapk-backend
+ DESTINATION ${PLUGIN_INSTALL_DIR}/discover
+ )
+
+-# install(FILES alpineapk-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories)
++install(
++ TARGETS alpineapk_kauth_helper
++ DESTINATION ${KAUTH_HELPER_INSTALL_DIR}
++)
+
+ # add_library(AlpineApkNotifier MODULE AlpineApkNotifier.cpp)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+new file mode 100644
+index 00000000..ba9ede91
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -0,0 +1,5 @@
++[org.kde.discover.alpineapkbackend.test_action]
++Name=Test Action
++Description=Just test
++Policy=auth_admin
++Persistence=session
+--
+GitLab
+
+
+From 992a4f4f3444194feb87ffa9c57f57e16261ae0b Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jan 2020 20:41:07 +0300
+Subject: [PATCH 17/62] AlpineApkBackend: WIP on using KAuth helper
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 2 +-
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 2 +-
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 34 +++++++++++++++++--
+ .../AlpineApkBackend/AlpineApkBackend.h | 3 ++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 1 +
+ .../org.kde.discover.alpineapkbackend.actions | 2 +-
+ 6 files changed, 39 insertions(+), 5 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 6783f35c..19236dcb 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -32,7 +32,7 @@ using namespace KAuth;
+
+ AlpineApkAuthHelper::AlpineApkAuthHelper() {}
+
+-ActionReply AlpineApkAuthHelper::test_action(const QVariantMap &args)
++ActionReply AlpineApkAuthHelper::test(const QVariantMap &args)
+ {
+ const QString txt = args[QStringLiteral("txt")].toString();
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index 86f7d3dc..947dcb7a 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -31,5 +31,5 @@ public:
+ AlpineApkAuthHelper();
+
+ public Q_SLOTS:
+- ActionReply test_action(const QVariantMap &args);
++ ActionReply test(const QVariantMap &args);
+ };
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 41b2e053..ed016aef 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -35,6 +35,8 @@
+ #include <KPluginFactory>
+ #include <KConfigGroup>
+ #include <KSharedConfig>
++#include <KAuthExecuteJob>
++
+ #include <QDebug>
+ #include <QLoggingCategory>
+ #include <QThread>
+@@ -116,6 +118,19 @@ void AlpineApkBackend::populate()
+ }
+ }
+
++void AlpineApkBackend::handleKauthHelperReply(KJob *job)
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ KAuth::ExecuteJob* reply = static_cast<KAuth::ExecuteJob *>(job);
++ const QVariantMap replyData = reply->data();
++ if (reply->error() == 0) {
++ qCDebug(LOG_ALPINEAPK) << replyData[QLatin1String("reply")].toString();
++ } else {
++ const QString message = replyData.value(QLatin1String("errorString"), reply->errorString()).toString();
++ qCDebug(LOG_ALPINEAPK) << message;
++ }
++}
++
+ void AlpineApkBackend::startCheckForUpdates()
+ {
+ if (m_fetching) {
+@@ -123,11 +138,26 @@ void AlpineApkBackend::startCheckForUpdates()
+ }
+ qCDebug(LOG_ALPINEAPK) << "startCheckForUpdates()";
+
++ // temporary hack - finish updates check in 5 seconds
++ QTimer::singleShot(5000, this, &AlpineApkBackend::finishCheckForUpdates);
++
+ m_fetching = true;
+ emit fetchingChanged();
+
+- // temporary hack - finish updates check in 5 seconds
+- QTimer::singleShot(5000, this, &AlpineApkBackend::finishCheckForUpdates);
++ KAuth::Action testAction(QStringLiteral("org.kde.discover.alpineapkbackend.test"));
++ testAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++ testAction.setArguments({
++ { QStringLiteral("txt"), QLatin1String("Wooo!") },
++ });
++ if (!testAction.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "kauth action is not valid!";
++ return;
++ }
++
++ KAuth::ExecuteJob *reply = testAction.execute();
++ QObject::connect(reply, &KAuth::ExecuteJob::result,
++ this, &AlpineApkBackend::handleKauthHelperReply);
++ reply->start();
+ }
+
+ void AlpineApkBackend::finishCheckForUpdates()
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 68a47bc9..a533cee3 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -29,6 +29,8 @@
+ class AlpineApkReviewsBackend;
+ class StandardBackendUpdater;
+ class AlpineApkResource;
++class KJob;
++
+ class AlpineApkBackend : public AbstractResourcesBackend
+ {
+ Q_OBJECT
+@@ -55,6 +57,7 @@ public:
+ bool hasApplications() const override;
+
+ public Q_SLOTS:
++ void handleKauthHelperReply(KJob *job);
+ void startCheckForUpdates();
+ void finishCheckForUpdates();
+
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index f67cf84f..ede3157f 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -34,6 +34,7 @@ target_link_libraries(
+ Qt5::Widgets
+ KF5::CoreAddons
+ KF5::ConfigCore
++ KF5::AuthCore
+ Discover::Common
+ apk-qt
+ )
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index ba9ede91..3b9a3116 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -1,4 +1,4 @@
+-[org.kde.discover.alpineapkbackend.test_action]
++[org.kde.discover.alpineapkbackend.test]
+ Name=Test Action
+ Description=Just test
+ Policy=auth_admin
+--
+GitLab
+
+
+From 1b05b5e711e98b69e07bc00c3f8b2c6608c09897 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 2 Feb 2020 19:08:38 +0300
+Subject: [PATCH 18/62] AlpineApkSourcesBackend: tidy up logging and add I18N
+
+---
+ .../AlpineApkBackend/AlpineApkSourcesBackend.cpp | 14 +++++++++-----
+ 1 file changed, 9 insertions(+), 5 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 09a86bdf..b964b40d 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -20,10 +20,14 @@
+
+ #include "AlpineApkSourcesBackend.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
++
+ #include <QDebug>
+ #include <QAction>
+ #include <QVector>
+
++// KF5
++#include <KLocalizedString>
++
+ // libapk-qt
+ #include <QtApk.h>
+
+@@ -39,7 +43,7 @@ AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *paren
+
+ // can be used to track enabling/disabling repo source
+ // QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) {
+- // qCDebug(LOG_ALPINEAPK) << "DummySource changed" << item << item->checkState();
++ // qCDebug(LOG_ALPINEAPK) << "source backend: DummySource changed" << item << item->checkState();
+ // });
+ }
+
+@@ -70,7 +74,7 @@ bool AlpineApkSourcesBackend::addSourceFull(const QString &id, const QString &co
+ return false;
+ }
+
+- qCDebug(LOG_ALPINEAPK) << "AlpineApkSourcesBackend: Adding source:" << id;
++ qCDebug(LOG_ALPINEAPK) << "source backend: Adding source:" << id;
+
+ QStandardItem *it = new QStandardItem(id);
+ it->setData(id, AbstractSourcesBackend::IdRole);
+@@ -96,7 +100,7 @@ bool AlpineApkSourcesBackend::removeSource(const QString &id)
+ {
+ const QStandardItem *it = sourceForId(id);
+ if (!it) {
+- qCWarning(LOG_ALPINEAPK) << "AlpineApkSourcesBackend: couldn't find " << id;
++ qCWarning(LOG_ALPINEAPK) << "source backend: couldn't find " << id;
+ return false;
+ }
+ return m_sourcesModel->removeRow(it->row());
+@@ -104,8 +108,8 @@ bool AlpineApkSourcesBackend::removeSource(const QString &id)
+
+ QString AlpineApkSourcesBackend::idDescription()
+ {
+- return QStringLiteral("Enter apk repository URL, for example: "
+- "http://dl-cdn.alpinelinux.org/alpine/edge/testing/");
++ return i18nc("Adding repo", "Enter apk repository URL, for example: "
++ "http://dl-cdn.alpinelinux.org/alpine/edge/testing/");
+ }
+
+ QVariantList AlpineApkSourcesBackend::actions() const
+--
+GitLab
+
+
+From 85e37f8afe2d71c3f2fd5a321579353f46e9c54c Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 03:49:52 +0300
+Subject: [PATCH 19/62] kath helper: add and implement update() and upgrade()
+ actions
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 97 +++++++++++++++++++
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 9 ++
+ .../org.kde.discover.alpineapkbackend.actions | 24 +++++
+ 3 files changed, 130 insertions(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 19236dcb..4ff5c880 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -20,14 +20,22 @@
+
+ #include <QProcess>
+ #include <QDebug>
++#include <QLoggingCategory>
+ #include <QJsonDocument>
+ #include <QJsonObject>
+ #include <QJsonArray>
+ #include <QFile>
++
+ #include <KAuthHelperSupport>
+
+ #include "AlpineApkAuthHelper.h"
+
++#ifdef QT_DEBUG
++Q_LOGGING_CATEGORY(LOG_AUTHHELPER, "org.kde.discover.alpineapkbackend.authhelper", QtDebugMsg)
++#else
++Q_LOGGING_CATEGORY(LOG_AUTHHELPER, "org.kde.discover.alpineapkbackend.authhelper", QtWarningMsg)
++#endif
++
+ using namespace KAuth;
+
+ AlpineApkAuthHelper::AlpineApkAuthHelper() {}
+@@ -39,6 +47,7 @@ ActionReply AlpineApkAuthHelper::test(const QVariantMap &args)
+ ActionReply reply = ActionReply::HelperErrorReply();
+ QByteArray replyData(QByteArrayLiteral("ok"));
+
++ // write some text file at the root directory as root, why not
+ QFile f(QStringLiteral("/lol.txt"));
+ if (f.open(QIODevice::ReadWrite | QIODevice::Text)) {
+ f.write(txt.toUtf8());
+@@ -53,4 +62,92 @@ ActionReply AlpineApkAuthHelper::test(const QVariantMap &args)
+ return reply;
+ }
+
++ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
++{
++ Q_UNUSED(args)
++ ActionReply reply = ActionReply::HelperErrorReply();
++
++ HelperSupport::progressStep(10);
++
++ if (!m_apkdb.open(QtApk::Database::QTAPK_OPENF_READWRITE)) {
++ reply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return reply;
++ }
++
++ bool update_ok = m_apkdb.updatePackageIndex();
++
++ if (update_ok) {
++ int updatesCount = m_apkdb.upgradeablePackagesCount();
++ reply = ActionReply::SuccessReply();
++ reply.setData({
++ { QLatin1String("updatesCount"), updatesCount }
++ });
++ } else {
++ reply.setErrorDescription(QStringLiteral("Repo update failed!"));
++ reply.setData({
++ { QLatin1String("errorString"), QStringLiteral("Repo update failed!") }
++ });
++ }
++
++ m_apkdb.close();
++ HelperSupport::progressStep(100);
++
++ return reply;
++}
++
++ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
++{
++ ActionReply reply = ActionReply::HelperErrorReply();
++ return reply;
++}
++
++ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
++{
++ ActionReply reply = ActionReply::HelperErrorReply();
++ return reply;
++}
++
++ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
++{
++ ActionReply reply = ActionReply::HelperErrorReply();
++
++ HelperSupport::progressStep(10);
++
++ if (!m_apkdb.open(QtApk::Database::QTAPK_OPENF_READWRITE)) {
++ reply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return reply;
++ }
++
++ bool onlySimulate = args.value(QLatin1String("onlySimulate"), false).toBool();
++ QtApk::Database::DbUpgradeFlags flags = QtApk::Database::QTAPK_UPGRADE_DEFAULT;
++ if (onlySimulate) {
++ flags = QtApk::Database::QTAPK_UPGRADE_SIMULATE;
++ qCDebug(LOG_AUTHHELPER) << "Simulating upgrade run.";
++ }
++
++ QtApk::Changeset changes;
++ bool upgrade_ok = m_apkdb.upgrade(flags, &changes);
++
++ if (upgrade_ok) {
++ reply = ActionReply::SuccessReply();
++ QVariantMap replyData;
++ const QVector<QtApk::ChangesetItem> ch = changes.changes();
++ QVector<QVariant> chVector;
++ QVector<QtApk::Package> pkgVector;
++ for (const QtApk::ChangesetItem &it: ch) {
++ pkgVector << it.newPackage;
++ }
++ replyData.insert(QLatin1String("changes"), QVariant::fromValue(pkgVector));
++ replyData.insert(QLatin1String("onlySimulate"), onlySimulate);
++ reply.setData(replyData);
++ } else {
++ reply.setErrorDescription(QStringLiteral("Repo upgrade failed!"));
++ }
++
++ m_apkdb.close();
++ HelperSupport::progressStep(100);
++
++ return reply;
++}
++
+ KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index 947dcb7a..5c06e557 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -22,6 +22,8 @@
+ #include <QVariant>
+ #include <KAuthActionReply>
+
++#include <QtApk.h>
++
+ using namespace KAuth;
+
+ class AlpineApkAuthHelper : public QObject
+@@ -32,4 +34,11 @@ public:
+
+ public Q_SLOTS:
+ ActionReply test(const QVariantMap &args);
++ ActionReply update(const QVariantMap &args);
++ ActionReply add(const QVariantMap &args);
++ ActionReply del(const QVariantMap &args);
++ ActionReply upgrade(const QVariantMap &args);
++
++private:
++ QtApk::Database m_apkdb;
+ };
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index 3b9a3116..10305a1a 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -3,3 +3,27 @@ Name=Test Action
+ Description=Just test
+ Policy=auth_admin
+ Persistence=session
++
++[org.kde.discover.alpineapkbackend.update]
++Name=Update repository index
++Description=Updates available packages list from repositories
++Policy=auth_admin
++Persistence=session
++
++[org.kde.discover.alpineapkbackend.upgrade]
++Name=Upgrade all upgradable packages
++Description=Upgrade installed packages to latest versions
++Policy=auth_admin
++Persistence=session
++
++[org.kde.discover.alpineapkbackend.add]
++Name=Install/upgrade package
++Description=Installs/upgrades one package
++Policy=auth_admin
++Persistence=session
++
++[org.kde.discover.alpineapkbackend.del]
++Name=Remove package
++Description=Uninstall one package
++Policy=auth_admin
++Persistence=session
+--
+GitLab
+
+
+From c36853bbfa5312a9fe2e816df7548e353cb142b5 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 03:51:02 +0300
+Subject: [PATCH 20/62] AlpineApkResource: add availableVersion field
+
+for updates
+---
+ .../backends/AlpineApkBackend/AlpineApkResource.cpp | 8 ++++++--
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.h | 2 ++
+ 2 files changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index d827e7b3..af480b6c 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -18,7 +18,6 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+-#include <KRandom>
+ #include "AlpineApkResource.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+ #include "Transaction/AddonList.h"
+@@ -39,7 +38,7 @@ QList<PackageState> AlpineApkResource::addonsInformation()
+
+ QString AlpineApkResource::availableVersion() const
+ {
+- return m_pkg.version;
++ return m_availableVersion;
+ }
+
+ QStringList AlpineApkResource::categories()
+@@ -180,6 +179,11 @@ void AlpineApkResource::setAddonInstalled(const QString &addon, bool installed)
+ }
+ }
+
++void AlpineApkResource::setAvailableVersion(const QString &av)
++{
++ m_availableVersion = av;
++}
++
+
+ void AlpineApkResource::invokeApplication() const
+ {
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+index 79b100e1..7140786c 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -67,11 +67,13 @@ public:
+ void setSection(const QString &sectionName);
+ void setAddons(const AddonList &addons);
+ void setAddonInstalled(const QString &addon, bool installed);
++ void setAvailableVersion(const QString &av);
+
+ public:
+ AbstractResource::State m_state;
+ const AbstractResource::Type m_type;
+ QtApk::Package m_pkg;
++ QString m_availableVersion;
+ QString m_category;
+ QString m_originSoruce;
+ QString m_sectionName;
+--
+GitLab
+
+
+From d5fbfb4b162848dad2b732fe513798f73f9bbd94 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 03:51:58 +0300
+Subject: [PATCH 21/62] Add AlpineApkUpdater class
+
+---
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 286 ++++++++++++++++++
+ .../AlpineApkBackend/AlpineApkUpdater.h | 190 ++++++++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 2 +
+ 3 files changed, 478 insertions(+)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+new file mode 100644
+index 00000000..1dd6cdac
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -0,0 +1,286 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include "AlpineApkUpdater.h"
++#include "AlpineApkResource.h"
++#include "AlpineApkBackend.h"
++#include "alpineapk_backend_logging.h"
++#include "utils.h"
++
++#include <KAuthExecuteJob>
++#include <KLocalizedString>
++
++#include <QtApk.h>
++
++
++AlpineApkUpdater::AlpineApkUpdater(AbstractResourcesBackend *parent)
++ : AbstractBackendUpdater(parent)
++ , m_backend(static_cast<AlpineApkBackend *>(parent))
++{
++ //
++}
++
++void AlpineApkUpdater::prepare()
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++
++ QtApk::Database *db = m_backend->apkdb();
++
++ if (db->isOpen()) {
++ return;
++ }
++
++ if (!db->open(QtApk::Database::QTAPK_OPENF_READONLY)) {
++ emit passiveMessage(i18n("Failed to open APK database!"));
++ return;
++ }
++
++ if (!db->upgrade(QtApk::Database::QTAPK_UPGRADE_SIMULATE, &m_upgradeable)) {
++ emit passiveMessage(i18n("Failed to get a list of packages to upgrade!"));
++ db->close();
++ return;
++ }
++ // clsoe DB ASAP
++ db->close();
++
++ m_updatesCount = m_upgradeable.changes().size();
++ qCDebug(LOG_ALPINEAPK) << "updater: prepare: updates count" << m_updatesCount;
++
++ m_allUpdateable.clear();
++ m_markedToUpdate.clear();
++ QHash<QString, AlpineApkResource *> *resources = m_backend->resourcesPtr();
++ for (const QtApk::ChangesetItem &it : qAsConst(m_upgradeable.changes())) {
++ const QtApk::Package &oldPkg = it.oldPackage;
++ const QString newVersion = it.newPackage.version;
++ AlpineApkResource *res = resources->value(oldPkg.name);
++ if (res) {
++ res->setAvailableVersion(newVersion);
++ m_allUpdateable.insert(res);
++ m_markedToUpdate.insert(res);
++ }
++ }
++
++ // emitting this signal here leads to infinite recursion
++ // emit updatesCountChanged(m_updatesCount);
++}
++
++bool AlpineApkUpdater::hasUpdates() const
++{
++ // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
++ return (m_updatesCount > 0);
++}
++
++qreal AlpineApkUpdater::progress() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return 0.0;
++}
++
++void AlpineApkUpdater::removeResources(const QList<AbstractResource *> &apps)
++{
++ // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ const QSet<AbstractResource *> checkSet = kToSet(apps);
++ m_markedToUpdate -= checkSet;
++}
++
++void AlpineApkUpdater::addResources(const QList<AbstractResource *> &apps)
++{
++ //Q_UNUSED(apps)
++ //qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ const QSet<AbstractResource *> checkSet = kToSet(apps);
++ m_markedToUpdate += checkSet;
++}
++
++QList<AbstractResource *> AlpineApkUpdater::toUpdate() const
++{
++ // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return m_allUpdateable.values();
++}
++
++QDateTime AlpineApkUpdater::lastUpdate() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return QDateTime();
++}
++
++bool AlpineApkUpdater::isCancelable() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return true;
++}
++
++bool AlpineApkUpdater::isProgressing() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return false;
++}
++
++bool AlpineApkUpdater::isMarked(AbstractResource *res) const
++{
++ // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return m_markedToUpdate.contains(res);
++ // return true;
++}
++
++void AlpineApkUpdater::fetchChangelog() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++}
++
++double AlpineApkUpdater::updateSize() const
++{
++ // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ double sum = 0.0;
++ for (AbstractResource *res : m_markedToUpdate) {
++ sum += res->size();
++ }
++ return sum;
++}
++
++quint64 AlpineApkUpdater::downloadSpeed() const
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return 0;
++}
++
++void AlpineApkUpdater::cancel()
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++}
++
++void AlpineApkUpdater::start()
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++ return;
++#if 0
++ KAuth::Action upgradeAction(QStringLiteral("org.kde.discover.alpineapkbackend.upgrade"));
++ upgradeAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++ if (!upgradeAction.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "kauth upgradeAction is not valid!";
++ return;
++ }
++ upgradeAction.setTimeout(60 * 1000); // 1 minute
++ upgradeAction.setDetails(i18n("Get the list of packages to upgrade"));
++ upgradeAction.addArgument(QLatin1String("onlySimulate"), true);
++
++ // run upgrade check with elevated privileges
++ KAuth::ExecuteJob *reply = upgradeAction.execute();
++ QObject::connect(reply, &KAuth::ExecuteJob::result,
++ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
++
++ reply->start();
++#endif
++}
++
++void AlpineApkUpdater::proceed()
++{
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
++}
++
++int AlpineApkUpdater::updatesCount()
++{
++ // qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
++ return m_updatesCount;
++}
++
++void AlpineApkUpdater::startCheckForUpdates()
++{
++ KAuth::Action updateAction(QStringLiteral("org.kde.discover.alpineapkbackend.update"));
++ updateAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++ if (!updateAction.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "kauth updateAction is not valid!";
++ return;
++ }
++ updateAction.setTimeout(60 * 1000); // 1 minute
++ updateAction.setDetails(i18n("Update repositories index"));
++
++ // run updates check with elevated privileges to access
++ // system package manager files
++ KAuth::ExecuteJob *reply = updateAction.execute();
++ QObject::connect(reply, &KAuth::ExecuteJob::result,
++ this, &AlpineApkUpdater::handleKAuthUpdateHelperReply);
++ //QObject::connect(reply, &KAuth::ExecuteJob::newData,
++ // this, &AlpineApkUpdater::handleKAuthUpdateHelperProgressStep);
++ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
++ this, &AlpineApkUpdater::handleKAuthUpdateHelperProgress);
++
++ reply->start();
++}
++
++void AlpineApkUpdater::handleKAuthUpdateHelperReply(KJob *job)
++{
++ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
++ const QVariantMap &replyData = reply->data();
++ if (reply->error() == 0) {
++ m_updatesCount = replyData.value(QLatin1String("updatesCount")).toInt();
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper update reply received, updatesCount:" << m_updatesCount;
++ Q_EMIT updatesCountChanged(m_updatesCount);
++ } else {
++ const QString message = replyData.value(QLatin1String("errorString"),
++ reply->errorString()).toString();
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
++ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ Q_EMIT passiveMessage(i18n("Authorization denied"));
++ } else {
++ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
++ }
++ }
++
++ // we are not in the state "Fetching updates" now, update UI
++ Q_EMIT checkForUpdatesFinished();
++}
++
++void AlpineApkUpdater::handleKAuthUpdateHelperProgress(KJob *job, unsigned long percent)
++{
++ Q_UNUSED(job)
++ qCDebug(LOG_ALPINEAPK) << " fetch updates progress: " << percent;
++ Q_EMIT fetchingUpdatesProgressChanged(percent);
++}
++
++void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
++{
++ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
++ const QVariantMap &replyData = reply->data();
++ if (reply->error() == 0) {
++ QVariant pkgsV = replyData.value(QLatin1String("changes"));
++ bool onlySimulate = replyData.value(QLatin1String("onlySimulate"), false).toBool();
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received:" << onlySimulate;
++ if (onlySimulate) {
++ QVector<QtApk::Package> pkgVector = pkgsV.value<QVector<QtApk::Package>>();
++ qCDebug(LOG_ALPINEAPK) << " num changes:" << pkgVector.size();
++ for (const QtApk::Package &pkg : pkgVector) {
++ qCDebug(LOG_ALPINEAPK) << " " << pkg.name << pkg.version;
++ }
++ }
++ } else {
++ const QString message = replyData.value(QLatin1String("errorString"),
++ reply->errorString()).toString();
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
++ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ Q_EMIT passiveMessage(i18n("Authorization denied"));
++ } else {
++ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
++ }
++ }
++
++ // we are not in the state "Fetching updates" now, update UI
++ Q_EMIT checkForUpdatesFinished();
++}
++
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+new file mode 100644
+index 00000000..504e8c59
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+@@ -0,0 +1,190 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef ALPINEAPKUPDATER_H
++#define ALPINEAPKUPDATER_H
++
++#include "resources/AbstractBackendUpdater.h"
++#include "resources/AbstractResourcesBackend.h"
++
++#include <QSet>
++#include <QDateTime>
++#include <QTimer>
++#include <QVariant>
++#include <QMap>
++#include <QVector>
++
++#include <QtApkChangeset.h>
++
++class AbstractResourcesBackend;
++class AlpineApkBackend;
++class KJob;
++
++class AlpineApkUpdater : public AbstractBackendUpdater
++{
++ Q_OBJECT
++ Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged)
++
++public:
++ explicit AlpineApkUpdater(AbstractResourcesBackend *parent = nullptr);
++
++ /**
++ * This method is called, when Muon switches to the updates view.
++ * Here the backend should mark all upgradeable packages as to be upgraded.
++ */
++ void prepare() override;
++
++ /**
++ * @returns true if the backend contains packages which can be updated
++ */
++ bool hasUpdates() const override;
++ /**
++ * @returns the progress of the update in percent
++ */
++ qreal progress() const override;
++
++ /**
++ * This method is used to remove resources from the list of packages
++ * marked to be upgraded. It will potentially be called before \start.
++ */
++ void removeResources(const QList<AbstractResource*> &apps) override;
++
++ /**
++ * This method is used to add resource to the list of packages marked to be upgraded.
++ * It will potentially be called before \start.
++ */
++ void addResources(const QList<AbstractResource*> &apps) override;
++
++ /**
++ * @returns the list of updateable resources in the system
++ */
++ QList<AbstractResource *> toUpdate() const override;
++
++ /**
++ * @returns the QDateTime when the last update happened
++ */
++ QDateTime lastUpdate() const override;
++
++ /**
++ * @returns whether the updater can currently be canceled or not
++ * @see cancelableChanged
++ */
++ bool isCancelable() const override;
++
++ /**
++ * @returns whether the updater is currently running or not
++ * this property decides, if there will be progress reporting in the GUI.
++ * This has to stay true during the whole transaction!
++ * @see progressingChanged
++ */
++ bool isProgressing() const override;
++
++ /**
++ * @returns whether @p res is marked for update
++ */
++ bool isMarked(AbstractResource* res) const override;
++
++ void fetchChangelog() const override;
++
++ /**
++ * @returns the size of all the packages set to update combined
++ */
++ double updateSize() const override;
++
++ /**
++ * @returns the speed at which we are downloading
++ */
++ quint64 downloadSpeed() const override;
++
++public Q_SLOTS:
++ /**
++ * If \isCancelable is true during the transaction, this method has
++ * to be implemented and will potentially be called when the user
++ * wants to cancel the update.
++ */
++ void cancel() override;
++
++ /**
++ * This method starts the update. All packages which are in \toUpdate
++ * are going to be updated.
++ *
++ * From this moment on the AbstractBackendUpdater should continuously update
++ * the other methods to show its progress.
++ *
++ * @see progress
++ * @see progressChanged
++ * @see isProgressing
++ * @see progressingChanged
++ */
++ void start() override;
++
++ /**
++ * Answers a proceed request
++ */
++ void proceed() override;
++
++Q_SIGNALS:
++ void checkForUpdatesFinished();
++ void updatesCountChanged(int updatesCount);
++ void fetchingUpdatesProgressChanged(int progress);
++ //void cancelTransaction();
++
++public Q_SLOTS:
++ int updatesCount();
++ void startCheckForUpdates();
++
++ // KAuth handler slots
++ // update
++ void handleKAuthUpdateHelperReply(KJob *job);
++ void handleKAuthUpdateHelperProgress(KJob *job, unsigned long percent);
++ // upgrade
++ void handleKAuthUpgradeHelperReply(KJob *job);
++
++ //void transactionRemoved(Transaction* t);
++ //void cleanup();
++
++public:
++ QVector<QtApk::ChangesetItem> &changes() { return m_upgradeable.changes(); }
++ const QVector<QtApk::ChangesetItem> &changes() const { return m_upgradeable.changes(); }
++
++private:
++ AlpineApkBackend *const m_backend;
++ int m_updatesCount = 0;
++ QtApk::Changeset m_upgradeable;
++ QSet<AbstractResource *> m_allUpdateable;
++ QSet<AbstractResource *> m_markedToUpdate;
++// void resourcesChanged(AbstractResource* res, const QVector<QByteArray>& props);
++// void refreshUpdateable();
++// void transactionAdded(Transaction* newTransaction);
++// void transactionProgressChanged();
++// void refreshProgress();
++// QVector<Transaction*> transactions() const;
++
++// QSet<AbstractResource*> m_upgradeable;
++// QSet<AbstractResource*> m_pendingResources;
++// bool m_settingUp;
++// qreal m_progress;
++// QDateTime m_lastUpdate;
++// QTimer m_timer;
++// bool m_canCancel = false;
++};
++
++
++#endif // ALPINEAPKUPDATER_H
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index ede3157f..38184945 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -9,6 +9,8 @@ set(alpineapkbackend_SRCS
+ AlpineApkReviewsBackend.h
+ AlpineApkSourcesBackend.cpp
+ AlpineApkSourcesBackend.h
++ AlpineApkUpdater.cpp
++ AlpineApkUpdater.h
+ AlpineApkTransaction.cpp
+ AlpineApkTransaction.h
+ )
+--
+GitLab
+
+
+From c336f16343186998926232d82df580af688f5220 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 03:52:41 +0300
+Subject: [PATCH 22/62] AlpineApkBackend: use our new updater
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 158 ++++++++----------
+ .../AlpineApkBackend/AlpineApkBackend.h | 21 ++-
+ 2 files changed, 85 insertions(+), 94 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index ed016aef..75738018 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -23,19 +23,14 @@
+ #include "AlpineApkReviewsBackend.h"
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkSourcesBackend.h"
++#include "AlpineApkUpdater.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+
+-#include "resources/StandardBackendUpdater.h"
+ #include "resources/SourcesModel.h"
+ #include "Transaction/Transaction.h"
+ #include "Category/Category.h"
+
+-#include <KAboutData>
+ #include <KLocalizedString>
+-#include <KPluginFactory>
+-#include <KConfigGroup>
+-#include <KSharedConfig>
+-#include <KAuthExecuteJob>
+
+ #include <QDebug>
+ #include <QLoggingCategory>
+@@ -47,45 +42,33 @@ DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
+
+ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ : AbstractResourcesBackend(parent)
+- , m_updater(new StandardBackendUpdater(this))
++ , m_updater(new AlpineApkUpdater(this))
+ , m_reviews(new AlpineApkReviewsBackend(this))
+- , m_startElements(120)
++ , m_updatesTimeoutTimer(new QTimer(this))
+ {
+ #ifndef QT_DEBUG
+ const_cast<QLoggingCategory &>(LOG_ALPINEAPK()).setEnabled(QtDebugMsg, false);
+ #endif
+
+- qCDebug(LOG_ALPINEAPK) << "constructing backend!";
++ // schedule checking for updates
++ QTimer::singleShot(1000, this, &AlpineApkBackend::checkForUpdates);
+
+- QTimer::singleShot(1000, this, &AlpineApkBackend::startCheckForUpdates);
+-
+- QObject::connect(m_updater, &StandardBackendUpdater::updatesCountChanged,
++ // connections with our updater
++ QObject::connect(m_updater, &AlpineApkUpdater::updatesCountChanged,
+ this, &AlpineApkBackend::updatesCountChanged);
++ QObject::connect(m_updater, &AlpineApkUpdater::checkForUpdatesFinished,
++ this, &AlpineApkBackend::finishCheckForUpdates);
++ QObject::connect(m_updater, &AlpineApkUpdater::fetchingUpdatesProgressChanged,
++ this, &AlpineApkBackend::setFetchingUpdatesProgress);
+
+- populate();
+-
+- SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+-}
+-
+-QVector<Category *> AlpineApkBackend::category() const
+-{
+- // single root category
+- // we could add more, but Alpine apk does not have this concept
+- static Category *cat = new Category(
+- QStringLiteral("All packages"), // displayName
+- QStringLiteral("package-x-generic"), // icon
+- {}, // orFilters
+- { displayName() }, // pluginName
+- {}, // subCategories
+- QUrl(), // decoration (what is it?)
+- false // isAddons
+- );
+- return { cat };
+-}
++ // safety measure: make sure update check process can finish in some finite time
++ QObject::connect(m_updatesTimeoutTimer, &QTimer::timeout,
++ this, &AlpineApkBackend::finishCheckForUpdates);
++ m_updatesTimeoutTimer->setTimerType(Qt::CoarseTimer);
++ m_updatesTimeoutTimer->setSingleShot(true);
++ m_updatesTimeoutTimer->setInterval(2 * 60 * 1000); // 2minutes
+
+-void AlpineApkBackend::populate()
+-{
+- qCDebug(LOG_ALPINEAPK) << "populating resources...";
++ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
+
+ if (m_apkdb.open(QtApk::Database::QTAPK_OPENF_READONLY)) {
+ m_availablePackages = m_apkdb.getAvailablePackages();
+@@ -103,7 +86,7 @@ void AlpineApkBackend::populate()
+ m_resources.insert(key, res);
+ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
+ }
+- qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
++ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
+ << "packages";
+ }
+ if (m_installedPackages.size() > 0) {
+@@ -113,62 +96,31 @@ void AlpineApkBackend::populate()
+ m_resources.value(key)->setState(AbstractResource::Installed);
+ }
+ }
+- qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
++ qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
+ << "packages";
+ }
+-}
+-
+-void AlpineApkBackend::handleKauthHelperReply(KJob *job)
+-{
+- qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- KAuth::ExecuteJob* reply = static_cast<KAuth::ExecuteJob *>(job);
+- const QVariantMap replyData = reply->data();
+- if (reply->error() == 0) {
+- qCDebug(LOG_ALPINEAPK) << replyData[QLatin1String("reply")].toString();
+- } else {
+- const QString message = replyData.value(QLatin1String("errorString"), reply->errorString()).toString();
+- qCDebug(LOG_ALPINEAPK) << message;
+- }
+-}
+-
+-void AlpineApkBackend::startCheckForUpdates()
+-{
+- if (m_fetching) {
+- return;
+- }
+- qCDebug(LOG_ALPINEAPK) << "startCheckForUpdates()";
+-
+- // temporary hack - finish updates check in 5 seconds
+- QTimer::singleShot(5000, this, &AlpineApkBackend::finishCheckForUpdates);
+-
+- m_fetching = true;
+- emit fetchingChanged();
+-
+- KAuth::Action testAction(QStringLiteral("org.kde.discover.alpineapkbackend.test"));
+- testAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+- testAction.setArguments({
+- { QStringLiteral("txt"), QLatin1String("Wooo!") },
+- });
+- if (!testAction.isValid()) {
+- qCWarning(LOG_ALPINEAPK) << "kauth action is not valid!";
+- return;
+- }
+
+- KAuth::ExecuteJob *reply = testAction.execute();
+- QObject::connect(reply, &KAuth::ExecuteJob::result,
+- this, &AlpineApkBackend::handleKauthHelperReply);
+- reply->start();
++ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+ }
+
+-void AlpineApkBackend::finishCheckForUpdates()
++QVector<Category *> AlpineApkBackend::category() const
+ {
+- m_fetching = false;
+- emit fetchingChanged();
++ // single root category
++ // we could add more, but Alpine apk does not have this concept
++ static Category *cat = new Category(
++ i18nc("Root category name", "Alpine packages"),
++ QStringLiteral("package-x-generic"), // icon
++ {}, // orFilters
++ { displayName() }, // pluginName
++ {}, // subCategories
++ QUrl(), // decoration (what is it?)
++ false // isAddons
++ );
++ return { cat };
+ }
+
+ int AlpineApkBackend::updatesCount() const
+ {
+- qCDebug(LOG_ALPINEAPK) << "updatesCount(): " << m_updater->updatesCount();
+ return m_updater->updatesCount();
+ }
+
+@@ -210,7 +162,7 @@ ResultsStream *AlpineApkBackend::findResourceByPackageName(const QUrl &searchUrl
+ AlpineApkResource *result = nullptr;
+
+ // QUrl("appstream://org.kde.krita.desktop")
+- // smart workaround for appstream
++ // smart workaround for appstream URLs
+ if (searchUrl.scheme() == QLatin1String("appstream")) {
+ // remove leading "org.kde."
+ QString pkgName = searchUrl.host();
+@@ -256,18 +208,46 @@ Transaction* AlpineApkBackend::removeApplication(AbstractResource *app)
+ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), Transaction::RemoveRole);
+ }
+
++int AlpineApkBackend::fetchingUpdatesProgress() const
++{
++ if (!m_fetching) return 100;
++ return m_fetchProgress;
++}
++
+ void AlpineApkBackend::checkForUpdates()
+ {
+ if (m_fetching) {
+- qCDebug(LOG_ALPINEAPK) << "checkForUpdates(): already fetching";
++ qCDebug(LOG_ALPINEAPK) << "backend: checkForUpdates(): already fetching";
+ return;
+ }
+- startCheckForUpdates();
++
++ qCDebug(LOG_ALPINEAPK) << "backend: start checkForUpdates()";
++
++ // safety measure - finish updates check in some time
++ m_updatesTimeoutTimer->start();
++
++ // let our updater do the job
++ m_updater->startCheckForUpdates();
++
++ // update UI
++ m_fetching = true;
++ m_fetchProgress = 0;
++ emit fetchingChanged();
++ emit fetchingUpdatesProgressChanged();
++}
++
++void AlpineApkBackend::finishCheckForUpdates()
++{
++ m_updatesTimeoutTimer->stop(); // stop safety timer
++ // update UI
++ m_fetching = false;
++ emit fetchingChanged();
++ emit fetchingUpdatesProgressChanged();
+ }
+
+ QString AlpineApkBackend::displayName() const
+ {
+- return QStringLiteral("Alpine APK backend");
++ return i18nc("Backend plugin display name", "Alpine APK backend");
+ }
+
+ bool AlpineApkBackend::hasApplications() const
+@@ -275,4 +255,10 @@ bool AlpineApkBackend::hasApplications() const
+ return true;
+ }
+
++void AlpineApkBackend::setFetchingUpdatesProgress(int percent)
++{
++ m_fetchProgress = percent;
++ emit fetchingUpdatesProgressChanged();
++}
++
+ #include "AlpineApkBackend.moc"
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index a533cee3..d6935257 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -27,14 +27,14 @@
+ #include <QtApk.h>
+
+ class AlpineApkReviewsBackend;
+-class StandardBackendUpdater;
++class AlpineApkUpdater;
+ class AlpineApkResource;
+ class KJob;
++class QTimer;
+
+ class AlpineApkBackend : public AbstractResourcesBackend
+ {
+ Q_OBJECT
+- Q_PROPERTY(int startElements MEMBER m_startElements)
+
+ public:
+ explicit AlpineApkBackend(QObject *parent = nullptr);
+@@ -46,32 +46,37 @@ public:
+ ResultsStream *search(const AbstractResourcesBackend::Filters &filter) override;
+ ResultsStream *findResourceByPackageName(const QUrl &search);
+ QHash<QString, AlpineApkResource *> resources() const { return m_resources; }
++ QHash<QString, AlpineApkResource *> *resourcesPtr() { return &m_resources; }
+ bool isValid() const override { return true; } // No external file dependencies that could cause runtime errors
+
+ Transaction *installApplication(AbstractResource *app) override;
+ Transaction *installApplication(AbstractResource *app, const AddonList &addons) override;
+ Transaction *removeApplication(AbstractResource *app) override;
+ bool isFetching() const override { return m_fetching; }
++ int fetchingUpdatesProgress() const override;
+ void checkForUpdates() override;
+ QString displayName() const override;
+ bool hasApplications() const override;
+
+ public Q_SLOTS:
+- void handleKauthHelperReply(KJob *job);
+- void startCheckForUpdates();
++ void setFetchingUpdatesProgress(int percent);
++
++private Q_SLOTS:
+ void finishCheckForUpdates();
+
+-private:
+- void populate();
++public:
++ QtApk::Database *apkdb() { return &m_apkdb; }
+
++private:
+ QHash<QString, AlpineApkResource *> m_resources;
+- StandardBackendUpdater *m_updater;
++ AlpineApkUpdater *m_updater;
+ AlpineApkReviewsBackend *m_reviews;
+ QtApk::Database m_apkdb;
+ QVector<QtApk::Package> m_availablePackages;
+ QVector<QtApk::Package> m_installedPackages;
+ bool m_fetching = false;
+- int m_startElements = 0;
++ int m_fetchProgress = 0;
++ QTimer *m_updatesTimeoutTimer;
+ };
+
+ #endif // AlpineApkBackend_H
+--
+GitLab
+
+
+From f5038a2fba366be10ffb562a5ba2b7ea4024c289 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 18:21:04 +0300
+Subject: [PATCH 23/62] AlpineApk{Updater,AuthHelper}: pass fakeRoot to helper
+
+Env vars are not inherited by elevated helper
+---
+ .../backends/AlpineApkBackend/AlpineApkAuthHelper.cpp | 11 ++++++++++-
+ .../backends/AlpineApkBackend/AlpineApkUpdater.cpp | 5 ++++-
+ 2 files changed, 14 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 4ff5c880..aa3dbdae 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -64,9 +64,13 @@ ActionReply AlpineApkAuthHelper::test(const QVariantMap &args)
+
+ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+ {
+- Q_UNUSED(args)
+ ActionReply reply = ActionReply::HelperErrorReply();
+
++ const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
++ if (!fakeRoot.isEmpty()) {
++ m_apkdb.setFakeRoot(fakeRoot);
++ }
++
+ HelperSupport::progressStep(10);
+
+ if (!m_apkdb.open(QtApk::Database::QTAPK_OPENF_READWRITE)) {
+@@ -125,6 +129,11 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ qCDebug(LOG_AUTHHELPER) << "Simulating upgrade run.";
+ }
+
++ const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
++ if (!fakeRoot.isEmpty()) {
++ m_apkdb.setFakeRoot(fakeRoot);
++ }
++
+ QtApk::Changeset changes;
+ bool upgrade_ok = m_apkdb.upgrade(flags, &changes);
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 1dd6cdac..d0c0482e 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -47,6 +47,7 @@ void AlpineApkUpdater::prepare()
+ return;
+ }
+
++ // readonly is fine for a simulation of upgrade
+ if (!db->open(QtApk::Database::QTAPK_OPENF_READONLY)) {
+ emit passiveMessage(i18n("Failed to open APK database!"));
+ return;
+@@ -57,7 +58,7 @@ void AlpineApkUpdater::prepare()
+ db->close();
+ return;
+ }
+- // clsoe DB ASAP
++ // close DB ASAP
+ db->close();
+
+ m_updatesCount = m_upgradeable.changes().size();
+@@ -202,6 +203,7 @@ int AlpineApkUpdater::updatesCount()
+
+ void AlpineApkUpdater::startCheckForUpdates()
+ {
++ QtApk::Database *db = m_backend->apkdb();
+ KAuth::Action updateAction(QStringLiteral("org.kde.discover.alpineapkbackend.update"));
+ updateAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+ if (!updateAction.isValid()) {
+@@ -210,6 +212,7 @@ void AlpineApkUpdater::startCheckForUpdates()
+ }
+ updateAction.setTimeout(60 * 1000); // 1 minute
+ updateAction.setDetails(i18n("Update repositories index"));
++ updateAction.addArgument(QLatin1String("fakeRoot"), db->fakeRoot());
+
+ // run updates check with elevated privileges to access
+ // system package manager files
+--
+GitLab
+
+
+From 42d0a0e0b609e5ed4944f5221111eaf1d413c67c Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 3 Feb 2020 20:14:13 +0300
+Subject: [PATCH 24/62] Auth helper: WIP on using progress_fd
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index aa3dbdae..4c3bf545 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -21,9 +21,8 @@
+ #include <QProcess>
+ #include <QDebug>
+ #include <QLoggingCategory>
+-#include <QJsonDocument>
+-#include <QJsonObject>
+-#include <QJsonArray>
++#include <QSocketNotifier>
++#include <QScopedPointer>
+ #include <QFile>
+
+ #include <KAuthHelperSupport>
+@@ -134,6 +133,14 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ m_apkdb.setFakeRoot(fakeRoot);
+ }
+
++ int progress_fd = m_apkdb.progressFd();
++ qCDebug(LOG_AUTHHELPER) << " progress_fd: " << progress_fd;
++
++ QScopedPointer<QSocketNotifier> notifier(new QSocketNotifier(progress_fd, QSocketNotifier::Read));
++ QObject::connect(notifier.data(), &QSocketNotifier::activated, notifier.data(), [](int sock) {
++ qCDebug(LOG_AUTHHELPER) << " read trigger from progress_fd!";
++ });
++
+ QtApk::Changeset changes;
+ bool upgrade_ok = m_apkdb.upgrade(flags, &changes);
+
+--
+GitLab
+
+
+From f6e5d590bc8570db785a4a5a2cfded6abf6012d8 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 20 Mar 2020 17:53:01 +0300
+Subject: [PATCH 25/62] auth helper: fix usused vars
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 4c3bf545..bc9c1ec6 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -100,12 +100,14 @@ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+
+ ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
+ {
++ Q_UNUSED(args)
+ ActionReply reply = ActionReply::HelperErrorReply();
+ return reply;
+ }
+
+ ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
+ {
++ Q_UNUSED(args)
+ ActionReply reply = ActionReply::HelperErrorReply();
+ return reply;
+ }
+@@ -138,6 +140,7 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+
+ QScopedPointer<QSocketNotifier> notifier(new QSocketNotifier(progress_fd, QSocketNotifier::Read));
+ QObject::connect(notifier.data(), &QSocketNotifier::activated, notifier.data(), [](int sock) {
++ Q_UNUSED(sock)
+ qCDebug(LOG_AUTHHELPER) << " read trigger from progress_fd!";
+ });
+
+--
+GitLab
+
+
+From 762b7be428274d6a020a4f7b800eaaecab23fca1 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 8 Apr 2020 05:55:33 +0300
+Subject: [PATCH 26/62] AlpineApkBackend: cmake: use namespaced ApkQt
+
+---
+ libdiscover/backends/AlpineApkBackend/CMakeLists.txt | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 38184945..42e09c5b 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -38,7 +38,7 @@ target_link_libraries(
+ KF5::ConfigCore
+ KF5::AuthCore
+ Discover::Common
+- apk-qt
++ ApkQt::apk-qt
+ )
+
+ # KAuth helper exe
+@@ -54,7 +54,7 @@ set_source_files_properties(
+ target_link_libraries(alpineapk_kauth_helper
+ Qt5::Core
+ KF5::AuthCore
+- apk-qt
++ ApkQt::apk-qt
+ )
+
+ kauth_install_actions(org.kde.discover.alpineapkbackend org.kde.discover.alpineapkbackend.actions)
+--
+GitLab
+
+
+From 9a963dff6d51cd5764c419d62732f706893b082c Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 9 Apr 2020 21:18:33 +0300
+Subject: [PATCH 27/62] AlpineApkBackend: mark pkgs as Technical type and fix
+ searhes
+
+This makes updates appear as "System updates" in updates view
+and also makes them correctly categorized in top level
+categories when other backend is installed (Flatpak, KNewStuff)
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 62 ++++++++++++-------
+ .../AlpineApkBackend/AlpineApkResource.cpp | 2 +-
+ 2 files changed, 42 insertions(+), 22 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 75738018..750d2820 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -79,9 +79,12 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ if (m_availablePackages.size() > 0) {
+ for (const QtApk::Package &pkg: m_availablePackages) {
+ AlpineApkResource *res = new AlpineApkResource(pkg, this);
+- res->setCategoryName(QStringLiteral("all"));
++ res->setCategoryName(QStringLiteral("alpine_packages"));
+ res->setOriginSource(QStringLiteral("apk"));
+ res->setSection(QStringLiteral("dummy"));
++ // here is the place to set a proper type of package
++ // AlpineApkResource defaults to AbstractResource::Technical,
++ // which places it into "System updates" section
+ const QString key = pkg.name.toLower();
+ m_resources.insert(key, res);
+ connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
+@@ -105,18 +108,22 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ QVector<Category *> AlpineApkBackend::category() const
+ {
+- // single root category
++ static QPair<FilterType, QString> s_apkFlt(
++ FilterType::CategoryFilter, QLatin1String("alpine_packages"));
++
++ // Display a single root category
+ // we could add more, but Alpine apk does not have this concept
+- static Category *cat = new Category(
+- i18nc("Root category name", "Alpine packages"),
+- QStringLiteral("package-x-generic"), // icon
+- {}, // orFilters
+- { displayName() }, // pluginName
+- {}, // subCategories
+- QUrl(), // decoration (what is it?)
+- false // isAddons
++ static Category *s_rootCat = new Category(
++ i18nc("Root category name", "Alpine packages"),
++ QStringLiteral("package-x-generic"), // icon
++ { s_apkFlt }, // orFilters - include packages that match filter
++ { displayName() }, // pluginName
++ {}, // subCategories - none
++ QUrl(), // decoration (what is it?)
++ false // isAddons
+ );
+- return { cat };
++
++ return { s_rootCat };
+ }
+
+ int AlpineApkBackend::updatesCount() const
+@@ -130,17 +137,23 @@ ResultsStream *AlpineApkBackend::search(const AbstractResourcesBackend::Filters
+ if (!filter.resourceUrl.isEmpty()) {
+ return findResourceByPackageName(filter.resourceUrl);
+ } else {
+- for (AbstractResource *r: qAsConst(m_resources)) {
+- if (r->type() == AbstractResource::Technical
+- && filter.state != AbstractResource::Upgradeable) {
+- continue;
+- }
+- if (r->state() < filter.state) {
++ for (AbstractResource *resource: qAsConst(m_resources)) {
++ // skip technical package types (not apps/addons)
++ // that are not upgradeable
++ // (does not work because for now all Alpine packages are "technical"
++ // if (resource->type() == AbstractResource::Technical
++ // && filter.state != AbstractResource::Upgradeable) {
++ // continue;
++ // }
++
++ // skip not-requested states
++ if (resource->state() < filter.state) {
+ continue;
+ }
+- if(r->name().contains(filter.search, Qt::CaseInsensitive)
+- || r->comment().contains(filter.search, Qt::CaseInsensitive)) {
+- ret += r;
++
++ if(resource->name().contains(filter.search, Qt::CaseInsensitive)
++ || resource->comment().contains(filter.search, Qt::CaseInsensitive)) {
++ ret += resource;
+ }
+ }
+ }
+@@ -162,7 +175,7 @@ ResultsStream *AlpineApkBackend::findResourceByPackageName(const QUrl &searchUrl
+ AlpineApkResource *result = nullptr;
+
+ // QUrl("appstream://org.kde.krita.desktop")
+- // smart workaround for appstream URLs
++ // smart workaround for appstream URLs - handle "featured" apps
+ if (searchUrl.scheme() == QLatin1String("appstream")) {
+ // remove leading "org.kde."
+ QString pkgName = searchUrl.host();
+@@ -177,6 +190,13 @@ ResultsStream *AlpineApkBackend::findResourceByPackageName(const QUrl &searchUrl
+ result = m_resources.value(pkgName);
+ }
+
++ // QUrl("apk://krita")
++ // handle packages from Alpine repos
++ if (searchUrl.scheme() == QLatin1String("apk")) {
++ const QString pkgName = searchUrl.host();
++ result = m_resources.value(pkgName);
++ }
++
+ if (!result) {
+ return new ResultsStream(QStringLiteral("AlpineApkStream"), {});
+ }
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index af480b6c..346a28b2 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -26,7 +26,7 @@ AlpineApkResource::AlpineApkResource(const QtApk::Package &apkPkg,
+ AbstractResourcesBackend *parent)
+ : AbstractResource(parent)
+ , m_state(AbstractResource::State::None)
+- , m_type(Application)
++ , m_type(AbstractResource::Type::Technical)
+ , m_pkg(apkPkg)
+ {
+ }
+--
+GitLab
+
+
+From be1cba7cf2204dfe632a8b976c01eb809dfc2ce0 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 10 Apr 2020 09:00:29 +0300
+Subject: [PATCH 28/62] KAuth helper: disable progress_fd for now
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 16 +++++++++-------
+ 1 file changed, 9 insertions(+), 7 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index bc9c1ec6..11e26786 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -26,6 +26,7 @@
+ #include <QFile>
+
+ #include <KAuthHelperSupport>
++#include <kauth_version.h>
+
+ #include "AlpineApkAuthHelper.h"
+
+@@ -135,14 +136,15 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ m_apkdb.setFakeRoot(fakeRoot);
+ }
+
+- int progress_fd = m_apkdb.progressFd();
+- qCDebug(LOG_AUTHHELPER) << " progress_fd: " << progress_fd;
++ // no progress notifications for now
++ //int progress_fd = m_apkdb.progressFd();
++ //qCDebug(LOG_AUTHHELPER) << " progress_fd: " << progress_fd;
+
+- QScopedPointer<QSocketNotifier> notifier(new QSocketNotifier(progress_fd, QSocketNotifier::Read));
+- QObject::connect(notifier.data(), &QSocketNotifier::activated, notifier.data(), [](int sock) {
+- Q_UNUSED(sock)
+- qCDebug(LOG_AUTHHELPER) << " read trigger from progress_fd!";
+- });
++ //QScopedPointer<QSocketNotifier> notifier(new QSocketNotifier(progress_fd, QSocketNotifier::Read));
++ //QObject::connect(notifier.data(), &QSocketNotifier::activated, notifier.data(), [](int sock) {
++ // Q_UNUSED(sock)
++ // qCDebug(LOG_AUTHHELPER) << " read trigger from progress_fd!";
++ //});
+
+ QtApk::Changeset changes;
+ bool upgrade_ok = m_apkdb.upgrade(flags, &changes);
+--
+GitLab
+
+
+From 10914a3a74fb4854170d9ad47f552541762fb722 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 10 Apr 2020 09:06:17 +0300
+Subject: [PATCH 29/62] AlpineApkUpdater: refactor out helper error handler
+ func
+
+---
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 37 +++++++++----------
+ .../AlpineApkBackend/AlpineApkUpdater.h | 6 +++
+ 2 files changed, 24 insertions(+), 19 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index d0c0482e..173b7e48 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -215,12 +215,11 @@ void AlpineApkUpdater::startCheckForUpdates()
+ updateAction.addArgument(QLatin1String("fakeRoot"), db->fakeRoot());
+
+ // run updates check with elevated privileges to access
+- // system package manager files
++ // system package manager files
+ KAuth::ExecuteJob *reply = updateAction.execute();
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpdateHelperReply);
+- //QObject::connect(reply, &KAuth::ExecuteJob::newData,
+- // this, &AlpineApkUpdater::handleKAuthUpdateHelperProgressStep);
++ // qOverload is needed because of conflict with getter named percent()
+ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
+ this, &AlpineApkUpdater::handleKAuthUpdateHelperProgress);
+
+@@ -236,14 +235,7 @@ void AlpineApkUpdater::handleKAuthUpdateHelperReply(KJob *job)
+ qCDebug(LOG_ALPINEAPK) << "KAuth helper update reply received, updatesCount:" << m_updatesCount;
+ Q_EMIT updatesCountChanged(m_updatesCount);
+ } else {
+- const QString message = replyData.value(QLatin1String("errorString"),
+- reply->errorString()).toString();
+- qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
+- if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
+- Q_EMIT passiveMessage(i18n("Authorization denied"));
+- } else {
+- Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
+- }
++ handleKAuthHelperError(reply, replyData);
+ }
+
+ // we are not in the state "Fetching updates" now, update UI
+@@ -273,17 +265,24 @@ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+ }
+ }
+ } else {
+- const QString message = replyData.value(QLatin1String("errorString"),
+- reply->errorString()).toString();
+- qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
+- if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
+- Q_EMIT passiveMessage(i18n("Authorization denied"));
+- } else {
+- Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
+- }
++ handleKAuthHelperError(reply, replyData);
+ }
+
+ // we are not in the state "Fetching updates" now, update UI
+ Q_EMIT checkForUpdatesFinished();
+ }
+
++void AlpineApkUpdater::handleKAuthHelperError(
++ KAuth::ExecuteJob *reply,
++ const QVariantMap &replyData)
++{
++ const QString message = replyData.value(QLatin1String("errorString"),
++ reply->errorString()).toString();
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
++ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ Q_EMIT passiveMessage(i18n("Authorization denied"));
++ } else {
++ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
++ }
++}
++
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+index 504e8c59..77140ca2 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+@@ -36,6 +36,9 @@
+ class AbstractResourcesBackend;
+ class AlpineApkBackend;
+ class KJob;
++namespace KAuth {
++ class ExecuteJob;
++}
+
+ class AlpineApkUpdater : public AbstractBackendUpdater
+ {
+@@ -164,6 +167,9 @@ public:
+ QVector<QtApk::ChangesetItem> &changes() { return m_upgradeable.changes(); }
+ const QVector<QtApk::ChangesetItem> &changes() const { return m_upgradeable.changes(); }
+
++protected:
++ void handleKAuthHelperError(KAuth::ExecuteJob *reply, const QVariantMap &replyData);
++
+ private:
+ AlpineApkBackend *const m_backend;
+ int m_updatesCount = 0;
+--
+GitLab
+
+
+From ea002ca88aeb3488c7f0b2f3f88b9d785bd8a771 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 10 Apr 2020 09:07:38 +0300
+Subject: [PATCH 30/62] AlpineApkUpdater: use non-deprecated version of
+ setDetails(V2)
+
+since KF 5.68
+---
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 20 +++++++++++++++++--
+ 1 file changed, 18 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 173b7e48..dae6c2a3 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -25,6 +25,7 @@
+ #include "utils.h"
+
+ #include <KAuthExecuteJob>
++#include <kauth_version.h>
+ #include <KLocalizedString>
+
+ #include <QtApk.h>
+@@ -178,8 +179,15 @@ void AlpineApkUpdater::start()
+ return;
+ }
+ upgradeAction.setTimeout(60 * 1000); // 1 minute
+- upgradeAction.setDetails(i18n("Get the list of packages to upgrade"));
+- upgradeAction.addArgument(QLatin1String("onlySimulate"), true);
++#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
++ upgradeAction.setDetails(i18n("Upgrade currently installed packages"));
++#else
++ static const KAuth::Action::DetailsMap details{
++ { KAuth::Action::AuthDetail::DetailMessage, i18n("Upgrade currently installed packages") }
++ };
++ upgradeAction.setDetailsV2(details);
++#endif
++ // upgradeAction.addArgument(QLatin1String("onlySimulate"), true);
+
+ // run upgrade check with elevated privileges
+ KAuth::ExecuteJob *reply = upgradeAction.execute();
+@@ -211,7 +219,15 @@ void AlpineApkUpdater::startCheckForUpdates()
+ return;
+ }
+ updateAction.setTimeout(60 * 1000); // 1 minute
++ // setDetails deprecated since KF 5.68, use setDetailsV2() with DetailsMap.
++#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+ updateAction.setDetails(i18n("Update repositories index"));
++#else
++ static const KAuth::Action::DetailsMap details{
++ { KAuth::Action::AuthDetail::DetailMessage, i18n("Update repositories index") }
++ };
++ updateAction.setDetailsV2(details);
++#endif
+ updateAction.addArgument(QLatin1String("fakeRoot"), db->fakeRoot());
+
+ // run updates check with elevated privileges to access
+--
+GitLab
+
+
+From fdf2085939477dbe1f5a45461529c78b92b953a2 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 10 Apr 2020 09:09:13 +0300
+Subject: [PATCH 31/62] WIP: AlpineApkUpdater: enable upgrade action again
+
+---
+ .../backends/AlpineApkBackend/AlpineApkUpdater.cpp | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index dae6c2a3..89a816c4 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -170,15 +170,17 @@ void AlpineApkUpdater::cancel()
+ void AlpineApkUpdater::start()
+ {
+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- return;
+-#if 0
++ //return;
++//#if 0
+ KAuth::Action upgradeAction(QStringLiteral("org.kde.discover.alpineapkbackend.upgrade"));
+ upgradeAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++
+ if (!upgradeAction.isValid()) {
+ qCWarning(LOG_ALPINEAPK) << "kauth upgradeAction is not valid!";
+ return;
+ }
+- upgradeAction.setTimeout(60 * 1000); // 1 minute
++
++ upgradeAction.setTimeout(30 * 60 * 1000); // 30 min
+ #if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+ upgradeAction.setDetails(i18n("Upgrade currently installed packages"));
+ #else
+@@ -189,13 +191,13 @@ void AlpineApkUpdater::start()
+ #endif
+ // upgradeAction.addArgument(QLatin1String("onlySimulate"), true);
+
+- // run upgrade check with elevated privileges
++ // run upgrade with elevated privileges
+ KAuth::ExecuteJob *reply = upgradeAction.execute();
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
+
+ reply->start();
+-#endif
++//#endif
+ }
+
+ void AlpineApkUpdater::proceed()
+@@ -272,7 +274,7 @@ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+ if (reply->error() == 0) {
+ QVariant pkgsV = replyData.value(QLatin1String("changes"));
+ bool onlySimulate = replyData.value(QLatin1String("onlySimulate"), false).toBool();
+- qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received:" << onlySimulate;
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received, onlySimulate:" << onlySimulate;
+ if (onlySimulate) {
+ QVector<QtApk::Package> pkgVector = pkgsV.value<QVector<QtApk::Package>>();
+ qCDebug(LOG_ALPINEAPK) << " num changes:" << pkgVector.size();
+--
+GitLab
+
+
+From 1061a9bf94a60c28c0f7902392fb6a49f848f8d8 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 11 Jun 2020 22:39:09 +0300
+Subject: [PATCH 32/62] Adapt to changes in libapk-qt 0.3
+
+* Rename link target to ApkQt::ApkQt
+* rename header include <QtApk.h> => <QtApk>
+---
+ .../backends/AlpineApkBackend/AlpineApkAuthHelper.cpp | 8 ++++----
+ .../backends/AlpineApkBackend/AlpineApkAuthHelper.h | 2 +-
+ .../backends/AlpineApkBackend/AlpineApkBackend.cpp | 2 +-
+ libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h | 2 +-
+ .../backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp | 2 +-
+ .../backends/AlpineApkBackend/AlpineApkUpdater.cpp | 6 +++---
+ libdiscover/backends/AlpineApkBackend/CMakeLists.txt | 4 ++--
+ 7 files changed, 13 insertions(+), 13 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 11e26786..0496c8f2 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -73,7 +73,7 @@ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+
+ HelperSupport::progressStep(10);
+
+- if (!m_apkdb.open(QtApk::Database::QTAPK_OPENF_READWRITE)) {
++ if (!m_apkdb.open(QtApk::QTAPK_OPENF_READWRITE)) {
+ reply.setErrorDescription(QStringLiteral("Failed to open database!"));
+ return reply;
+ }
+@@ -119,15 +119,15 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+
+ HelperSupport::progressStep(10);
+
+- if (!m_apkdb.open(QtApk::Database::QTAPK_OPENF_READWRITE)) {
++ if (!m_apkdb.open(QtApk::QTAPK_OPENF_READWRITE)) {
+ reply.setErrorDescription(QStringLiteral("Failed to open database!"));
+ return reply;
+ }
+
+ bool onlySimulate = args.value(QLatin1String("onlySimulate"), false).toBool();
+- QtApk::Database::DbUpgradeFlags flags = QtApk::Database::QTAPK_UPGRADE_DEFAULT;
++ QtApk::DbUpgradeFlags flags = QtApk::QTAPK_UPGRADE_DEFAULT;
+ if (onlySimulate) {
+- flags = QtApk::Database::QTAPK_UPGRADE_SIMULATE;
++ flags = QtApk::QTAPK_UPGRADE_SIMULATE;
+ qCDebug(LOG_AUTHHELPER) << "Simulating upgrade run.";
+ }
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index 5c06e557..a634ce23 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -22,7 +22,7 @@
+ #include <QVariant>
+ #include <KAuthActionReply>
+
+-#include <QtApk.h>
++#include <QtApk>
+
+ using namespace KAuth;
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 750d2820..a089afea 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -70,7 +70,7 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
+
+- if (m_apkdb.open(QtApk::Database::QTAPK_OPENF_READONLY)) {
++ if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
+ m_availablePackages = m_apkdb.getAvailablePackages();
+ m_installedPackages = m_apkdb.getInstalledPackages();
+ m_apkdb.close();
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index d6935257..755cf6a5 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -24,7 +24,7 @@
+ #include <resources/AbstractResourcesBackend.h>
+ #include <QVariantList>
+
+-#include <QtApk.h>
++#include <QtApk>
+
+ class AlpineApkReviewsBackend;
+ class AlpineApkUpdater;
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index b964b40d..225fb443 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -29,7 +29,7 @@
+ #include <KLocalizedString>
+
+ // libapk-qt
+-#include <QtApk.h>
++#include <QtApk>
+
+ AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *parent)
+ : AbstractSourcesBackend(parent)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 89a816c4..0083f340 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -28,7 +28,7 @@
+ #include <kauth_version.h>
+ #include <KLocalizedString>
+
+-#include <QtApk.h>
++#include <QtApk>
+
+
+ AlpineApkUpdater::AlpineApkUpdater(AbstractResourcesBackend *parent)
+@@ -49,12 +49,12 @@ void AlpineApkUpdater::prepare()
+ }
+
+ // readonly is fine for a simulation of upgrade
+- if (!db->open(QtApk::Database::QTAPK_OPENF_READONLY)) {
++ if (!db->open(QtApk::QTAPK_OPENF_READONLY)) {
+ emit passiveMessage(i18n("Failed to open APK database!"));
+ return;
+ }
+
+- if (!db->upgrade(QtApk::Database::QTAPK_UPGRADE_SIMULATE, &m_upgradeable)) {
++ if (!db->upgrade(QtApk::QTAPK_UPGRADE_SIMULATE, &m_upgradeable)) {
+ emit passiveMessage(i18n("Failed to get a list of packages to upgrade!"));
+ db->close();
+ return;
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 42e09c5b..319c7ad2 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -38,7 +38,7 @@ target_link_libraries(
+ KF5::ConfigCore
+ KF5::AuthCore
+ Discover::Common
+- ApkQt::apk-qt
++ ApkQt::ApkQt
+ )
+
+ # KAuth helper exe
+@@ -54,7 +54,7 @@ set_source_files_properties(
+ target_link_libraries(alpineapk_kauth_helper
+ Qt5::Core
+ KF5::AuthCore
+- ApkQt::apk-qt
++ ApkQt::ApkQt
+ )
+
+ kauth_install_actions(org.kde.discover.alpineapkbackend org.kde.discover.alpineapkbackend.actions)
+--
+GitLab
+
+
+From 46c472be152ee67f48e03c8b0bf87bd2a537040e Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 28 Jun 2020 03:38:15 +0300
+Subject: [PATCH 33/62] AlpineApkAuthHelper: implement all operations in async
+ mode
+
+also delete useless test() action
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 234 ++++++++++++------
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 19 +-
+ .../org.kde.discover.alpineapkbackend.actions | 6 -
+ 3 files changed, 181 insertions(+), 78 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 0496c8f2..a3236011 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -40,88 +40,194 @@ using namespace KAuth;
+
+ AlpineApkAuthHelper::AlpineApkAuthHelper() {}
+
+-ActionReply AlpineApkAuthHelper::test(const QVariantMap &args)
++bool AlpineApkAuthHelper::openDatabase(const QVariantMap &args, bool readwrite)
+ {
+- const QString txt = args[QStringLiteral("txt")].toString();
+-
+- ActionReply reply = ActionReply::HelperErrorReply();
+- QByteArray replyData(QByteArrayLiteral("ok"));
++ // maybe set fakeRoot (needs to be done before Database::open()
++ const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
++ if (!fakeRoot.isEmpty()) {
++ m_apkdb.setFakeRoot(fakeRoot);
++ }
+
+- // write some text file at the root directory as root, why not
+- QFile f(QStringLiteral("/lol.txt"));
+- if (f.open(QIODevice::ReadWrite | QIODevice::Text)) {
+- f.write(txt.toUtf8());
+- f.close();
++ // calculate flags to use during open
++ QtApk::DbOpenFlags fl = QtApk::QTAPK_OPENF_ENABLE_PROGRESSFD;
++ if (readwrite) {
++ fl |= QtApk::QTAPK_OPENF_READWRITE;
++ }
+
+- reply = ActionReply::SuccessReply();
+- reply.setData({
+- { QStringLiteral("reply"), replyData },
+- });
++ if (!m_apkdb.open(fl)) {
++ return false;
+ }
++ return true;
++}
+
+- return reply;
++void AlpineApkAuthHelper::closeDatabase()
++{
++ m_apkdb.close();
+ }
+
+-ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
++void AlpineApkAuthHelper::setupTransactionPostCreate(QtApk::Transaction *trans)
+ {
+- ActionReply reply = ActionReply::HelperErrorReply();
++ // receive progress notifications
++ QObject::connect(trans, &QtApk::Transaction::progressChanged,
++ this, &AlpineApkAuthHelper::reportProgress);
+
+- const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
+- if (!fakeRoot.isEmpty()) {
+- m_apkdb.setFakeRoot(fakeRoot);
+- }
++ // receive error messages
++ QObject::connect(trans, &QtApk::Transaction::errorOccured,
++ this, &AlpineApkAuthHelper::onTransactionError);
+
+- HelperSupport::progressStep(10);
++ // what to do when transaction is complete
++ QObject::connect(trans, &QtApk::Transaction::finished,
++ this, &AlpineApkAuthHelper::onTransactionFinished);
++}
+
+- if (!m_apkdb.open(QtApk::QTAPK_OPENF_READWRITE)) {
+- reply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return reply;
++void AlpineApkAuthHelper::reportProgress(float percent)
++{
++ int p = static_cast<int>(percent);
++ if (p < 0) p = 0;
++ if (p > 100) p = 100;
++ HelperSupport::progressStep(p);
++}
++
++void AlpineApkAuthHelper::onTransactionError(const QString &msg)
++{
++ qCWarning(LOG_AUTHHELPER).nospace() << "ERROR occured in transaction \""
++ << m_currentTransaction->desc()
++ << "\": " << msg;
++ const QString errMsg = m_currentTransaction->desc() + QLatin1String(" failed: ") + msg;
++ m_actionReply.setErrorDescription(errMsg);
++ m_actionReply.setData({
++ { QLatin1String("errorString"), errMsg }
++ });
++ m_trans_ok = false;
++}
++
++void AlpineApkAuthHelper::onTransactionFinished()
++{
++ m_lastChangeset = m_currentTransaction->changeset();
++ m_currentTransaction->deleteLater();
++ m_currentTransaction = nullptr;
++ m_loop.quit();
++}
++
++ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
++{
++ // return error by default
++ m_actionReply = ActionReply::HelperErrorReply();
++
++ HelperSupport::progressStep(0);
++
++ if (!openDatabase(args)) {
++ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return m_actionReply;
+ }
+
+- bool update_ok = m_apkdb.updatePackageIndex();
++ m_trans_ok = true;
++ QtApk::Transaction *trans = m_apkdb.updatePackageIndex();
++ setupTransactionPostCreate(trans);
+
+- if (update_ok) {
++ trans->start();
++ m_loop.exec();
++
++ if (m_trans_ok) {
+ int updatesCount = m_apkdb.upgradeablePackagesCount();
+- reply = ActionReply::SuccessReply();
+- reply.setData({
++ m_actionReply = ActionReply::SuccessReply();
++ m_actionReply.setData({
+ { QLatin1String("updatesCount"), updatesCount }
+ });
+- } else {
+- reply.setErrorDescription(QStringLiteral("Repo update failed!"));
+- reply.setData({
+- { QLatin1String("errorString"), QStringLiteral("Repo update failed!") }
+- });
+ }
+
+- m_apkdb.close();
++ closeDatabase();
+ HelperSupport::progressStep(100);
+
+- return reply;
++ return m_actionReply;
+ }
+
+ ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
+ {
+- Q_UNUSED(args)
+- ActionReply reply = ActionReply::HelperErrorReply();
+- return reply;
++ // return error by default
++ m_actionReply = ActionReply::HelperErrorReply();
++
++ HelperSupport::progressStep(0);
++
++ if (!openDatabase(args)) {
++ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return m_actionReply;
++ }
++
++ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
++ if (pkgName.isEmpty()) {
++ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for adding!"));
++ return m_actionReply;
++ }
++
++ m_trans_ok = true;
++ QtApk::Transaction *trans = m_apkdb.add(pkgName);
++ setupTransactionPostCreate(trans);
++
++ trans->start();
++ m_loop.exec();
++
++ if (m_trans_ok) {
++ m_actionReply = ActionReply::SuccessReply();
++ }
++
++ closeDatabase();
++ HelperSupport::progressStep(100);
++
++ return m_actionReply;
+ }
+
+ ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
+ {
+- Q_UNUSED(args)
+- ActionReply reply = ActionReply::HelperErrorReply();
+- return reply;
++ // return error by default
++ m_actionReply = ActionReply::HelperErrorReply();
++
++ HelperSupport::progressStep(0);
++
++ if (!openDatabase(args)) {
++ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return m_actionReply;
++ }
++
++ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
++ if (pkgName.isEmpty()) {
++ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for removing!"));
++ return m_actionReply;
++ }
++
++ const bool delRdepends = args.value(QLatin1String("delRdepends"), false).toBool();
++
++ QtApk::DbDelFlags delFlags = QtApk::QTAPK_DEL_DEFAULT;
++ if (delRdepends) {
++ delFlags = QtApk::QTAPK_DEL_RDEPENDS;
++ }
++
++ m_trans_ok = true;
++ QtApk::Transaction *trans = m_apkdb.del(pkgName, delFlags);
++ setupTransactionPostCreate(trans);
++
++ trans->start();
++ m_loop.exec();
++
++ if (m_trans_ok) {
++ m_actionReply = ActionReply::SuccessReply();
++ }
++
++ closeDatabase();
++ HelperSupport::progressStep(100);
++
++ return m_actionReply;
+ }
+
+ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ {
+- ActionReply reply = ActionReply::HelperErrorReply();
++ m_actionReply = ActionReply::HelperErrorReply();
+
+- HelperSupport::progressStep(10);
++ HelperSupport::progressStep(0);
+
+- if (!m_apkdb.open(QtApk::QTAPK_OPENF_READWRITE)) {
+- reply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return reply;
++ if (!openDatabase(args)) {
++ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
++ return m_actionReply;
+ }
+
+ bool onlySimulate = args.value(QLatin1String("onlySimulate"), false).toBool();
+@@ -131,28 +237,18 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ qCDebug(LOG_AUTHHELPER) << "Simulating upgrade run.";
+ }
+
+- const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
+- if (!fakeRoot.isEmpty()) {
+- m_apkdb.setFakeRoot(fakeRoot);
+- }
+-
+- // no progress notifications for now
+- //int progress_fd = m_apkdb.progressFd();
+- //qCDebug(LOG_AUTHHELPER) << " progress_fd: " << progress_fd;
++ m_trans_ok = true;
+
+- //QScopedPointer<QSocketNotifier> notifier(new QSocketNotifier(progress_fd, QSocketNotifier::Read));
+- //QObject::connect(notifier.data(), &QSocketNotifier::activated, notifier.data(), [](int sock) {
+- // Q_UNUSED(sock)
+- // qCDebug(LOG_AUTHHELPER) << " read trigger from progress_fd!";
+- //});
++ QtApk::Transaction *trans = m_apkdb.upgrade(flags);
++ setupTransactionPostCreate(trans);
+
+- QtApk::Changeset changes;
+- bool upgrade_ok = m_apkdb.upgrade(flags, &changes);
++ trans->start();
++ m_loop.exec();
+
+- if (upgrade_ok) {
+- reply = ActionReply::SuccessReply();
++ if (m_trans_ok) {
++ m_actionReply = ActionReply::SuccessReply();
+ QVariantMap replyData;
+- const QVector<QtApk::ChangesetItem> ch = changes.changes();
++ const QVector<QtApk::ChangesetItem> ch = m_lastChangeset.changes();
+ QVector<QVariant> chVector;
+ QVector<QtApk::Package> pkgVector;
+ for (const QtApk::ChangesetItem &it: ch) {
+@@ -160,15 +256,13 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ }
+ replyData.insert(QLatin1String("changes"), QVariant::fromValue(pkgVector));
+ replyData.insert(QLatin1String("onlySimulate"), onlySimulate);
+- reply.setData(replyData);
+- } else {
+- reply.setErrorDescription(QStringLiteral("Repo upgrade failed!"));
++ m_actionReply.setData(replyData);
+ }
+
+- m_apkdb.close();
++ closeDatabase();
+ HelperSupport::progressStep(100);
+
+- return reply;
++ return m_actionReply;
+ }
+
+ KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index a634ce23..a670a2aa 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -18,6 +18,7 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
++#include <QEventLoop>
+ #include <QObject>
+ #include <QVariant>
+ #include <KAuthActionReply>
+@@ -33,12 +34,26 @@ public:
+ AlpineApkAuthHelper();
+
+ public Q_SLOTS:
+- ActionReply test(const QVariantMap &args);
+ ActionReply update(const QVariantMap &args);
+ ActionReply add(const QVariantMap &args);
+ ActionReply del(const QVariantMap &args);
+ ActionReply upgrade(const QVariantMap &args);
+
++protected:
++ bool openDatabase(const QVariantMap &args, bool readwrite = true);
++ void closeDatabase();
++ void setupTransactionPostCreate(QtApk::Transaction *trans);
++
++protected Q_SLOTS:
++ void reportProgress(float percent);
++ void onTransactionError(const QString &msg);
++ void onTransactionFinished();
++
+ private:
+- QtApk::Database m_apkdb;
++ QtApk::DatabaseAsync m_apkdb; // runs transactions in bg thread
++ QtApk::Transaction *m_currentTransaction = nullptr;
++ QEventLoop m_loop; // event loop that will run and wait while bg transaction is in progress
++ ActionReply m_actionReply; // return value for main action slots
++ bool m_trans_ok = true; // flag to indicate if bg transaction was successful
++ QtApk::Changeset m_lastChangeset; // changeset from last completed transaction
+ };
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index 10305a1a..0755c415 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -1,9 +1,3 @@
+-[org.kde.discover.alpineapkbackend.test]
+-Name=Test Action
+-Description=Just test
+-Policy=auth_admin
+-Persistence=session
+-
+ [org.kde.discover.alpineapkbackend.update]
+ Name=Update repository index
+ Description=Updates available packages list from repositories
+--
+GitLab
+
+
+From 0e6cedcab270b845a8391491f2eb21bfb63b9137 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 29 Jun 2020 03:22:42 +0300
+Subject: [PATCH 34/62] AlpineApkAuthHelper: add method to write repositories
+ config
+
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 21 +++++++++++++++++++
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 1 +
+ .../org.kde.discover.alpineapkbackend.actions | 6 ++++++
+ 3 files changed, 28 insertions(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index a3236011..81c18255 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -265,4 +265,25 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ return m_actionReply;
+ }
+
++ActionReply AlpineApkAuthHelper::repoconfig(const QVariantMap &args)
++{
++ m_actionReply = ActionReply::HelperErrorReply();
++ HelperSupport::progressStep(10);
++
++ if (args.contains(QLatin1String("repoList"))) {
++ const QVariant v = args.value(QLatin1String("repoList"));
++ const QVector<QtApk::Repository> repoVec = v.value<QVector<QtApk::Repository>>();
++ if (QtApk::Database::saveRepositories(repoVec)) {
++ m_actionReply = ActionReply::SuccessReply(); // OK
++ } else {
++ m_actionReply.setErrorDescription(QStringLiteral("Failed to write repositories config!"));
++ }
++ } else {
++ m_actionReply.setErrorDescription(QStringLiteral("repoList parameter is missing in request!"));
++ }
++
++ HelperSupport::progressStep(100);
++ return m_actionReply;
++}
++
+ KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index a670a2aa..acce5648 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -38,6 +38,7 @@ public Q_SLOTS:
+ ActionReply add(const QVariantMap &args);
+ ActionReply del(const QVariantMap &args);
+ ActionReply upgrade(const QVariantMap &args);
++ ActionReply repoconfig(const QVariantMap &args);
+
+ protected:
+ bool openDatabase(const QVariantMap &args, bool readwrite = true);
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index 0755c415..f1bffe65 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -21,3 +21,9 @@ Name=Remove package
+ Description=Uninstall one package
+ Policy=auth_admin
+ Persistence=session
++
++[org.kde.discover.alpineapkbackend.repoconfig]
++Name=Remove package
++Description=Configure repositories URLs
++Policy=auth_admin
++Persistence=session
+--
+GitLab
+
+
+From a29f4083d97a854b988d14b34400f6812b5000c7 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 29 Jun 2020 06:52:42 +0300
+Subject: [PATCH 35/62] AlpineApkAuthHelper: create event loop only when needed
+
+It fixes error "QEventLoop: requires QApplication",
+this makes it creation delayed to moment *after*
+QCoreApplication object is created by KAuthCore
+internals.
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 14 +++++++++-----
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 2 +-
+ 2 files changed, 10 insertions(+), 6 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index 81c18255..b436aa73 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -78,6 +78,10 @@ void AlpineApkAuthHelper::setupTransactionPostCreate(QtApk::Transaction *trans)
+ // what to do when transaction is complete
+ QObject::connect(trans, &QtApk::Transaction::finished,
+ this, &AlpineApkAuthHelper::onTransactionFinished);
++
++ if (!m_loop) {
++ m_loop = new QEventLoop(this);
++ }
+ }
+
+ void AlpineApkAuthHelper::reportProgress(float percent)
+@@ -106,7 +110,7 @@ void AlpineApkAuthHelper::onTransactionFinished()
+ m_lastChangeset = m_currentTransaction->changeset();
+ m_currentTransaction->deleteLater();
+ m_currentTransaction = nullptr;
+- m_loop.quit();
++ m_loop->quit();
+ }
+
+ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+@@ -126,7 +130,7 @@ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+ setupTransactionPostCreate(trans);
+
+ trans->start();
+- m_loop.exec();
++ m_loop->exec();
+
+ if (m_trans_ok) {
+ int updatesCount = m_apkdb.upgradeablePackagesCount();
+@@ -165,7 +169,7 @@ ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
+ setupTransactionPostCreate(trans);
+
+ trans->start();
+- m_loop.exec();
++ m_loop->exec();
+
+ if (m_trans_ok) {
+ m_actionReply = ActionReply::SuccessReply();
+@@ -207,7 +211,7 @@ ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
+ setupTransactionPostCreate(trans);
+
+ trans->start();
+- m_loop.exec();
++ m_loop->exec();
+
+ if (m_trans_ok) {
+ m_actionReply = ActionReply::SuccessReply();
+@@ -243,7 +247,7 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ setupTransactionPostCreate(trans);
+
+ trans->start();
+- m_loop.exec();
++ m_loop->exec();
+
+ if (m_trans_ok) {
+ m_actionReply = ActionReply::SuccessReply();
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index acce5648..eed39f28 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -53,7 +53,7 @@ protected Q_SLOTS:
+ private:
+ QtApk::DatabaseAsync m_apkdb; // runs transactions in bg thread
+ QtApk::Transaction *m_currentTransaction = nullptr;
+- QEventLoop m_loop; // event loop that will run and wait while bg transaction is in progress
++ QEventLoop *m_loop = nullptr; // event loop that will run and wait while bg transaction is in progress
+ ActionReply m_actionReply; // return value for main action slots
+ bool m_trans_ok = true; // flag to indicate if bg transaction was successful
+ QtApk::Changeset m_lastChangeset; // changeset from last completed transaction
+--
+GitLab
+
+
+From 731de8079606f91c9631ed1f26c92a4c11a4195a Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 29 Jun 2020 06:55:59 +0300
+Subject: [PATCH 36/62] AlpineApkAuthHelper: fix nullptr deref in
+ onTransactionFinished
+
+m_currentTransaction was never assigned
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index b436aa73..f5d8e17e 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -67,6 +67,8 @@ void AlpineApkAuthHelper::closeDatabase()
+
+ void AlpineApkAuthHelper::setupTransactionPostCreate(QtApk::Transaction *trans)
+ {
++ m_currentTransaction = trans; // remember current transaction here
++
+ // receive progress notifications
+ QObject::connect(trans, &QtApk::Transaction::progressChanged,
+ this, &AlpineApkAuthHelper::reportProgress);
+--
+GitLab
+
+
+From 675e5a1dc6d0415be6b63a1d5296ad7092724b3d Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 30 Jun 2020 05:39:42 +0300
+Subject: [PATCH 37/62] AlpineApkSourcesBackend: fully functional sources
+ editor
+
+* allows enable/disable repo
+* allows to add/remove repo
+* allows to change sources order
+* allows to reload/save changes
+---
+ .../AlpineApkSourcesBackend.cpp | 130 +++++++++++++-----
+ .../AlpineApkSourcesBackend.h | 7 +
+ 2 files changed, 102 insertions(+), 35 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 225fb443..28f08ef8 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -26,6 +26,8 @@
+ #include <QVector>
+
+ // KF5
++#include <KAuthExecuteJob>
++#include <kauth_version.h>
+ #include <KLocalizedString>
+
+ // libapk-qt
+@@ -35,16 +37,19 @@ AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *paren
+ : AbstractSourcesBackend(parent)
+ , m_sourcesModel(new QStandardItemModel(this))
+ , m_refreshAction(new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")),
+- QStringLiteral("Refresh"), this))
++ QStringLiteral("Reload"), this))
++ , m_saveAction(new QAction(QIcon::fromTheme(QStringLiteral("document-save")),
++ QStringLiteral("Save"), this))
++ // ^^ unfortunately QML side ignores icons for custom actions
+ {
+ loadSources();
+ QObject::connect(m_refreshAction, &QAction::triggered,
+ this, &AlpineApkSourcesBackend::loadSources);
+-
+- // can be used to track enabling/disabling repo source
+- // QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) {
+- // qCDebug(LOG_ALPINEAPK) << "source backend: DummySource changed" << item << item->checkState();
+- // });
++ QObject::connect(m_saveAction, &QAction::triggered,
++ this, &AlpineApkSourcesBackend::saveSources);
++ // track enabling/disabling repo source
++ QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged,
++ this, &AlpineApkSourcesBackend::onItemChanged);
+ }
+
+ QAbstractItemModel *AlpineApkSourcesBackend::sources()
+@@ -52,48 +57,97 @@ QAbstractItemModel *AlpineApkSourcesBackend::sources()
+ return m_sourcesModel;
+ }
+
++QStandardItem *AlpineApkSourcesBackend::sourceForId(const QString& id) const
++{
++ for (int i = 0; i < m_sourcesModel->rowCount(); ++i) {
++ QStandardItem *item = m_sourcesModel->item(i, 0);
++ if (item->data(AbstractSourcesBackend::IdRole) == id) {
++ return item;
++ }
++ }
++ return nullptr;
++}
++
+ bool AlpineApkSourcesBackend::addSource(const QString &id)
+ {
+- return addSourceFull(id, QString(), true);
++ m_repos.append(QtApk::Repository(id, QString(), true));
++ fillModelFromRepos();
++ return true;
+ }
+
+-QStandardItem *AlpineApkSourcesBackend::sourceForId(const QString& id) const
++void AlpineApkSourcesBackend::loadSources()
++{
++ m_repos = QtApk::Database::getRepositories();
++ fillModelFromRepos();
++}
++
++void AlpineApkSourcesBackend::fillModelFromRepos()
+ {
+- for (int i = 0, c = m_sourcesModel->rowCount(); i < c; ++i) {
+- QStandardItem *it = m_sourcesModel->item(i, 0);
+- if (it->text() == id) {
+- return it;
++ m_sourcesModel->clear();
++ for (const QtApk::Repository &repo: m_repos) {
++ if (repo.url.isEmpty()) {
++ continue;
+ }
++ qCDebug(LOG_ALPINEAPK) << "source backend: Adding source:" << repo.url << repo.enabled;
++ QStandardItem *it = new QStandardItem(repo.url);
++ it->setData(repo.url, AbstractSourcesBackend::IdRole);
++ it->setData(repo.comment, Qt::ToolTipRole);
++ it->setCheckable(true);
++ it->setCheckState(repo.enabled ? Qt::Checked : Qt::Unchecked);
++ m_sourcesModel->appendRow(it);
+ }
+- return nullptr;
+ }
+
+-bool AlpineApkSourcesBackend::addSourceFull(const QString &id, const QString &comment, bool enabled)
++void AlpineApkSourcesBackend::saveSources()
+ {
+- if (id.isEmpty()) {
+- return false;
++ KAuth::Action repoConfigAction(QStringLiteral("org.kde.discover.alpineapkbackend.repoconfig"));
++ repoConfigAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++ if (!repoConfigAction.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "repoConfigAction is not valid!";
++ return;
+ }
+
+- qCDebug(LOG_ALPINEAPK) << "source backend: Adding source:" << id;
++ repoConfigAction.setTimeout(1 * 60 * 1000); // 1 min
++#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
++ upgradeAction.setDetails(i18n("Configure repositories URLs"));
++#else
++ static const KAuth::Action::DetailsMap details{
++ { KAuth::Action::AuthDetail::DetailMessage, i18n("Configure repositories URLs") }
++ };
++ repoConfigAction.setDetailsV2(details);
++#endif
++ // pass in new repositories list
++ repoConfigAction.addArgument(QLatin1String("repoList"),
++ QVariant::fromValue<QVector<QtApk::Repository>>(m_repos));
++
++ // run with elevated privileges
++ KAuth::ExecuteJob *reply = repoConfigAction.execute();
++ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this] (KJob *job) {
++ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
++ if (reply->error() != 0) {
++ const QString errMessage = reply->errorString();
++ qCWarning(LOG_ALPINEAPK) << "KAuth helper returned error:"
++ << reply->error() << errMessage;
++ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ Q_EMIT passiveMessage(i18n("Authorization denied"));
++ } else {
++ Q_EMIT passiveMessage(i18n("Error: ") + errMessage);
++ }
++ }
++ this->loadSources();
++ });
+
+- QStandardItem *it = new QStandardItem(id);
+- it->setData(id, AbstractSourcesBackend::IdRole);
+- it->setData(comment, Qt::ToolTipRole);
+- it->setCheckable(true);
+- it->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
+- // for now, disable editing sources
+- it->setFlags(it->flags() & ~Qt::ItemIsEnabled);
+- m_sourcesModel->appendRow(it);
+- return true;
++ reply->start();
+ }
+
+-void AlpineApkSourcesBackend::loadSources()
++void AlpineApkSourcesBackend::onItemChanged(QStandardItem *item)
+ {
+- QVector<QtApk::Repository> repos = QtApk::Database::getRepositories();
+- m_sourcesModel->clear();
+- for (const QtApk::Repository &repo: repos) {
+- addSourceFull(repo.url, repo.comment, repo.enabled);
+- }
++ // update internal storage vector and relaod model from it
++ // otherwise checks state are not updated in UI
++ const Qt::CheckState cs = item->checkState();
++ const QModelIndex idx = m_sourcesModel->indexFromItem(item);
++ m_repos[idx.row()].enabled = (cs == Qt::Checked);
++ fillModelFromRepos();
+ }
+
+ bool AlpineApkSourcesBackend::removeSource(const QString &id)
+@@ -103,18 +157,20 @@ bool AlpineApkSourcesBackend::removeSource(const QString &id)
+ qCWarning(LOG_ALPINEAPK) << "source backend: couldn't find " << id;
+ return false;
+ }
++ m_repos.remove(it->row());
+ return m_sourcesModel->removeRow(it->row());
+ }
+
+ QString AlpineApkSourcesBackend::idDescription()
+ {
+- return i18nc("Adding repo", "Enter apk repository URL, for example: "
++ return i18nc("Adding repo", "Enter Alpine repository URL, for example: "
+ "http://dl-cdn.alpinelinux.org/alpine/edge/testing/");
+ }
+
+ QVariantList AlpineApkSourcesBackend::actions() const
+ {
+ static const QVariantList s_actions {
++ QVariant::fromValue<QObject *>(m_saveAction),
+ QVariant::fromValue<QObject *>(m_refreshAction),
+ };
+ return s_actions;
+@@ -122,12 +178,12 @@ QVariantList AlpineApkSourcesBackend::actions() const
+
+ bool AlpineApkSourcesBackend::supportsAdding() const
+ {
+- return false; // for now, disable editing sources
++ return true;
+ }
+
+ bool AlpineApkSourcesBackend::canMoveSources() const
+ {
+- return false; // for now, disable editing sources
++ return true;
+ }
+
+ bool AlpineApkSourcesBackend::moveSource(const QString& sourceId, int delta)
+@@ -147,5 +203,9 @@ bool AlpineApkSourcesBackend::moveSource(const QString& sourceId, int delta)
+ || row == (m_sourcesModel->rowCount() - 1)) {
+ Q_EMIT lastSourceIdChanged();
+ }
++
++ // swap also items in internal storage vector
++ m_repos.swapItemsAt(row, destRow);
++
+ return true;
+ }
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+index 57894591..eacda22d 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+@@ -24,6 +24,8 @@
+ #include <resources/AbstractSourcesBackend.h>
+ #include <QStandardItemModel>
+
++#include <QtApkRepository.h>
++
+ class AlpineApkSourcesBackend : public AbstractSourcesBackend
+ {
+ public:
+@@ -42,9 +44,14 @@ private:
+ QStandardItem *sourceForId(const QString &id) const;
+ bool addSourceFull(const QString &id, const QString &comment, bool enabled);
+ void loadSources();
++ void saveSources();
++ void fillModelFromRepos();
++ void onItemChanged(QStandardItem* item);
+
+ QStandardItemModel *m_sourcesModel = nullptr;
+ QAction *m_refreshAction = nullptr;
++ QAction *m_saveAction = nullptr;
++ QVector<QtApk::Repository> m_repos;
+ };
+
+ #endif // ALPINEAPKSOURCESBACKEND_H
+--
+GitLab
+
+
+From 355f05379ed44cbd04208cba261995cb0adcb727 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 1 Jul 2020 08:34:10 +0300
+Subject: [PATCH 38/62] AlpineApkUpdater: update progressing status
+
+---
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 25 +++++++++++--------
+ .../AlpineApkBackend/AlpineApkUpdater.h | 2 +-
+ 2 files changed, 16 insertions(+), 11 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 0083f340..2f3d184f 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -125,20 +125,18 @@ QDateTime AlpineApkUpdater::lastUpdate() const
+ bool AlpineApkUpdater::isCancelable() const
+ {
+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- return true;
++ return false;
+ }
+
+ bool AlpineApkUpdater::isProgressing() const
+ {
+- qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- return false;
++ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_progressing;
++ return m_progressing;
+ }
+
+ bool AlpineApkUpdater::isMarked(AbstractResource *res) const
+ {
+- // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+ return m_markedToUpdate.contains(res);
+- // return true;
+ }
+
+ void AlpineApkUpdater::fetchChangelog() const
+@@ -148,7 +146,6 @@ void AlpineApkUpdater::fetchChangelog() const
+
+ double AlpineApkUpdater::updateSize() const
+ {
+- // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+ double sum = 0.0;
+ for (AbstractResource *res : m_markedToUpdate) {
+ sum += res->size();
+@@ -170,8 +167,6 @@ void AlpineApkUpdater::cancel()
+ void AlpineApkUpdater::start()
+ {
+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- //return;
+-//#if 0
+ KAuth::Action upgradeAction(QStringLiteral("org.kde.discover.alpineapkbackend.upgrade"));
+ upgradeAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+
+@@ -196,8 +191,10 @@ void AlpineApkUpdater::start()
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
+
++ m_progressing = true;
++ Q_EMIT progressingChanged(m_progressing);
++
+ reply->start();
+-//#endif
+ }
+
+ void AlpineApkUpdater::proceed()
+@@ -207,7 +204,7 @@ void AlpineApkUpdater::proceed()
+
+ int AlpineApkUpdater::updatesCount()
+ {
+- // qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
++ qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
+ return m_updatesCount;
+ }
+
+@@ -241,6 +238,10 @@ void AlpineApkUpdater::startCheckForUpdates()
+ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
+ this, &AlpineApkUpdater::handleKAuthUpdateHelperProgress);
+
++ m_progressing = true;
++ Q_EMIT progressingChanged(m_progressing);
++ Q_EMIT progressChanged(0);
++
+ reply->start();
+ }
+
+@@ -256,6 +257,9 @@ void AlpineApkUpdater::handleKAuthUpdateHelperReply(KJob *job)
+ handleKAuthHelperError(reply, replyData);
+ }
+
++ m_progressing = false;
++ Q_EMIT progressingChanged(m_progressing);
++
+ // we are not in the state "Fetching updates" now, update UI
+ Q_EMIT checkForUpdatesFinished();
+ }
+@@ -265,6 +269,7 @@ void AlpineApkUpdater::handleKAuthUpdateHelperProgress(KJob *job, unsigned long
+ Q_UNUSED(job)
+ qCDebug(LOG_ALPINEAPK) << " fetch updates progress: " << percent;
+ Q_EMIT fetchingUpdatesProgressChanged(percent);
++ Q_EMIT progressChanged(static_cast<qreal>(percent));
+ }
+
+ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+index 77140ca2..0ee2fcb4 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+@@ -185,7 +185,7 @@ private:
+
+ // QSet<AbstractResource*> m_upgradeable;
+ // QSet<AbstractResource*> m_pendingResources;
+-// bool m_settingUp;
++ bool m_progressing = false;
+ // qreal m_progress;
+ // QDateTime m_lastUpdate;
+ // QTimer m_timer;
+--
+GitLab
+
+
+From 53c7dcf6655a2a7c4d979c59780ed2e4b86da6d0 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 1 Jul 2020 10:06:03 +0300
+Subject: [PATCH 39/62] AlpineApkTransaction: transaction should know its
+ backend
+
+---
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 28 +++++++++----------
+ .../AlpineApkBackend/AlpineApkTransaction.h | 27 ++++++++++--------
+ 2 files changed, 29 insertions(+), 26 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index b4f90df5..fdc1d053 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -26,7 +26,6 @@
+ #include <QDebug>
+ #include <KRandom>
+
+-// #define TEST_PROCEED
+
+ AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, Role role)
+ : AlpineApkTransaction(app, {}, role)
+@@ -36,12 +35,25 @@ AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, Role role)
+ AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, const AddonList &addons, Transaction::Role role)
+ : Transaction(app->backend(), app, role, addons)
+ , m_app(app)
++ , m_backend(static_cast<AlpineApkBackend *>(app->backend()))
+ {
+- setCancellable(true);
++ setCancellable(false);
+ setStatus(DownloadingStatus);
+ iterateTransaction();
+ }
+
++void AlpineApkTransaction::proceed()
++{
++ finishTransaction();
++}
++
++void AlpineApkTransaction::cancel()
++{
++ m_iterate = false;
++
++ setStatus(CancelledStatus);
++}
++
+ void AlpineApkTransaction::iterateTransaction()
+ {
+ if (!m_iterate) {
+@@ -59,18 +71,6 @@ void AlpineApkTransaction::iterateTransaction()
+ }
+ }
+
+-void AlpineApkTransaction::proceed()
+-{
+- finishTransaction();
+-}
+-
+-void AlpineApkTransaction::cancel()
+-{
+- m_iterate = false;
+-
+- setStatus(CancelledStatus);
+-}
+-
+ void AlpineApkTransaction::finishTransaction()
+ {
+ AbstractResource::State newState;
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+index 63aeef8d..ba0a7750 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+@@ -23,24 +23,27 @@
+
+ #include <Transaction/Transaction.h>
+
++class AlpineApkBackend;
+ class AlpineApkResource;
++
+ class AlpineApkTransaction : public Transaction
+ {
+- Q_OBJECT
+- public:
+- AlpineApkTransaction(AlpineApkResource *app, Role role);
+- AlpineApkTransaction(AlpineApkResource *app, const AddonList &list, Role role);
++Q_OBJECT
++public:
++ AlpineApkTransaction(AlpineApkResource *app, Role role);
++ AlpineApkTransaction(AlpineApkResource *app, const AddonList &list, Role role);
+
+- void cancel() override;
+- void proceed() override;
++ void cancel() override;
++ void proceed() override;
+
+- private Q_SLOTS:
+- void iterateTransaction();
+- void finishTransaction();
++private Q_SLOTS:
++ void iterateTransaction();
++ void finishTransaction();
+
+- private:
+- bool m_iterate = true;
+- AlpineApkResource *m_app;
++private:
++ bool m_iterate = true;
++ AlpineApkResource *m_app;
++ AlpineApkBackend *m_backend;
+ };
+
+ #endif // ALPINEAPKTRANSACTION_H
+--
+GitLab
+
+
+From bc80a45c1214dbaf4d26698b59ec2a7687568578 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Tue, 28 Jul 2020 21:05:11 +0300
+Subject: [PATCH 40/62] AlpineApkBackend: style fixes
+
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 15 ++++++++++-----
+ 1 file changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index a089afea..b695e5e6 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -66,7 +66,7 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ this, &AlpineApkBackend::finishCheckForUpdates);
+ m_updatesTimeoutTimer->setTimerType(Qt::CoarseTimer);
+ m_updatesTimeoutTimer->setSingleShot(true);
+- m_updatesTimeoutTimer->setInterval(2 * 60 * 1000); // 2minutes
++ m_updatesTimeoutTimer->setInterval(5 * 60 * 1000); // 5 minutes
+
+ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
+
+@@ -87,7 +87,8 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ // which places it into "System updates" section
+ const QString key = pkg.name.toLower();
+ m_resources.insert(key, res);
+- connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
++ connect(res, &AlpineApkResource::stateChanged,
++ this, &AlpineApkBackend::updatesCountChanged);
+ }
+ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
+ << "packages";
+@@ -215,17 +216,20 @@ AbstractReviewsBackend *AlpineApkBackend::reviewsBackend() const
+
+ Transaction* AlpineApkBackend::installApplication(AbstractResource *app, const AddonList &addons)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), addons, Transaction::InstallRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
++ addons, Transaction::InstallRole);
+ }
+
+ Transaction* AlpineApkBackend::installApplication(AbstractResource *app)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), Transaction::InstallRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
++ Transaction::InstallRole);
+ }
+
+ Transaction* AlpineApkBackend::removeApplication(AbstractResource *app)
+ {
+- return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app), Transaction::RemoveRole);
++ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
++ Transaction::RemoveRole);
+ }
+
+ int AlpineApkBackend::fetchingUpdatesProgress() const
+@@ -281,4 +285,5 @@ void AlpineApkBackend::setFetchingUpdatesProgress(int percent)
+ emit fetchingUpdatesProgressChanged();
+ }
+
++// needed because DISCOVER_BACKEND_PLUGIN(AlpineApkBackend) contains Q_OBJECT
+ #include "AlpineApkBackend.moc"
+--
+GitLab
+
+
+From 030de6832ec272e596db97577994a103ddc53e57 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jul 2020 03:48:50 +0300
+Subject: [PATCH 41/62] AlpineApk KAuth backend: fix repoconfig action name
+
+---
+ .../AlpineApkBackend/org.kde.discover.alpineapkbackend.actions | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index f1bffe65..5f7f7677 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -23,7 +23,7 @@ Policy=auth_admin
+ Persistence=session
+
+ [org.kde.discover.alpineapkbackend.repoconfig]
+-Name=Remove package
++Name=Configure repositories
+ Description=Configure repositories URLs
+ Policy=auth_admin
+ Persistence=session
+--
+GitLab
+
+
+From 3c121cf5f78c515869228428e949d09f207e2c0c Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jul 2020 03:50:27 +0300
+Subject: [PATCH 42/62] AlpineApkAuthHelper: clsoe database only on helper exit
+
+Don't be so quick to close database after each operation, close it in
+Helper class destructor, on helper exit.
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 24 ++++++++++++-------
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 1 +
+ 2 files changed, 16 insertions(+), 9 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index f5d8e17e..e6e6cb7a 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -40,8 +40,18 @@ using namespace KAuth;
+
+ AlpineApkAuthHelper::AlpineApkAuthHelper() {}
+
++AlpineApkAuthHelper::~AlpineApkAuthHelper()
++{
++ closeDatabase();
++}
++
+ bool AlpineApkAuthHelper::openDatabase(const QVariantMap &args, bool readwrite)
+ {
++ // is already opened?
++ if (m_apkdb.isOpen()) {
++ return true;
++ }
++
+ // maybe set fakeRoot (needs to be done before Database::open()
+ const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
+ if (!fakeRoot.isEmpty()) {
+@@ -62,7 +72,11 @@ bool AlpineApkAuthHelper::openDatabase(const QVariantMap &args, bool readwrite)
+
+ void AlpineApkAuthHelper::closeDatabase()
+ {
+- m_apkdb.close();
++ // close database only if opened
++ if (m_apkdb.isOpen()) {
++ // this also stops bg thread
++ m_apkdb.close();
++ }
+ }
+
+ void AlpineApkAuthHelper::setupTransactionPostCreate(QtApk::Transaction *trans)
+@@ -142,9 +156,7 @@ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+ });
+ }
+
+- closeDatabase();
+ HelperSupport::progressStep(100);
+-
+ return m_actionReply;
+ }
+
+@@ -177,9 +189,7 @@ ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
+ m_actionReply = ActionReply::SuccessReply();
+ }
+
+- closeDatabase();
+ HelperSupport::progressStep(100);
+-
+ return m_actionReply;
+ }
+
+@@ -219,9 +229,7 @@ ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
+ m_actionReply = ActionReply::SuccessReply();
+ }
+
+- closeDatabase();
+ HelperSupport::progressStep(100);
+-
+ return m_actionReply;
+ }
+
+@@ -265,9 +273,7 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ m_actionReply.setData(replyData);
+ }
+
+- closeDatabase();
+ HelperSupport::progressStep(100);
+-
+ return m_actionReply;
+ }
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index eed39f28..7fd69a8f 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -32,6 +32,7 @@ class AlpineApkAuthHelper : public QObject
+ Q_OBJECT
+ public:
+ AlpineApkAuthHelper();
++ ~AlpineApkAuthHelper() override;
+
+ public Q_SLOTS:
+ ActionReply update(const QVariantMap &args);
+--
+GitLab
+
+
+From 6489f33f67a1b68e440b0eea3192e0edcb6721a3 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 29 Jul 2020 03:59:10 +0300
+Subject: [PATCH 43/62] AlpineApkTransaction: implement add/del packages.
+
+---
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 120 ++++++++++++++----
+ .../AlpineApkBackend/AlpineApkTransaction.h | 12 +-
+ 2 files changed, 101 insertions(+), 31 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index fdc1d053..b5d659da 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -22,56 +22,118 @@
+ #include "AlpineApkBackend.h"
+ #include "AlpineApkResource.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+-#include <QTimer>
++
++// Qt
+ #include <QDebug>
+-#include <KRandom>
++#include <QTimer>
+
++// KF5
++#include <KAuthExecuteJob>
++#include <kauth_version.h>
++#include <KLocalizedString>
+
+-AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, Role role)
+- : AlpineApkTransaction(app, {}, role)
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *res, Role role)
++ : AlpineApkTransaction(res, {}, role)
+ {
+ }
+
+-AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *app, const AddonList &addons, Transaction::Role role)
+- : Transaction(app->backend(), app, role, addons)
+- , m_app(app)
+- , m_backend(static_cast<AlpineApkBackend *>(app->backend()))
++AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *res, const AddonList &addons, Transaction::Role role)
++ : Transaction(res->backend(), res, role, addons)
++ , m_resource(res)
++ , m_backend(static_cast<AlpineApkBackend *>(res->backend()))
+ {
+ setCancellable(false);
+- setStatus(DownloadingStatus);
+- iterateTransaction();
++ setStatus(QueuedStatus);
++ // seems like Discover's transactions are supposed to start
++ // automatically; no dedicated method to start transaction?
++ startTransaction();
+ }
+
+ void AlpineApkTransaction::proceed()
+ {
+- finishTransaction();
++ startTransaction();
+ }
+
+ void AlpineApkTransaction::cancel()
+ {
+- m_iterate = false;
+-
+ setStatus(CancelledStatus);
+ }
+
+-void AlpineApkTransaction::iterateTransaction()
++void AlpineApkTransaction::startTransaction()
+ {
+- if (!m_iterate) {
++ KAuth::Action authAction;
++ QString actionDescription(i18n("Install package"));
++ switch(role()) {
++ case InstallRole:
++ authAction.setName(QStringLiteral("org.kde.discover.alpineapkbackend.add"));
++ break;
++ case RemoveRole:
++ authAction.setName(QStringLiteral("org.kde.discover.alpineapkbackend.del"));
++ actionDescription = i18n("Remove package");
++ break;
++ case ChangeAddonsRole:
++ qCWarning(LOG_ALPINEAPK) << "Addons are not supported by Alpine APK Backend!";
+ return;
++ break;
+ }
++ authAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+
+- if(progress() < 100) {
+- setProgress(qBound(0, progress() + (KRandom::random() % 30), 100));
+- QTimer::singleShot(/*KRandom::random()%*/100, this, &AlpineApkTransaction::iterateTransaction);
+- } else if (status() == DownloadingStatus) {
+- setStatus(CommittingStatus);
+- QTimer::singleShot(/*KRandom::random()%*/100, this, &AlpineApkTransaction::iterateTransaction);
++ if (!authAction.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "kauth addAction is not valid!";
++ return;
++ }
++
++ authAction.setTimeout(60 * 60 * 1000); // 60 min
++#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
++ addAction.setDetails(actionDescription);
++#else
++ const KAuth::Action::DetailsMap details{
++ { KAuth::Action::AuthDetail::DetailMessage, actionDescription }
++ };
++ authAction.setDetailsV2(details);
++#endif
++ authAction.addArgument(QLatin1String("pkgName"), m_resource->m_pkg.name);
++
++ // run upgrade with elevated privileges
++ KAuth::ExecuteJob *reply = authAction.execute();
++
++ // get result of this job
++ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this](KJob *job) {
++ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
++ const QVariantMap &replyData = reply->data();
++ if (reply->error() == 0) {
++ finishTransactionOK();
++ } else {
++ QString message = replyData.value(QLatin1String("errorString"),
++ reply->errorString()).toString();
++ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ message = i18n("Error: Authorization denied");
++ }
++ finishTransactionWithError(message);
++ }
++ });
++
++ // get progress reports for this job
++ QObject::connect(reply, QOverload<KJob*, unsigned long>::of(&KAuth::ExecuteJob::percent), this,
++ [this](KJob *job, unsigned long percent) {
++ Q_UNUSED(job)
++ if (percent >= 40 && role() == InstallRole) {
++ setStatus(CommittingStatus);
++ }
++ setProgress(static_cast<int>(percent));
++ });
++
++ setProgress(0);
++ if (role() == InstallRole) {
++ setStatus(DownloadingStatus);
+ } else {
+- finishTransaction();
++ setStatus(CommittingStatus);
+ }
++
++ reply->start();
+ }
+
+-void AlpineApkTransaction::finishTransaction()
++void AlpineApkTransaction::finishTransactionOK()
+ {
+ AbstractResource::State newState;
+ switch(role()) {
+@@ -83,8 +145,16 @@ void AlpineApkTransaction::finishTransaction()
+ newState = AbstractResource::None;
+ break;
+ }
+- m_app->setAddons(addons());
+- m_app->setState(newState);
++ m_resource->setAddons(addons());
++ m_resource->setState(newState);
+ setStatus(DoneStatus);
+ deleteLater();
+ }
++
++void AlpineApkTransaction::finishTransactionWithError(const QString &errMsg)
++{
++ qCWarning(LOG_ALPINEAPK) << "Transaction finished with error:" << errMsg;
++ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + errMsg);
++ setStatus(DoneWithErrorStatus);
++ deleteLater();
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+index ba0a7750..cab1f6b9 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
+@@ -30,19 +30,19 @@ class AlpineApkTransaction : public Transaction
+ {
+ Q_OBJECT
+ public:
+- AlpineApkTransaction(AlpineApkResource *app, Role role);
+- AlpineApkTransaction(AlpineApkResource *app, const AddonList &list, Role role);
++ AlpineApkTransaction(AlpineApkResource *res, Role role);
++ AlpineApkTransaction(AlpineApkResource *res, const AddonList &list, Role role);
+
+ void cancel() override;
+ void proceed() override;
+
+ private Q_SLOTS:
+- void iterateTransaction();
+- void finishTransaction();
++ void startTransaction();
++ void finishTransactionOK();
++ void finishTransactionWithError(const QString &errMsg);
+
+ private:
+- bool m_iterate = true;
+- AlpineApkResource *m_app;
++ AlpineApkResource *m_resource;
+ AlpineApkBackend *m_backend;
+ };
+
+--
+GitLab
+
+
+From b21745b7881dedb89219f7c77153d258c774d40d Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 30 Jul 2020 03:41:57 +0300
+Subject: [PATCH 44/62] AlpineApkBackend: build with AppStreamQt support
+
+provide list of AppStreamQt components, load them once at startup
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 88 +++++++++++++++++--
+ .../AlpineApkBackend/AlpineApkBackend.h | 4 +
+ .../backends/AlpineApkBackend/CMakeLists.txt | 1 +
+ 3 files changed, 88 insertions(+), 5 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index b695e5e6..ac91a6ef 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -32,11 +32,14 @@
+
+ #include <KLocalizedString>
+
++#include <AppStreamQt/pool.h>
++
++#include <QAction>
+ #include <QDebug>
+ #include <QLoggingCategory>
++#include <QSet>
+ #include <QThread>
+ #include <QTimer>
+-#include <QAction>
+
+ DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
+
+@@ -68,6 +71,36 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ m_updatesTimeoutTimer->setSingleShot(true);
+ m_updatesTimeoutTimer->setInterval(5 * 60 * 1000); // 5 minutes
+
++ qCDebug(LOG_ALPINEAPK) << "backend: loading AppStream metadata...";
++ AppStream::Pool *appStreamPool = new AppStream::Pool(this);
++ appStreamPool->setFlags(AppStream::Pool::FlagReadCollection |
++ AppStream::Pool::FlagReadMetainfo |
++ AppStream::Pool::FlagReadDesktopFiles);
++ appStreamPool->setCacheFlags(AppStream::Pool::CacheFlagUseUser |
++ AppStream::Pool::CacheFlagUseSystem);
++ if (!appStreamPool->load()) {
++ qCWarning(LOG_ALPINEAPK) << "backend: Failed to load appstream data:"
++ << appStreamPool->lastError();
++ } else {
++ m_appStreamComponents = appStreamPool->components();
++ qCDebug(LOG_ALPINEAPK) << "backend: loaded AppStream metadata OK:"
++ << m_appStreamComponents.size() << "components.";
++ // collect all categories present in appstream metadata
++// QSet<QString> collectedCategories;
++// for (const AppStream::Component &component : m_appStreamComponents) {
++// const QStringList cats = component.categories();
++// for (const QString &cat : cats) {
++// collectedCategories.insert(cat);
++// }
++// }
++// for (const QString &cat : collectedCategories) {
++// qCDebug(LOG_ALPINEAPK) << " collected category: " << cat;
++// m_collectedCategories << cat;
++// }
++ }
++ delete appStreamPool;
++ appStreamPool = nullptr;
++
+ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
+
+ if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
+@@ -78,13 +111,38 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ if (m_availablePackages.size() > 0) {
+ for (const QtApk::Package &pkg: m_availablePackages) {
+- AlpineApkResource *res = new AlpineApkResource(pkg, this);
++
++ // try to find appstream data for this package
++ AppStream::Component appstreamComponent;
++ AbstractResource::Type resType = AbstractResource::Type::Technical; // default
++ for (const auto& appsC : m_appStreamComponents) {
++ // find result which package name is exactly the one we want
++ if (appsC.packageNames().contains(pkg.name)) {
++ // found!
++ appstreamComponent = appsC;
++ // determine resource type here
++ switch (appsC.kind()) {
++ case AppStream::Component::KindDesktopApp:
++ case AppStream::Component::KindConsoleApp:
++ case AppStream::Component::KindWebApp:
++ resType = AbstractResource::Type::Application;
++ break;
++ case AppStream::Component::KindAddon:
++ resType = AbstractResource::Type::Addon;
++ break;
++ default:
++ resType = AbstractResource::Type::Technical;
++ break;
++ }
++ break; // exit for() loop
++ }
++ }
++
++ AlpineApkResource *res = new AlpineApkResource(pkg, appstreamComponent, resType, this);
+ res->setCategoryName(QStringLiteral("alpine_packages"));
+ res->setOriginSource(QStringLiteral("apk"));
+ res->setSection(QStringLiteral("dummy"));
+- // here is the place to set a proper type of package
+- // AlpineApkResource defaults to AbstractResource::Technical,
+- // which places it into "System updates" section
++
+ const QString key = pkg.name.toLower();
+ m_resources.insert(key, res);
+ connect(res, &AlpineApkResource::stateChanged,
+@@ -125,6 +183,26 @@ QVector<Category *> AlpineApkBackend::category() const
+ );
+
+ return { s_rootCat };
++
++// static QVector<Category *> s_cats;
++// if (s_cats.isEmpty()) {
++// // fill only once
++// s_cats << s_rootCat;
++// for (const QString &scat : m_collectedCategories) {
++// Category *cat = new Category(
++// scat, // name
++// QStringLiteral("package-x-generic"), // icon
++// {}, // orFilters
++// { displayName() }, // pluginName
++// {}, // subcategories
++// QUrl(), // decoration
++// false // isAddons
++// );
++// s_cats << cat;
++// }
++// }
++// return s_cats;
++ // ^^ causes deep hang in discover in recalculating QML bindings
+ }
+
+ int AlpineApkBackend::updatesCount() const
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 755cf6a5..efcaf2ea 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -26,6 +26,8 @@
+
+ #include <QtApk>
+
++#include <AppStreamQt/component.h>
++
+ class AlpineApkReviewsBackend;
+ class AlpineApkUpdater;
+ class AlpineApkResource;
+@@ -77,6 +79,8 @@ private:
+ bool m_fetching = false;
+ int m_fetchProgress = 0;
+ QTimer *m_updatesTimeoutTimer;
++ QList<AppStream::Component> m_appStreamComponents;
++ // QVector<QString> m_collectedCategories;
+ };
+
+ #endif // AlpineApkBackend_H
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 319c7ad2..53652538 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -39,6 +39,7 @@ target_link_libraries(
+ KF5::AuthCore
+ Discover::Common
+ ApkQt::ApkQt
++ AppStreamQt
+ )
+
+ # KAuth helper exe
+--
+GitLab
+
+
+From e30e7319103e7aabaea9bcc424b33cc1a0226111 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 30 Jul 2020 03:44:55 +0300
+Subject: [PATCH 45/62] AlpineApkResource: AppStream integration
+
+return some data from appstream metadata, if available
+---
+ .../AlpineApkBackend/AlpineApkResource.cpp | 136 +++++++++++++++++-
+ .../AlpineApkBackend/AlpineApkResource.h | 13 +-
+ 2 files changed, 141 insertions(+), 8 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index 346a28b2..a6f4bc0f 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -20,14 +20,29 @@
+
+ #include "AlpineApkResource.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
++
++#include <AppStreamQt/icon.h>
++#include <AppStreamQt/pool.h>
++#include <AppStreamQt/release.h>
++
++#include <QFileInfo>
++#include <QIcon>
++#include <QProcess>
++
++// libdiscover
++#include "appstream/AppStreamUtils.h"
++#include "config-paths.h"
+ #include "Transaction/AddonList.h"
+
+ AlpineApkResource::AlpineApkResource(const QtApk::Package &apkPkg,
++ AppStream::Component &component,
++ AbstractResource::Type typ,
+ AbstractResourcesBackend *parent)
+ : AbstractResource(parent)
+ , m_state(AbstractResource::State::None)
+- , m_type(AbstractResource::Type::Technical)
++ , m_type(typ)
+ , m_pkg(apkPkg)
++ , m_appsC(component)
+ {
+ }
+
+@@ -43,11 +58,17 @@ QString AlpineApkResource::availableVersion() const
+
+ QStringList AlpineApkResource::categories()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.categories();
++ }
+ return { m_category };
+ }
+
+ QString AlpineApkResource::comment()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.summary();
++ }
+ return m_pkg.description;
+ }
+
+@@ -58,26 +79,83 @@ int AlpineApkResource::size()
+
+ QUrl AlpineApkResource::homepage()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.url(AppStream::Component::UrlKindHomepage);
++ }
+ return QUrl::fromUserInput(m_pkg.url);
+ }
+
+ QUrl AlpineApkResource::helpURL()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.url(AppStream::Component::UrlKindHelp);
++ }
+ return QUrl();
+ }
+
+ QUrl AlpineApkResource::bugURL()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.url(AppStream::Component::UrlKindBugtracker);
++ }
+ return QUrl();
+ }
+
+ QUrl AlpineApkResource::donationURL()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.url(AppStream::Component::UrlKindDonation);
++ }
+ return QUrl();
+ }
+
++///xdg-compatible icon name to represent the resource, url or QIcon
+ QVariant AlpineApkResource::icon() const
+ {
++ if (hasAppStreamData()) {
++ const QList<AppStream::Icon> icns = m_appsC.icons();
++ if (icns.size() == 0) {
++ return QStringLiteral("package-x-generic");
++ }
++ QIcon ico;
++ const AppStream::Icon &appIco = icns.first();
++
++ switch (appIco.kind()) {
++ case AppStream::Icon::KindStock:
++ // we can create icons of this type directly from theme
++ ico = QIcon::fromTheme(appIco.name());
++ break;
++ case AppStream::Icon::KindLocal:
++ case AppStream::Icon::KindCached: {
++ // try from predefined standard Alpine path
++ const QString appstreamIconsPath = QLatin1String("/usr/share/app-info/icons/");
++ const QString path = appstreamIconsPath + appIco.url().path();
++ if (QFileInfo::exists(path)) {
++ ico.addFile(path, appIco.size());
++ } else {
++ const QString altPath = appstreamIconsPath +
++ QStringLiteral("%1x%2/").arg(appIco.size().width()).arg(appIco.size().height()) +
++ appIco.url().path();
++ if (QFileInfo::exists(altPath)) {
++ ico.addFile(altPath, appIco.size());
++ }
++ }
++ } break;
++ default: break;
++ }
++
++ // return icon only if we successfully loaded it
++ if (!ico.isNull()) {
++ return QVariant::fromValue<QIcon>(ico);
++ }
++
++ // try to load from icon theme by package name, this is better
++ // than nothing and works surprisingly well for many packages
++ ico = QIcon::fromTheme(m_pkg.name);
++ if (!ico.isNull()) {
++ return QVariant::fromValue<QIcon>(ico);
++ }
++ }
+ return QStringLiteral("package-x-generic");
+ }
+
+@@ -98,11 +176,17 @@ QJsonArray AlpineApkResource::licenses()
+
+ QString AlpineApkResource::longDescription()
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.description();
++ }
+ return m_pkg.description;
+ }
+
+ QString AlpineApkResource::name() const
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.name();
++ }
+ return m_pkg.name;
+ }
+
+@@ -128,13 +212,25 @@ AbstractResource::State AlpineApkResource::state()
+
+ void AlpineApkResource::fetchChangelog()
+ {
+- // QString log = longDescription();
+- // Q_EMIT changelogFetched(log);
++ if (hasAppStreamData()) {
++ emit changelogFetched(AppStreamUtils::changelogToHtml(m_appsC));
++ }
+ }
+
+ void AlpineApkResource::fetchScreenshots()
+ {
+- // Q_EMIT screenshotsFetched(m_screenshotThumbnails, m_screenshots);
++ if (hasAppStreamData()) {
++ const QPair<QList<QUrl>, QList<QUrl> > sc = AppStreamUtils::fetchScreenshots(m_appsC);
++ Q_EMIT screenshotsFetched(sc.first, sc.second);
++ }
++}
++
++QString AlpineApkResource::appstreamId() const
++{
++ if (hasAppStreamData()) {
++ return m_appsC.id();
++ }
++ return QString();
+ }
+
+ void AlpineApkResource::setState(AbstractResource::State state)
+@@ -184,20 +280,41 @@ void AlpineApkResource::setAvailableVersion(const QString &av)
+ m_availableVersion = av;
+ }
+
++bool AlpineApkResource::hasAppStreamData() const
++{
++ return !m_appsC.id().isEmpty();
++}
++
++bool AlpineApkResource::canExecute() const
++{
++ if (hasAppStreamData()) {
++ return (m_appsC.kind() == AppStream::Component::KindDesktopApp &&
++ (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable));
++ }
++ return false;
++}
+
+ void AlpineApkResource::invokeApplication() const
+ {
+- // QDesktopServices d;
+- // d.openUrl(QUrl(QStringLiteral("https://projects.kde.org/projects/extragear/sysadmin/muon")));
++ const QString desktopFile = QLatin1String("/usr/share/applications/") + appstreamId();
++ if (QFile::exists(desktopFile)) {
++ QProcess::startDetached(QStringLiteral("kstart5"), {QStringLiteral("--service"), desktopFile});
++ }
+ }
+
+ QUrl AlpineApkResource::url() const
+ {
++ if (hasAppStreamData()) {
++ return QUrl(QStringLiteral("appstream://") + appstreamId());
++ }
+ return QUrl(QLatin1String("apk://") + packageName());
+ }
+
+ QString AlpineApkResource::author() const
+ {
++ if (hasAppStreamData()) {
++ return m_appsC.developerName();
++ }
+ return m_pkg.maintainer;
+ }
+
+@@ -208,5 +325,12 @@ QString AlpineApkResource::sourceIcon() const
+
+ QDate AlpineApkResource::releaseDate() const
+ {
++ if (hasAppStreamData()) {
++ if (!m_appsC.releases().isEmpty()) {
++ auto release = m_appsC.releases().constFirst();
++ return release.timestamp().date();
++ }
++ }
++ // just build date is fine, too
+ return m_pkg.buildTime.date();
+ }
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+index 7140786c..e8948c46 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -23,6 +23,7 @@
+
+ #include <resources/AbstractResource.h>
+ #include <QtApkPackage.h>
++#include <AppStreamQt/component.h>
+
+ class AddonList;
+
+@@ -31,7 +32,10 @@ class AlpineApkResource : public AbstractResource
+ Q_OBJECT
+
+ public:
+- explicit AlpineApkResource(const QtApk::Package &apkPkg, AbstractResourcesBackend *parent);
++ explicit AlpineApkResource(const QtApk::Package &apkPkg,
++ AppStream::Component &component,
++ AbstractResource::Type typ,
++ AbstractResourcesBackend *parent);
+
+ QList<PackageState> addonsInformation() override;
+ QString section() override;
+@@ -52,10 +56,11 @@ public:
+ QString name() const override;
+ QString packageName() const override;
+ AbstractResource::Type type() const override { return m_type; }
+- bool canExecute() const override { return true; }
++ bool canExecute() const override;
+ void invokeApplication() const override;
+ void fetchChangelog() override;
+ void fetchScreenshots() override;
++ QString appstreamId() const override;
+ QUrl url() const override;
+ QString author() const override;
+ QString sourceIcon() const override;
+@@ -69,6 +74,9 @@ public:
+ void setAddonInstalled(const QString &addon, bool installed);
+ void setAvailableVersion(const QString &av);
+
++private:
++ bool hasAppStreamData() const;
++
+ public:
+ AbstractResource::State m_state;
+ const AbstractResource::Type m_type;
+@@ -78,6 +86,7 @@ public:
+ QString m_originSoruce;
+ QString m_sectionName;
+ QList<PackageState> m_addons;
++ AppStream::Component m_appsC;
+ };
+
+ #endif // ALPINEAPKRESOURCE_H
+--
+GitLab
+
+
+From 656f93bd845834c2115880a74b8420d7b5dfaac5 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 28 Sep 2020 08:38:25 +0300
+Subject: [PATCH 46/62] AlpineApkBackend: cmake: depend on Qt5::Concurrent
+
+---
+ libdiscover/backends/AlpineApkBackend/CMakeLists.txt | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 53652538..9e3bd82c 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -34,6 +34,7 @@ target_link_libraries(
+ PRIVATE
+ Qt5::Core
+ Qt5::Widgets
++ Qt5::Concurrent
+ KF5::CoreAddons
+ KF5::ConfigCore
+ KF5::AuthCore
+--
+GitLab
+
+
+From 722746208bd713f5fd5d9ea7f3bb0cd2546b712d Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 28 Sep 2020 09:51:12 +0300
+Subject: [PATCH 47/62] AlpineApkBackend: load packages data in backgroud
+
+Load packages data and appstream metadata in background thread to
+unblock UI and increase discover startup time.
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 114 +++++++++++++-----
+ .../AlpineApkBackend/AlpineApkBackend.h | 5 +
+ 2 files changed, 91 insertions(+), 28 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index ac91a6ef..e441c4b3 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -35,10 +35,14 @@
+ #include <AppStreamQt/pool.h>
+
+ #include <QAction>
++#include <QtConcurrentRun>
+ #include <QDebug>
++#include <QFuture>
++#include <QFutureWatcher>
+ #include <QLoggingCategory>
+ #include <QSet>
+ #include <QThread>
++#include <QThreadPool>
+ #include <QTimer>
+
+ DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
+@@ -53,9 +57,6 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ const_cast<QLoggingCategory &>(LOG_ALPINEAPK()).setEnabled(QtDebugMsg, false);
+ #endif
+
+- // schedule checking for updates
+- QTimer::singleShot(1000, this, &AlpineApkBackend::checkForUpdates);
+-
+ // connections with our updater
+ QObject::connect(m_updater, &AlpineApkUpdater::updatesCountChanged,
+ this, &AlpineApkBackend::updatesCountChanged);
+@@ -71,8 +72,24 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ m_updatesTimeoutTimer->setSingleShot(true);
+ m_updatesTimeoutTimer->setInterval(5 * 60 * 1000); // 5 minutes
+
++ // load packages data in a separate thread; it takes a noticeable amount of time
++ // and this way UI is not blocked here
++ m_fetching = true; // we are busy!
++ QFuture<void> loadResFuture = QtConcurrent::run(QThreadPool::globalInstance(), this,
++ &AlpineApkBackend::loadResources);
++
++ QObject::connect(&m_voidFutureWatcher, &QFutureWatcher<void>::finished,
++ this, &AlpineApkBackend::onLoadResourcesFinished);
++ m_voidFutureWatcher.setFuture(loadResFuture);
++
++ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
++}
++
++void AlpineApkBackend::loadResources()
++{
+ qCDebug(LOG_ALPINEAPK) << "backend: loading AppStream metadata...";
+- AppStream::Pool *appStreamPool = new AppStream::Pool(this);
++
++ AppStream::Pool *appStreamPool = new AppStream::Pool();
+ appStreamPool->setFlags(AppStream::Pool::FlagReadCollection |
+ AppStream::Pool::FlagReadMetainfo |
+ AppStream::Pool::FlagReadDesktopFiles);
+@@ -102,6 +119,7 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ appStreamPool = nullptr;
+
+ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
++ emit this->passiveMessage(i18n("Loading, please wait..."));
+
+ if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
+ m_availablePackages = m_apkdb.getAvailablePackages();
+@@ -114,43 +132,77 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+
+ // try to find appstream data for this package
+ AppStream::Component appstreamComponent;
+- AbstractResource::Type resType = AbstractResource::Type::Technical; // default
+ for (const auto& appsC : m_appStreamComponents) {
+ // find result which package name is exactly the one we want
+ if (appsC.packageNames().contains(pkg.name)) {
+- // found!
+- appstreamComponent = appsC;
+- // determine resource type here
+- switch (appsC.kind()) {
+- case AppStream::Component::KindDesktopApp:
+- case AppStream::Component::KindConsoleApp:
+- case AppStream::Component::KindWebApp:
+- resType = AbstractResource::Type::Application;
+- break;
+- case AppStream::Component::KindAddon:
+- resType = AbstractResource::Type::Addon;
+- break;
+- default:
+- resType = AbstractResource::Type::Technical;
+- break;
++ // workaround for kate (Kate Sessions is found first, but
++ // package name = "kate" too, bugged metadata?)
++ if (pkg.name == QStringLiteral("kate")) {
++ // qCDebug(LOG_ALPINEAPK) << appsC.packageNames() << appsC.id();
++ // ^^ ("kate") "org.kde.plasma.katesessions"
++ if (appsC.id() != QStringLiteral("org.kde.kate")) {
++ continue;
++ }
+ }
++ appstreamComponent = appsC;
+ break; // exit for() loop
+ }
+ }
+
++ const QString key = pkg.name.toLower();
++ m_resourcesAppstreamData.insert(key, appstreamComponent);
++ }
++ }
++
++ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
++ << "packages";
++ qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
++ << "packages";
++}
++
++static AbstractResource::Type toDiscoverResourceType(const AppStream::Component &component)
++{
++ AbstractResource::Type resType = AbstractResource::Type::Technical; // default
++ // determine resource type here
++ switch (component.kind()) {
++ case AppStream::Component::KindDesktopApp:
++ case AppStream::Component::KindConsoleApp:
++ case AppStream::Component::KindWebApp:
++ resType = AbstractResource::Type::Application;
++ break;
++ case AppStream::Component::KindAddon:
++ resType = AbstractResource::Type::Addon;
++ break;
++ default:
++ resType = AbstractResource::Type::Technical;
++ break;
++ }
++ return resType;
++}
++
++void AlpineApkBackend::onLoadResourcesFinished()
++{
++ qCDebug(LOG_ALPINEAPK) << "backend: appstream data loaded and sorted; fill in resources";
++
++ if (m_availablePackages.size() > 0) {
++ for (const QtApk::Package &pkg: m_availablePackages) {
++ const QString key = pkg.name.toLower();
++
++ AppStream::Component &appstreamComponent = m_resourcesAppstreamData[key];
++ const AbstractResource::Type resType = toDiscoverResourceType(appstreamComponent);
++
+ AlpineApkResource *res = new AlpineApkResource(pkg, appstreamComponent, resType, this);
+ res->setCategoryName(QStringLiteral("alpine_packages"));
+ res->setOriginSource(QStringLiteral("apk"));
+ res->setSection(QStringLiteral("dummy"));
+
+- const QString key = pkg.name.toLower();
+ m_resources.insert(key, res);
+- connect(res, &AlpineApkResource::stateChanged,
+- this, &AlpineApkBackend::updatesCountChanged);
++ QObject::connect(res, &AlpineApkResource::stateChanged,
++ this, &AlpineApkBackend::updatesCountChanged);
+ }
+- qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
+- << "packages";
+ }
++
++ // update "installed/not installed" state
+ if (m_installedPackages.size() > 0) {
+ for (const QtApk::Package &pkg: m_installedPackages) {
+ const QString key = pkg.name.toLower();
+@@ -158,11 +210,17 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ m_resources.value(key)->setState(AbstractResource::Installed);
+ }
+ }
+- qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
+- << "packages";
+ }
+
+- SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
++ qCDebug(LOG_ALPINEAPK) << "backend: resources loaded.";
++
++ m_fetching = false;
++ emit fetchingChanged();
++ // ^^ this causes the UI to update "Featured" page and show
++ // to user that we actually have loaded packages data
++
++ // schedule check for updates 1 sec after we've loaded all resources
++ QTimer::singleShot(1000, this, &AlpineApkBackend::checkForUpdates);
+ }
+
+ QVector<Category *> AlpineApkBackend::category() const
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index efcaf2ea..2ec8b00b 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -23,6 +23,7 @@
+
+ #include <resources/AbstractResourcesBackend.h>
+ #include <QVariantList>
++#include <QFutureWatcher>
+
+ #include <QtApk>
+
+@@ -65,12 +66,15 @@ public Q_SLOTS:
+
+ private Q_SLOTS:
+ void finishCheckForUpdates();
++ void loadResources();
++ void onLoadResourcesFinished();
+
+ public:
+ QtApk::Database *apkdb() { return &m_apkdb; }
+
+ private:
+ QHash<QString, AlpineApkResource *> m_resources;
++ QHash<QString, AppStream::Component> m_resourcesAppstreamData;
+ AlpineApkUpdater *m_updater;
+ AlpineApkReviewsBackend *m_reviews;
+ QtApk::Database m_apkdb;
+@@ -81,6 +85,7 @@ private:
+ QTimer *m_updatesTimeoutTimer;
+ QList<AppStream::Component> m_appStreamComponents;
+ // QVector<QString> m_collectedCategories;
++ QFutureWatcher<void> m_voidFutureWatcher;
+ };
+
+ #endif // AlpineApkBackend_H
+--
+GitLab
+
+
+From 9110e3e0a945f0a383d50cac77f720706ecf725a Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 28 Sep 2020 09:51:35 +0300
+Subject: [PATCH 48/62] AlpineApkUpdater: disable too spammy logging
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 2f3d184f..c03fd5c5 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -204,7 +204,7 @@ void AlpineApkUpdater::proceed()
+
+ int AlpineApkUpdater::updatesCount()
+ {
+- qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
++ // qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
+ return m_updatesCount;
+ }
+
+--
+GitLab
+
+
+From c8bedfdd53b6f8da72745efb9090f285a33ea8ca Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 13 Jan 2021 22:08:44 +0300
+Subject: [PATCH 49/62] AlpineApkUpdater: try harder to get error string from
+ helper
+
+---
+ .../backends/AlpineApkBackend/AlpineApkUpdater.cpp | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index c03fd5c5..677a784f 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -299,12 +299,22 @@ void AlpineApkUpdater::handleKAuthHelperError(
+ KAuth::ExecuteJob *reply,
+ const QVariantMap &replyData)
+ {
+- const QString message = replyData.value(QLatin1String("errorString"),
++ // error message should be received as part of JSON reply from helper
++ QString message = replyData.value(QLatin1String("errorString"),
+ reply->errorString()).toString();
+- qCDebug(LOG_ALPINEAPK) << "KAuth helper returned error:" << message << reply->error();
+ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
++ qCWarning(LOG_ALPINEAPK) << "updater: KAuth helper returned AuthorizationDeniedError";
+ Q_EMIT passiveMessage(i18n("Authorization denied"));
+ } else {
++ // if received error message is empty, try other ways to get error text for user
++ // there are multiple ways to get error messages in kauth/kjob
++ if (message.isEmpty()) {
++ message = reply->errorString();
++ if (message.isEmpty()) {
++ message = reply->errorText();
++ }
++ }
++ qCDebug(LOG_ALPINEAPK) << "updater: KAuth helper returned error:" << message << reply->error();
+ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
+ }
+ }
+--
+GitLab
+
+
+From 563f349cf9cab5642e675d67d16ae990b0f5ae3b Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 24 Jan 2021 05:20:42 +0300
+Subject: [PATCH 50/62] fixup AlpineApkTransaction.cpp
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index b5d659da..ffd44292 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -94,7 +94,7 @@ void AlpineApkTransaction::startTransaction()
+ #endif
+ authAction.addArgument(QLatin1String("pkgName"), m_resource->m_pkg.name);
+
+- // run upgrade with elevated privileges
++ // run action with elevated privileges
+ KAuth::ExecuteJob *reply = authAction.execute();
+
+ // get result of this job
+--
+GitLab
+
+
+From d3800d49e67a05ceb354c0193eaf797d9858bd39 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 24 Jan 2021 08:16:20 +0300
+Subject: [PATCH 51/62] AlpineApkBackend: rename root category name
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index e441c4b3..8ddcc8e9 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -231,7 +231,7 @@ QVector<Category *> AlpineApkBackend::category() const
+ // Display a single root category
+ // we could add more, but Alpine apk does not have this concept
+ static Category *s_rootCat = new Category(
+- i18nc("Root category name", "Alpine packages"),
++ i18nc("Root category name", "Alpine Linux packages"),
+ QStringLiteral("package-x-generic"), // icon
+ { s_apkFlt }, // orFilters - include packages that match filter
+ { displayName() }, // pluginName
+--
+GitLab
+
+
+From 9284ff898c9fcb32eef7dc8d406141e41d476cf1 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 24 Jan 2021 08:28:04 +0300
+Subject: [PATCH 52/62] KAuth helper: use single KAuth action for all
+ operations
+
+Use single entry point for all package management operations.
+Using single KAuth action allows user to enter his password only once
+for all privileged operations during Discover session.
+---
+ .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 91 ++++++++++---------
+ .../AlpineApkBackend/AlpineApkAuthHelper.h | 15 ++-
+ .../org.kde.discover.alpineapkbackend.actions | 30 +-----
+ 3 files changed, 59 insertions(+), 77 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+index e6e6cb7a..97affc01 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
+@@ -113,6 +113,7 @@ void AlpineApkAuthHelper::onTransactionError(const QString &msg)
+ qCWarning(LOG_AUTHHELPER).nospace() << "ERROR occured in transaction \""
+ << m_currentTransaction->desc()
+ << "\": " << msg;
++ // construct error message to use in helper reply
+ const QString errMsg = m_currentTransaction->desc() + QLatin1String(" failed: ") + msg;
+ m_actionReply.setErrorDescription(errMsg);
+ m_actionReply.setData({
+@@ -129,16 +130,48 @@ void AlpineApkAuthHelper::onTransactionFinished()
+ m_loop->quit();
+ }
+
+-ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
++// single entry point for all package management actions
++ActionReply AlpineApkAuthHelper::pkgmgmt(const QVariantMap &args)
+ {
+- // return error by default
+ m_actionReply = ActionReply::HelperErrorReply();
+-
+ HelperSupport::progressStep(0);
+
++ // actual package management action to perform is passed in "pkgAction" argument
++ if (!args.contains(QLatin1String("pkgAction"))) {
++ m_actionReply.setError(ActionReply::InvalidActionError);
++ m_actionReply.setErrorDescription(QLatin1String("Please pass \'pkgAction\' argument."));
++ HelperSupport::progressStep(100);
++ return m_actionReply;
++ }
++
++ const QString pkgAction = args.value(QLatin1String("pkgAction")).toString();
++
++ if (pkgAction == QStringLiteral("update")) {
++ update(args);
++ } else if (pkgAction == QStringLiteral("add")) {
++ add(args);
++ } else if (pkgAction == QStringLiteral("del")) {
++ del(args);
++ } else if (pkgAction == QStringLiteral("upgrade")) {
++ upgrade(args);
++ } else if (pkgAction == QStringLiteral("repoconfig")) {
++ repoconfig(args);
++ } else {
++ // error: unknown pkgAction
++ m_actionReply.setError(ActionReply::NoSuchActionError);
++ m_actionReply.setErrorDescription(QLatin1String("Please pass a valid \'pkgAction\' argument. "
++ "Action \"%1\" is not recognized.").arg(pkgAction));
++ }
++
++ HelperSupport::progressStep(100);
++ return m_actionReply;
++}
++
++void AlpineApkAuthHelper::update(const QVariantMap &args)
++{
+ if (!openDatabase(args)) {
+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return m_actionReply;
++ return;
+ }
+
+ m_trans_ok = true;
+@@ -155,27 +188,19 @@ ActionReply AlpineApkAuthHelper::update(const QVariantMap &args)
+ { QLatin1String("updatesCount"), updatesCount }
+ });
+ }
+-
+- HelperSupport::progressStep(100);
+- return m_actionReply;
+ }
+
+-ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
++void AlpineApkAuthHelper::add(const QVariantMap &args)
+ {
+- // return error by default
+- m_actionReply = ActionReply::HelperErrorReply();
+-
+- HelperSupport::progressStep(0);
+-
+ if (!openDatabase(args)) {
+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return m_actionReply;
++ return;
+ }
+
+ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
+ if (pkgName.isEmpty()) {
+ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for adding!"));
+- return m_actionReply;
++ return;
+ }
+
+ m_trans_ok = true;
+@@ -188,27 +213,19 @@ ActionReply AlpineApkAuthHelper::add(const QVariantMap &args)
+ if (m_trans_ok) {
+ m_actionReply = ActionReply::SuccessReply();
+ }
+-
+- HelperSupport::progressStep(100);
+- return m_actionReply;
+ }
+
+-ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
++void AlpineApkAuthHelper::del(const QVariantMap &args)
+ {
+- // return error by default
+- m_actionReply = ActionReply::HelperErrorReply();
+-
+- HelperSupport::progressStep(0);
+-
+ if (!openDatabase(args)) {
+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return m_actionReply;
++ return;
+ }
+
+ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
+ if (pkgName.isEmpty()) {
+ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for removing!"));
+- return m_actionReply;
++ return;
+ }
+
+ const bool delRdepends = args.value(QLatin1String("delRdepends"), false).toBool();
+@@ -228,20 +245,13 @@ ActionReply AlpineApkAuthHelper::del(const QVariantMap &args)
+ if (m_trans_ok) {
+ m_actionReply = ActionReply::SuccessReply();
+ }
+-
+- HelperSupport::progressStep(100);
+- return m_actionReply;
+ }
+
+-ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
++void AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ {
+- m_actionReply = ActionReply::HelperErrorReply();
+-
+- HelperSupport::progressStep(0);
+-
+ if (!openDatabase(args)) {
+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
+- return m_actionReply;
++ return;
+ }
+
+ bool onlySimulate = args.value(QLatin1String("onlySimulate"), false).toBool();
+@@ -272,16 +282,10 @@ ActionReply AlpineApkAuthHelper::upgrade(const QVariantMap &args)
+ replyData.insert(QLatin1String("onlySimulate"), onlySimulate);
+ m_actionReply.setData(replyData);
+ }
+-
+- HelperSupport::progressStep(100);
+- return m_actionReply;
+ }
+
+-ActionReply AlpineApkAuthHelper::repoconfig(const QVariantMap &args)
++void AlpineApkAuthHelper::repoconfig(const QVariantMap &args)
+ {
+- m_actionReply = ActionReply::HelperErrorReply();
+- HelperSupport::progressStep(10);
+-
+ if (args.contains(QLatin1String("repoList"))) {
+ const QVariant v = args.value(QLatin1String("repoList"));
+ const QVector<QtApk::Repository> repoVec = v.value<QVector<QtApk::Repository>>();
+@@ -293,9 +297,6 @@ ActionReply AlpineApkAuthHelper::repoconfig(const QVariantMap &args)
+ } else {
+ m_actionReply.setErrorDescription(QStringLiteral("repoList parameter is missing in request!"));
+ }
+-
+- HelperSupport::progressStep(100);
+- return m_actionReply;
+ }
+
+ KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+index 7fd69a8f..240e6ed3 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
+@@ -35,17 +35,22 @@ public:
+ ~AlpineApkAuthHelper() override;
+
+ public Q_SLOTS:
+- ActionReply update(const QVariantMap &args);
+- ActionReply add(const QVariantMap &args);
+- ActionReply del(const QVariantMap &args);
+- ActionReply upgrade(const QVariantMap &args);
+- ActionReply repoconfig(const QVariantMap &args);
++ // single entry point for all package management operations
++ ActionReply pkgmgmt(const QVariantMap &args);
+
+ protected:
++ // helpers
+ bool openDatabase(const QVariantMap &args, bool readwrite = true);
+ void closeDatabase();
+ void setupTransactionPostCreate(QtApk::Transaction *trans);
+
++ // individual pakckage management actions
++ void update(const QVariantMap &args);
++ void add(const QVariantMap &args);
++ void del(const QVariantMap &args);
++ void upgrade(const QVariantMap &args);
++ void repoconfig(const QVariantMap &args);
++
+ protected Q_SLOTS:
+ void reportProgress(float percent);
+ void onTransactionError(const QString &msg);
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index 5f7f7677..c9bb5f9f 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -1,29 +1,5 @@
+-[org.kde.discover.alpineapkbackend.update]
+-Name=Update repository index
+-Description=Updates available packages list from repositories
+-Policy=auth_admin
+-Persistence=session
+-
+-[org.kde.discover.alpineapkbackend.upgrade]
+-Name=Upgrade all upgradable packages
+-Description=Upgrade installed packages to latest versions
+-Policy=auth_admin
+-Persistence=session
+-
+-[org.kde.discover.alpineapkbackend.add]
+-Name=Install/upgrade package
+-Description=Installs/upgrades one package
+-Policy=auth_admin
+-Persistence=session
+-
+-[org.kde.discover.alpineapkbackend.del]
+-Name=Remove package
+-Description=Uninstall one package
+-Policy=auth_admin
+-Persistence=session
+-
+-[org.kde.discover.alpineapkbackend.repoconfig]
+-Name=Configure repositories
+-Description=Configure repositories URLs
++[org.kde.discover.alpineapkbackend.pkgmgmt]
++Name=Package management
++Description=Install or remove packages, upgrade system
+ Policy=auth_admin
+ Persistence=session
+--
+GitLab
+
+
+From 5d3beff075531cce8263f386eb067ab9943c4017 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Sun, 24 Jan 2021 08:32:59 +0300
+Subject: [PATCH 53/62] AlpineApk: refactor code to use single entry for all
+ KAuth stuff
+
+ * Add AlpineApkAuthActionFactory to contain all KAuth
+ action creation stuff in one place.
+ * ActionFactory: Use new single entry point for all KAuth
+ helper operations.
+ * Switch all places in code that used KAuth actions to
+ using ActionFactory:create*() methods.
+---
+ .../AlpineApkAuthActionFactory.cpp | 118 ++++++++++++++++++
+ .../AlpineApkAuthActionFactory.h | 41 ++++++
+ .../AlpineApkSourcesBackend.cpp | 28 +----
+ .../AlpineApkBackend/AlpineApkTransaction.cpp | 29 +----
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 45 +------
+ .../backends/AlpineApkBackend/CMakeLists.txt | 2 +
+ 6 files changed, 178 insertions(+), 85 deletions(-)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
+new file mode 100644
+index 00000000..972f8ec5
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
+@@ -0,0 +1,118 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include <KLocalizedString>
++#include <kauth_version.h>
++
++#include "AlpineApkAuthActionFactory.h"
++#include "alpineapk_backend_logging.h"
++
++namespace ActionFactory {
++
++static KAuth::Action createAlpineApkKAuthAction()
++{
++ KAuth::Action action(QStringLiteral("org.kde.discover.alpineapkbackend.pkgmgmt"));
++ action.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
++ if (!action.isValid()) {
++ qCWarning(LOG_ALPINEAPK) << "Created KAuth action is not valid!";
++ return action;
++ }
++
++ // set action description
++ // setDetails deprecated since KF 5.68, use setDetailsV2() with DetailsMap.
++#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
++ action.setDetails(i18n("Package management"));
++#else
++ static const KAuth::Action::DetailsMap details{
++ { KAuth::Action::AuthDetail::DetailMessage, i18n("Package management") }
++ };
++ action.setDetailsV2(details);
++#endif
++
++ // change default timeout to 1 minute, bcause default DBus timeout
++ // of 25 seconds is not enough
++ action.setTimeout(1 * 60 * 1000);
++
++ return action;
++}
++
++KAuth::ExecuteJob *createUpdateAction(const QString &fakeRoot)
++{
++ KAuth::Action action = createAlpineApkKAuthAction();
++ if (!action.isValid()) {
++ return nullptr;
++ }
++ // update-action specific details
++ action.setTimeout(2 * 60 * 1000); // 2 minutes
++ action.addArgument(QLatin1String("pkgAction"), QLatin1String("update"));
++ action.addArgument(QLatin1String("fakeRoot"), fakeRoot);
++ return action.execute();
++}
++
++KAuth::ExecuteJob *createUpgradeAction(bool onlySimulate)
++{
++ KAuth::Action action = createAlpineApkKAuthAction();
++ if (!action.isValid()) {
++ return nullptr;
++ }
++ action.setTimeout(3 * 60 * 60 * 1000); // 3 hours, system upgrade can take really long
++ action.addArgument(QLatin1String("pkgAction"), QLatin1String("upgrade"));
++ action.addArgument(QLatin1String("onlySimulate"), onlySimulate);
++ return action.execute();
++}
++
++KAuth::ExecuteJob *createAddAction(const QString &pkgName)
++{
++ KAuth::Action action = createAlpineApkKAuthAction();
++ if (!action.isValid()) {
++ return nullptr;
++ }
++ action.setTimeout(1 * 60 * 60 * 1000); // 1 hour, in case package is really big?
++ action.addArgument(QLatin1String("pkgAction"), QLatin1String("add"));
++ action.addArgument(QLatin1String("pkgName"), pkgName);
++ return action.execute();
++}
++
++KAuth::ExecuteJob *createDelAction(const QString &pkgName)
++{
++ KAuth::Action action = createAlpineApkKAuthAction();
++ if (!action.isValid()) {
++ return nullptr;
++ }
++ action.setTimeout(1 * 60 * 60 * 1000); // although deletion is almost instant
++ action.addArgument(QLatin1String("pkgAction"), QLatin1String("del"));
++ action.addArgument(QLatin1String("pkgName"), pkgName);
++ return action.execute();
++}
++
++KAuth::ExecuteJob *createRepoconfigAction(const QVariant &repoUrls)
++{
++ KAuth::Action action = createAlpineApkKAuthAction();
++ if (!action.isValid()) {
++ return nullptr;
++ }
++ // should be instant, writes few lines to /etc/apk/repositories
++ action.setTimeout(1 * 60 * 1000); // 1 minute
++ action.addArgument(QLatin1String("pkgAction"), QLatin1String("repoconfig"));
++ action.addArgument(QLatin1String("repoList"), repoUrls);
++ return action.execute();
++}
++
++} // namespace ActionFactory
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
+new file mode 100644
+index 00000000..ff5667f8
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
+@@ -0,0 +1,41 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef AlpineApkAuthActionFactory_H
++#define AlpineApkAuthActionFactory_H
++
++#include <QString>
++#include <QVariant>
++
++#include <KAuthAction>
++#include <KAuthActionReply>
++#include <KAuthExecuteJob>
++
++namespace ActionFactory {
++
++KAuth::ExecuteJob *createUpdateAction(const QString &fakeRoot);
++KAuth::ExecuteJob *createUpgradeAction(bool onlySimulate = false);
++KAuth::ExecuteJob *createAddAction(const QString &pkgName);
++KAuth::ExecuteJob *createDelAction(const QString &pkgName);
++KAuth::ExecuteJob *createRepoconfigAction(const QVariant &repoUrls);
++
++} // namespace ActionFactory
++
++#endif
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index 28f08ef8..a126483a 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -19,6 +19,7 @@
+ ***************************************************************************/
+
+ #include "AlpineApkSourcesBackend.h"
++#include "AlpineApkAuthActionFactory.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+
+ #include <QDebug>
+@@ -27,7 +28,6 @@
+
+ // KF5
+ #include <KAuthExecuteJob>
+-#include <kauth_version.h>
+ #include <KLocalizedString>
+
+ // libapk-qt
+@@ -100,28 +100,12 @@ void AlpineApkSourcesBackend::fillModelFromRepos()
+
+ void AlpineApkSourcesBackend::saveSources()
+ {
+- KAuth::Action repoConfigAction(QStringLiteral("org.kde.discover.alpineapkbackend.repoconfig"));
+- repoConfigAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+- if (!repoConfigAction.isValid()) {
+- qCWarning(LOG_ALPINEAPK) << "repoConfigAction is not valid!";
+- return;
+- }
+-
+- repoConfigAction.setTimeout(1 * 60 * 1000); // 1 min
+-#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+- upgradeAction.setDetails(i18n("Configure repositories URLs"));
+-#else
+- static const KAuth::Action::DetailsMap details{
+- { KAuth::Action::AuthDetail::DetailMessage, i18n("Configure repositories URLs") }
+- };
+- repoConfigAction.setDetailsV2(details);
+-#endif
+- // pass in new repositories list
+- repoConfigAction.addArgument(QLatin1String("repoList"),
+- QVariant::fromValue<QVector<QtApk::Repository>>(m_repos));
++ const QVariant repoUrls = QVariant::fromValue<QVector<QtApk::Repository>>(m_repos);
+
+ // run with elevated privileges
+- KAuth::ExecuteJob *reply = repoConfigAction.execute();
++ KAuth::ExecuteJob *reply = ActionFactory::createRepoconfigAction(repoUrls);
++ if (!reply) return;
++
+ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this] (KJob *job) {
+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
+ if (reply->error() != 0) {
+@@ -142,7 +126,7 @@ void AlpineApkSourcesBackend::saveSources()
+
+ void AlpineApkSourcesBackend::onItemChanged(QStandardItem *item)
+ {
+- // update internal storage vector and relaod model from it
++ // update internal storage vector and reload model from it
+ // otherwise checks state are not updated in UI
+ const Qt::CheckState cs = item->checkState();
+ const QModelIndex idx = m_sourcesModel->indexFromItem(item);
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+index ffd44292..26bf1bd0 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
+@@ -21,6 +21,7 @@
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkBackend.h"
+ #include "AlpineApkResource.h"
++#include "AlpineApkAuthActionFactory.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+
+ // Qt
+@@ -29,7 +30,6 @@
+
+ // KF5
+ #include <KAuthExecuteJob>
+-#include <kauth_version.h>
+ #include <KLocalizedString>
+
+ AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *res, Role role)
+@@ -61,42 +61,23 @@ void AlpineApkTransaction::cancel()
+
+ void AlpineApkTransaction::startTransaction()
+ {
+- KAuth::Action authAction;
+- QString actionDescription(i18n("Install package"));
++ KAuth::ExecuteJob *reply = nullptr;
+ switch(role()) {
+ case InstallRole:
+- authAction.setName(QStringLiteral("org.kde.discover.alpineapkbackend.add"));
++ reply = ActionFactory::createAddAction(m_resource->m_pkg.name);
+ break;
+ case RemoveRole:
+- authAction.setName(QStringLiteral("org.kde.discover.alpineapkbackend.del"));
+- actionDescription = i18n("Remove package");
++ reply = ActionFactory::createDelAction(m_resource->m_pkg.name);
+ break;
+ case ChangeAddonsRole:
+ qCWarning(LOG_ALPINEAPK) << "Addons are not supported by Alpine APK Backend!";
+- return;
+ break;
+ }
+- authAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+
+- if (!authAction.isValid()) {
+- qCWarning(LOG_ALPINEAPK) << "kauth addAction is not valid!";
++ if (!reply) {
+ return;
+ }
+
+- authAction.setTimeout(60 * 60 * 1000); // 60 min
+-#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+- addAction.setDetails(actionDescription);
+-#else
+- const KAuth::Action::DetailsMap details{
+- { KAuth::Action::AuthDetail::DetailMessage, actionDescription }
+- };
+- authAction.setDetailsV2(details);
+-#endif
+- authAction.addArgument(QLatin1String("pkgName"), m_resource->m_pkg.name);
+-
+- // run action with elevated privileges
+- KAuth::ExecuteJob *reply = authAction.execute();
+-
+ // get result of this job
+ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this](KJob *job) {
+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 677a784f..0bbbce1c 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -21,11 +21,11 @@
+ #include "AlpineApkUpdater.h"
+ #include "AlpineApkResource.h"
+ #include "AlpineApkBackend.h"
++#include "AlpineApkAuthActionFactory.h"
+ #include "alpineapk_backend_logging.h"
+ #include "utils.h"
+
+ #include <KAuthExecuteJob>
+-#include <kauth_version.h>
+ #include <KLocalizedString>
+
+ #include <QtApk>
+@@ -167,27 +167,10 @@ void AlpineApkUpdater::cancel()
+ void AlpineApkUpdater::start()
+ {
+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- KAuth::Action upgradeAction(QStringLiteral("org.kde.discover.alpineapkbackend.upgrade"));
+- upgradeAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+-
+- if (!upgradeAction.isValid()) {
+- qCWarning(LOG_ALPINEAPK) << "kauth upgradeAction is not valid!";
+- return;
+- }
+-
+- upgradeAction.setTimeout(30 * 60 * 1000); // 30 min
+-#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+- upgradeAction.setDetails(i18n("Upgrade currently installed packages"));
+-#else
+- static const KAuth::Action::DetailsMap details{
+- { KAuth::Action::AuthDetail::DetailMessage, i18n("Upgrade currently installed packages") }
+- };
+- upgradeAction.setDetailsV2(details);
+-#endif
+- // upgradeAction.addArgument(QLatin1String("onlySimulate"), true);
+
+ // run upgrade with elevated privileges
+- KAuth::ExecuteJob *reply = upgradeAction.execute();
++ KAuth::ExecuteJob *reply = ActionFactory::createUpgradeAction();
++ if (!reply) return;
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
+
+@@ -211,27 +194,11 @@ int AlpineApkUpdater::updatesCount()
+ void AlpineApkUpdater::startCheckForUpdates()
+ {
+ QtApk::Database *db = m_backend->apkdb();
+- KAuth::Action updateAction(QStringLiteral("org.kde.discover.alpineapkbackend.update"));
+- updateAction.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
+- if (!updateAction.isValid()) {
+- qCWarning(LOG_ALPINEAPK) << "kauth updateAction is not valid!";
+- return;
+- }
+- updateAction.setTimeout(60 * 1000); // 1 minute
+- // setDetails deprecated since KF 5.68, use setDetailsV2() with DetailsMap.
+-#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
+- updateAction.setDetails(i18n("Update repositories index"));
+-#else
+- static const KAuth::Action::DetailsMap details{
+- { KAuth::Action::AuthDetail::DetailMessage, i18n("Update repositories index") }
+- };
+- updateAction.setDetailsV2(details);
+-#endif
+- updateAction.addArgument(QLatin1String("fakeRoot"), db->fakeRoot());
+
+ // run updates check with elevated privileges to access
+ // system package manager files
+- KAuth::ExecuteJob *reply = updateAction.execute();
++ KAuth::ExecuteJob *reply = ActionFactory::createUpdateAction(db->fakeRoot());
++ if (!reply) return;
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpdateHelperReply);
+ // qOverload is needed because of conflict with getter named percent()
+@@ -279,8 +246,8 @@ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+ if (reply->error() == 0) {
+ QVariant pkgsV = replyData.value(QLatin1String("changes"));
+ bool onlySimulate = replyData.value(QLatin1String("onlySimulate"), false).toBool();
+- qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received, onlySimulate:" << onlySimulate;
+ if (onlySimulate) {
++ qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received, simulation mode";
+ QVector<QtApk::Package> pkgVector = pkgsV.value<QVector<QtApk::Package>>();
+ qCDebug(LOG_ALPINEAPK) << " num changes:" << pkgVector.size();
+ for (const QtApk::Package &pkg : pkgVector) {
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 9e3bd82c..7e54cfd9 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -1,6 +1,8 @@
+ find_package(KF5Auth CONFIG REQUIRED) # Probably should be moved to top CMakeLists
+
+ set(alpineapkbackend_SRCS
++ AlpineApkAuthActionFactory.h
++ AlpineApkAuthActionFactory.cpp
+ AlpineApkBackend.cpp
+ AlpineApkBackend.h
+ AlpineApkResource.cpp
+--
+GitLab
+
+
+From c3416800dbfa16dd35c24b5a8c77ea6ebe6e4b89 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 29 Jan 2021 12:00:56 +0300
+Subject: [PATCH 54/62] AlpineApkResource: add AppStream data setter
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp | 5 +++++
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.h | 1 +
+ 2 files changed, 6 insertions(+)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index a6f4bc0f..8f493a49 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -285,6 +285,11 @@ bool AlpineApkResource::hasAppStreamData() const
+ return !m_appsC.id().isEmpty();
+ }
+
++void AlpineApkResource::setAppStreamData(const AppStream::Component &component)
++{
++ m_appsC = component;
++}
++
+ bool AlpineApkResource::canExecute() const
+ {
+ if (hasAppStreamData()) {
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+index e8948c46..5304a877 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
+@@ -73,6 +73,7 @@ public:
+ void setAddons(const AddonList &addons);
+ void setAddonInstalled(const QString &addon, bool installed);
+ void setAvailableVersion(const QString &av);
++ void setAppStreamData(const AppStream::Component &component);
+
+ private:
+ bool hasAppStreamData() const;
+--
+GitLab
+
+
+From 85869f1ec60905ce436bd975cbf36c40c6072717 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 29 Jan 2021 12:11:51 +0300
+Subject: [PATCH 55/62] Add AppstreamDataDownloader class
+
+---
+ .../AppstreamDataDownloader.cpp | 303 ++++++++++++++++++
+ .../AppstreamDataDownloader.h | 139 ++++++++
+ .../backends/AlpineApkBackend/CMakeLists.txt | 2 +
+ 3 files changed, 444 insertions(+)
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
+ create mode 100644 libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
+new file mode 100644
+index 00000000..16587994
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
+@@ -0,0 +1,303 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#include <QDateTime>
++#include <QDir>
++#include <QEventLoop>
++#include <QFile>
++#include <QFileInfo>
++#include <QJsonArray>
++#include <QJsonDocument>
++#include <QJsonParseError>
++#include <QJsonObject>
++#include <QNetworkAccessManager>
++#include <QNetworkReply>
++#include <QNetworkRequest>
++#include <QStandardPaths>
++#include <QThreadPool>
++#include <QUrl>
++#include <QtConcurrentRun>
++
++#include "AppstreamDataDownloader.h"
++#include "alpineapk_backend_logging.h"
++
++namespace DiscoverVersion {
++// contains static QLatin1String version("5.20.5"); definition
++// autogenerated from top CMakeLists.txt
++#include "../../../DiscoverVersion.h"
++}
++
++#ifdef ALPINE_LINUX_BUILD
++/**
++ * @brief ApkAppstreamDataDownloader::getApkArch
++ * Reads current configured system apk architecture
++ * from "/etc/apk/arch" file.
++ * @return "x86_64" / "armhf" / "armv7" / "aarch64" and so on
++ */
++static QString getApkArch()
++{
++ static QString s_retArch;
++ if (!s_retArch.isEmpty()) {
++ return s_retArch;
++ }
++ QFile archFile(QStringLiteral("/etc/apk/arch"));
++ if (!archFile.open(QIODevice::ReadOnly)) {
++ // TODO: we could try to guess at compile time by checking presence of
++ // defines like __x86_64__ (check with: "gcc -march=native -dM -E - </dev/null")
++ // but that seems like outside of scope of this small function
++ return s_retArch;
++ }
++ s_retArch = QString::fromUtf8(archFile.readAll()).trimmed();
++ archFile.close();
++ return s_retArch;
++}
++
++static inline void replaceCARCH(QString &in, const QString &arch)
++{
++ // URLs in JSON look like this:
++ // https://appstream.alpinelinux.org/data/edge/main/Components-main-@CARCH@.xml.gz
++ // we need to replace @CARCH@ with a real arch value
++ in.replace(QLatin1String("@CARCH@"), arch);
++}
++#endif
++
++QString AppstreamDataDownloader::getAppStreamCacheDir()
++{
++ QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
++ // ^^ "~/.cache/discover"
++ cachePath += QStringLiteral("/appstream_data");
++ QDir cacheDir(cachePath);
++ if (!cacheDir.exists()) {
++ const bool ok = cacheDir.mkpath(QStringLiteral("."));
++ if (ok) {
++ qCDebug(LOG_ALPINEAPK) << "Created appstream data cache dir:" << cachePath;
++ } else {
++ qCWarning(LOG_ALPINEAPK) << "Failed to create appstream data cache dir:" << cachePath;
++ }
++ }
++ return cachePath;
++}
++
++AppstreamDataDownloader::AppstreamDataDownloader(QObject *parent)
++ : QObject(parent)
++{
++}
++
++void AppstreamDataDownloader::setCacheExpirePeriodSecs(qint64 secs)
++{
++ m_cacheExpireSeconds = secs;
++}
++
++void AppstreamDataDownloader::loadUrlsJson(const QString &jsonPath)
++{
++ const QString jsonBaseName = QFileInfo(jsonPath).baseName();
++ QFile jsonFile(jsonPath);
++ if (!jsonFile.open(QIODevice::ReadOnly)) {
++ qCWarning(LOG_ALPINEAPK) << "Failed to open JSON:" << jsonPath << "for reading!";
++ Q_EMIT downloadFinished();
++ return;
++ }
++ const QByteArray jsonBa = jsonFile.readAll();
++ jsonFile.close();
++
++ QJsonParseError jsonError;
++ const QJsonDocument jDoc = QJsonDocument::fromJson(jsonBa, &jsonError);
++ if (jDoc.isNull()) {
++ qCWarning(LOG_ALPINEAPK) << "Failed to parse JSON:" << jsonPath << "!";
++ qCWarning(LOG_ALPINEAPK) << jsonError.errorString();
++ Q_EMIT downloadFinished();
++ return;
++ }
++ // JSON structure:
++ // {
++ // "urls": [
++ // "https://...", "https://...", "https://..."
++ // ]
++ // }
++ const QJsonObject rootObj = jDoc.object();
++ const QJsonArray urls = rootObj.value(QLatin1String("urls")).toArray();
++ for (const QJsonValue &urlValue : urls) {
++ QString url = urlValue.toString();
++#ifdef ALPINE_LINUX_BUILD
++ replaceCARCH(url, getApkArch());
++#endif
++ m_urls.append(url);
++ // prefixes are used to avoid name clashes with similar URL paths
++ // from other JSON files. json file basename is used as prefix
++ m_urlPrefixes.insert(url, jsonBaseName);
++ }
++}
++
++QString AppstreamDataDownloader::getLocalFileSavePath(const QUrl &urlToDownload)
++{
++ // we are adding a prefix here to local file name to avoid possible
++ // file name clashes with files from other JSONs
++ const QString urlPrefix = m_urlPrefixes.value(urlToDownload.toString(), QString());
++ const QFileInfo urlInfo(urlToDownload.path());
++ const QString localCacheFile = AppstreamDataDownloader::getAppStreamCacheDir()
++ + QDir::separator()
++ + urlPrefix + QLatin1Char('_')
++ + urlInfo.fileName();
++ // aka "~/.cache/discover/appstream_data/urlPrefix_fileName.xml.gz"
++ return localCacheFile;
++}
++
++void AppstreamDataDownloader::start()
++{
++ m_urls.clear();
++ // load json files with appdata URLs configuration
++ const QString path = QStandardPaths::locate(
++ QStandardPaths::GenericDataLocation,
++ QLatin1String("libdiscover/external-appstream-urls"),
++ QStandardPaths::LocateDirectory);
++ if (path.isEmpty()) {
++ qCWarning(LOG_ALPINEAPK) << "external-appstream-urls directory does not exist.";
++ return;
++ }
++
++ QDir jsonsDir(path);
++ // search for all JSON files in that directory and load each one
++ QFileInfoList fileList = jsonsDir.entryInfoList({QStringLiteral("*.json")}, QDir::Files);
++ for (const QFileInfo &fi : fileList) {
++ qCDebug(LOG_ALPINEAPK) << " reading URLs JSON: " << fi.absoluteFilePath();
++ loadUrlsJson(fi.absoluteFilePath());
++ }
++
++ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: urls:" << m_urls;
++
++ // check if download is needed at all, maybe all files are already up to date?
++
++ getAppStreamCacheDir(); // can create a cache dir if not exists
++
++ const QDateTime dtNow = QDateTime::currentDateTime();
++ m_urlsToDownload.clear();
++ for (const QString &url : m_urls) {
++ const QUrl urlToDownload(url, QUrl::TolerantMode);
++ const QString localCacheFile = getLocalFileSavePath(urlToDownload);
++ const QFileInfo localFi(localCacheFile);
++ if (localFi.exists()) {
++ int modifiedSecsAgo = localFi.lastModified().secsTo(dtNow);
++ if (modifiedSecsAgo >= m_cacheExpireSeconds) {
++ m_urlsToDownload.append(url);
++ }
++ qCDebug(LOG_ALPINEAPK) << " appstream metadata file: " << localFi.fileName()
++ << " was last modified " << modifiedSecsAgo << " seconds ago";
++ } else {
++ // locally downloaded file does not even exist, we need to download it
++ m_urlsToDownload.append(url);
++ qCDebug(LOG_ALPINEAPK) << " appstream metadata file: " << localFi.fileName()
++ << " does not exist, queued for downloading";
++ }
++ }
++
++ if (m_urlsToDownload.size() > 0) {
++ // some files are outdated; download is needed
++ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: We will need to download "
++ << m_urlsToDownload.size() << " file(s)";
++
++ // start downloader in a background thread
++ QFuture<void> downloaderFuture = QtConcurrent::run(
++ QThreadPool::globalInstance(), this, &AppstreamDataDownloader::download);
++
++ // directly connect signal to signal
++ QObject::connect(&m_voidFutureWatcher, &QFutureWatcher<void>::finished,
++ this, &AppstreamDataDownloader::downloadFinished);
++ m_voidFutureWatcher.setFuture(downloaderFuture);
++ } else {
++ // no need to download anything
++ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: All appstream data files "
++ "are up to date, not downloading anything";
++ Q_EMIT downloadFinished();
++ return;
++ }
++}
++
++// this function runs in background thread
++void AppstreamDataDownloader::download()
++{
++ QNetworkAccessManager nam;
++ QList<QNetworkReply *> replies;
++ QEventLoop loop;
++
++ // start a HTTP GET request for each URL
++ const QStringList urls = m_urlsToDownload;
++ const QString discoverVersion(QStringLiteral("plasma-discover %1").arg(DiscoverVersion::version));
++ for (const QString &url : urls) {
++ const QUrl uurl(url, QUrl::TolerantMode);
++ QNetworkRequest req(uurl);
++ req.setHeader(QNetworkRequest::UserAgentHeader, discoverVersion);
++ replies.push_back(nam.get(req));
++ }
++
++ for (QNetworkReply *rep : replies) {
++ // lambda that stops the loop when all requests have finished
++ // intentionaly use contextless lambda, it is not called otherwise
++ QObject::connect(rep, &QNetworkReply::finished, [&loop, &replies, rep] () {
++ const int numReplies = replies.size();
++ int numFinished = 0;
++ for (QNetworkReply *arep : replies) {
++ if (arep->isFinished()) {
++ numFinished++;
++ }
++ }
++ if (numFinished >= numReplies) {
++ loop.quit();
++ }
++ qCDebug(LOG_ALPINEAPK).nospace()
++ << "appstream_downloader: " << rep->url()
++ << " request finished (" << numFinished << "/" << numReplies << ")";
++ });
++ }
++
++ // wait for all requests to finish
++ loop.exec();
++
++ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: all downloads have finished!";
++
++ int numErrors = 0;
++ for (QNetworkReply *rep : replies) {
++ const QString localCacheFile = getLocalFileSavePath(rep->url());
++
++ if (rep->error() == QNetworkReply::NoError) {
++ // read received reply contents and save it to file
++ const QByteArray data = rep->readAll();
++ QFile fout(localCacheFile);
++ if (fout.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
++ fout.write(data);
++ fout.close();
++ m_cacheWasUpdated = true;
++ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: saved: " << localCacheFile;
++ } else {
++ qCWarning(LOG_ALPINEAPK) << "appstream_downloader: failed to save:" << localCacheFile;
++ }
++ } else {
++ // download failed for some reason
++ QFileInfo urlinfo(rep->url().path());
++ qCWarning(LOG_ALPINEAPK) << "appstream_downloader: failed to download"
++ << urlinfo.fileName() << rep->errorString();
++ numErrors++;
++ }
++ }
++
++ // cleanup: delete all replies objects
++ for (QNetworkReply *arep : replies) {
++ arep->deleteLater();
++ }
++}
+diff --git a/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
+new file mode 100644
+index 00000000..05771982
+--- /dev/null
++++ b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
+@@ -0,0 +1,139 @@
++/***************************************************************************
++ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++
++#ifndef AlpineAppstreamDataDownloader_H
++#define AlpineAppstreamDataDownloader_H
++
++#include <QFutureWatcher>
++#include <QHash>
++#include <QList>
++#include <QObject>
++#include <QString>
++#include <QUrl>
++
++/**
++ * @brief The AppstreamDataDownloader class
++ *
++ * @details The job of this class is to download appstream data
++ * gzipped XMLs from the Web server hosted somewhere (for
++ * Alpine Linux - at https://appstream.alpinelinux.org).
++ *
++ * Some distros (for example, Alpine Linux) do not provide
++ * appstream data as installable package, instead they host it
++ * on the internet and you have to download and install them
++ * manually.
++ *
++ * Logic behind this decision is beyond my understanding, but we
++ * have what we have... Those files are not very large (from few
++ * kilobytes to couple of megabytes) but we still need them.
++ *
++ * URLs to download archives from are stored in JSON files in
++ * /usr/share/libdiscover/external-appstream-urls/ directory
++ * (QStandardPaths::GenericDataLocation/libdiscover/external-appstream-urls
++ * from C++/Qt code, ${DATA_INSTALL_DIR}/libdiscover/external-appstream-urls
++ * from cmake).
++ *
++ * JSON file format:
++ * ---------------------
++ * {
++ * "urls": [ "https://url1", "https://url2", ... ]
++ * }
++ * ---------------------
++ *
++ * This class can load any amount of those JSON files,
++ * fetch URLs from them and download all files pointed by
++ * those URLs to discover's cache directory:
++ * "~/.cache/discover/appstream_data" (aka QStandardPaths::CacheLocation).
++ * If files are already present in cache and not outdated,
++ * they are not downloaded again. Default cache expiration
++ * time is 2 days.
++ */
++class AppstreamDataDownloader: public QObject
++{
++ Q_OBJECT
++public:
++ explicit AppstreamDataDownloader(QObject *parent = nullptr);
++
++ /**
++ * @brief getAppstreamCacheDir
++ * @details Use return value of this function to add extra metadata
++ * directories to AppStream loader, in case of AppStreamQt:
++ * AppStream::Pool::addMetadataLocation().
++ * This method creates a cache dir if it does not exist.
++ * @return directory where downloaded files are stored.
++ */
++ static QString getAppStreamCacheDir();
++
++ /**
++ * @brief cacheWasUpdated
++ * @details Call this after receiving downloadFinished() signal to
++ * test if there actually was something new downloaded.
++ *
++ * @return true, if new files were actually downloaded, or
++ * false is files already present in cache are up to date.
++ */
++ bool cacheWasUpdated() const { return m_cacheWasUpdated; }
++
++ /**
++ * @brief getCacheExpirePeriodSecs
++ * @return cache expire timeout in seconds
++ */
++ qint64 getCacheExpirePeriodSecs() const { return m_cacheExpireSeconds; }
++
++ /**
++ * @brief setCacheExpirePeriodSecs
++ * @param secs - new cache expiration timeout, in seconds.
++ */
++ void setCacheExpirePeriodSecs(qint64 secs);
++
++public Q_SLOTS:
++ /**
++ * @brief start
++ * Start the background thread that does all the job.
++ * downloadFinished() signal will be emitted when everything is done.
++ * start() may finish immediately if all cached files are
++ * up to date and no downloads are needed.
++ */
++ void start();
++
++Q_SIGNALS:
++ /**
++ * @brief downloadFinished
++ * This signal is emitted when download job is finished.
++ * To check if there were actual downloads performed, call
++ * cacheWasUpdated().
++ */
++ void downloadFinished();
++
++private:
++ QString getLocalFileSavePath(const QUrl &urlTodownload);
++ void loadUrlsJson(const QString &path);
++ void download();
++
++protected:
++ qint64 m_cacheExpireSeconds = 2 * 24 * 3600; // 2 days
++ QStringList m_urls;
++ QStringList m_urlsToDownload;
++ QHash<QString, QString> m_urlPrefixes;
++ QFutureWatcher<void> m_voidFutureWatcher;
++ bool m_cacheWasUpdated = false;
++};
++
++#endif
+diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+index 7e54cfd9..8602dce7 100644
+--- a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
++++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
+@@ -15,6 +15,8 @@ set(alpineapkbackend_SRCS
+ AlpineApkUpdater.h
+ AlpineApkTransaction.cpp
+ AlpineApkTransaction.h
++ AppstreamDataDownloader.h
++ AppstreamDataDownloader.cpp
+ )
+
+ ecm_qt_declare_logging_category(
+--
+GitLab
+
+
+From 18d3bc729adc212c18364c86c5849fed83c29c16 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Fri, 29 Jan 2021 12:17:48 +0300
+Subject: [PATCH 56/62] AlpineApkBackend: use AppstreamDataDownloader
+
+Download AppStream data from external URLs after
+loading resources initially. Reload AppStream
+metadata after download has finished.
+
+Refactor resource loading process into several
+subroutines for easier understanding and code sharing.
+---
+ .../AlpineApkBackend/AlpineApkBackend.cpp | 166 +++++++++++++-----
+ .../AlpineApkBackend/AlpineApkBackend.h | 10 +-
+ 2 files changed, 131 insertions(+), 45 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 8ddcc8e9..4bfe165b 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -24,6 +24,7 @@
+ #include "AlpineApkTransaction.h"
+ #include "AlpineApkSourcesBackend.h"
+ #include "AlpineApkUpdater.h"
++#include "AppstreamDataDownloader.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+
+ #include "resources/SourcesModel.h"
+@@ -85,16 +86,19 @@ AlpineApkBackend::AlpineApkBackend(QObject *parent)
+ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
+ }
+
+-void AlpineApkBackend::loadResources()
++// this fills in m_appStreamComponents
++void AlpineApkBackend::loadAppStreamComponents()
+ {
+- qCDebug(LOG_ALPINEAPK) << "backend: loading AppStream metadata...";
+-
+ AppStream::Pool *appStreamPool = new AppStream::Pool();
+ appStreamPool->setFlags(AppStream::Pool::FlagReadCollection |
+ AppStream::Pool::FlagReadMetainfo |
+ AppStream::Pool::FlagReadDesktopFiles);
+ appStreamPool->setCacheFlags(AppStream::Pool::CacheFlagUseUser |
+ AppStream::Pool::CacheFlagUseSystem);
++
++ // hey hey! cool stuff
++ appStreamPool->addMetadataLocation(AppstreamDataDownloader::getAppStreamCacheDir());
++
+ if (!appStreamPool->load()) {
+ qCWarning(LOG_ALPINEAPK) << "backend: Failed to load appstream data:"
+ << appStreamPool->lastError();
+@@ -103,36 +107,32 @@ void AlpineApkBackend::loadResources()
+ qCDebug(LOG_ALPINEAPK) << "backend: loaded AppStream metadata OK:"
+ << m_appStreamComponents.size() << "components.";
+ // collect all categories present in appstream metadata
+-// QSet<QString> collectedCategories;
+-// for (const AppStream::Component &component : m_appStreamComponents) {
+-// const QStringList cats = component.categories();
+-// for (const QString &cat : cats) {
+-// collectedCategories.insert(cat);
+-// }
+-// }
+-// for (const QString &cat : collectedCategories) {
+-// qCDebug(LOG_ALPINEAPK) << " collected category: " << cat;
+-// m_collectedCategories << cat;
+-// }
++ // QSet<QString> collectedCategories;
++ // for (const AppStream::Component &component : m_appStreamComponents) {
++ // const QStringList cats = component.categories();
++ // for (const QString &cat : cats) {
++ // collectedCategories.insert(cat);
++ // }
++ // }
++ // for (const QString &cat : collectedCategories) {
++ // qCDebug(LOG_ALPINEAPK) << " collected category: " << cat;
++ // m_collectedCategories << cat;
++ // }
+ }
+ delete appStreamPool;
+- appStreamPool = nullptr;
+-
+- qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
+- emit this->passiveMessage(i18n("Loading, please wait..."));
+-
+- if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
+- m_availablePackages = m_apkdb.getAvailablePackages();
+- m_installedPackages = m_apkdb.getInstalledPackages();
+- m_apkdb.close();
+- }
++}
+
++// this uses m_appStreamComponents and m_availablePackages
++// to fill in m_resourcesAppstreamData
++void AlpineApkBackend::parseAppStreamMetadata()
++{
+ if (m_availablePackages.size() > 0) {
+- for (const QtApk::Package &pkg: m_availablePackages) {
++
++ for (const QtApk::Package &pkg: qAsConst(m_availablePackages)) {
+
+ // try to find appstream data for this package
+ AppStream::Component appstreamComponent;
+- for (const auto& appsC : m_appStreamComponents) {
++ for (const auto& appsC : qAsConst(m_appStreamComponents)) {
+ // find result which package name is exactly the one we want
+ if (appsC.packageNames().contains(pkg.name)) {
+ // workaround for kate (Kate Sessions is found first, but
+@@ -153,11 +153,6 @@ void AlpineApkBackend::loadResources()
+ m_resourcesAppstreamData.insert(key, appstreamComponent);
+ }
+ }
+-
+- qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
+- << "packages";
+- qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
+- << "packages";
+ }
+
+ static AbstractResource::Type toDiscoverResourceType(const AppStream::Component &component)
+@@ -180,28 +175,81 @@ static AbstractResource::Type toDiscoverResourceType(const AppStream::Component
+ return resType;
+ }
+
+-void AlpineApkBackend::onLoadResourcesFinished()
++void AlpineApkBackend::fillResourcesAndApplyAppStreamData()
+ {
+- qCDebug(LOG_ALPINEAPK) << "backend: appstream data loaded and sorted; fill in resources";
+-
++ // now the tricky part - we need to reapply appstream component metadata to each resource
+ if (m_availablePackages.size() > 0) {
+ for (const QtApk::Package &pkg: m_availablePackages) {
+ const QString key = pkg.name.toLower();
+
+- AppStream::Component &appstreamComponent = m_resourcesAppstreamData[key];
+- const AbstractResource::Type resType = toDiscoverResourceType(appstreamComponent);
++ AppStream::Component &appsComponent = m_resourcesAppstreamData[key];
++ const AbstractResource::Type resType = toDiscoverResourceType(appsComponent);
++
++ AlpineApkResource *res = m_resources.value(key, nullptr);
++ if (res == nullptr) {
++ // during first run of this function during initial load
++ // m_resources hash is empty, so we need to insert new items
++ res = new AlpineApkResource(pkg, appsComponent, resType, this);
++ res->setCategoryName(QStringLiteral("alpine_packages"));
++ res->setOriginSource(QStringLiteral("apk"));
++ res->setSection(QStringLiteral("dummy"));
++ m_resources.insert(key, res);
++ QObject::connect(res, &AlpineApkResource::stateChanged,
++ this, &AlpineApkBackend::updatesCountChanged);
++ } else {
++ // this is not an initial run, just update existing resource
++ res->setAppStreamData(appsComponent);
++ }
++ }
++ }
++}
+
+- AlpineApkResource *res = new AlpineApkResource(pkg, appstreamComponent, resType, this);
+- res->setCategoryName(QStringLiteral("alpine_packages"));
+- res->setOriginSource(QStringLiteral("apk"));
+- res->setSection(QStringLiteral("dummy"));
++void AlpineApkBackend::reloadAppStreamMetadata()
++{
++ // mark us as "Loading..."
++ m_fetching = true;
++ emit fetchingChanged();
+
+- m_resources.insert(key, res);
+- QObject::connect(res, &AlpineApkResource::stateChanged,
+- this, &AlpineApkBackend::updatesCountChanged);
+- }
++ loadAppStreamComponents();
++ parseAppStreamMetadata();
++ fillResourcesAndApplyAppStreamData();
++
++ // mark us as "done loading"
++ m_fetching = false;
++ emit fetchingChanged();
++}
++
++// this function is executed in the background thread
++void AlpineApkBackend::loadResources()
++{
++ Q_EMIT this->passiveMessage(i18n("Loading, please wait..."));
++
++ qCDebug(LOG_ALPINEAPK) << "backend: loading AppStream metadata...";
++
++ loadAppStreamComponents();
++
++ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
++
++ if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
++ m_availablePackages = m_apkdb.getAvailablePackages();
++ m_installedPackages = m_apkdb.getInstalledPackages();
++ m_apkdb.close();
+ }
+
++ parseAppStreamMetadata();
++
++ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
++ << "packages";
++ qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
++ << "packages";
++}
++
++void AlpineApkBackend::onLoadResourcesFinished()
++{
++ qCDebug(LOG_ALPINEAPK) << "backend: appstream data loaded and sorted; fill in resources";
++
++ fillResourcesAndApplyAppStreamData();
++
+ // update "installed/not installed" state
+ if (m_installedPackages.size() > 0) {
+ for (const QtApk::Package &pkg: m_installedPackages) {
+@@ -221,6 +269,36 @@ void AlpineApkBackend::onLoadResourcesFinished()
+
+ // schedule check for updates 1 sec after we've loaded all resources
+ QTimer::singleShot(1000, this, &AlpineApkBackend::checkForUpdates);
++
++ // AppStream appdata downloader can download updated metadata files
++ // in a background thread. When potential download is finished,
++ // appstream data will be reloaded.
++ m_appstreamDownloader = new AppstreamDataDownloader(nullptr);
++ QObject::connect(m_appstreamDownloader, &AppstreamDataDownloader::downloadFinished,
++ this, &AlpineApkBackend::onAppstreamDataDownloaded, Qt::QueuedConnection);
++ m_appstreamDownloader->start();
++}
++
++void AlpineApkBackend::onAppstreamDataDownloaded()
++{
++ if (m_appstreamDownloader) {
++ if (m_appstreamDownloader->cacheWasUpdated()) {
++ // it means we need to reload previously loaded appstream metadata
++ // m_fetching is true if loadResources() is still executing
++ // in a background thread
++ if (!m_fetching) {
++ qCDebug(LOG_ALPINEAPK) << "AppStream metadata was updated; re-applying it to all resources";
++ reloadAppStreamMetadata();
++ } else {
++ qCWarning(LOG_ALPINEAPK) << "AppStream metadata was updated, but cannot apply it: still fetching";
++ // it should not really happen, but if it happens,
++ // then downloaded metadata will be used on the next
++ // discover launch anyway.
++ }
++ }
++ delete m_appstreamDownloader;
++ m_appstreamDownloader = nullptr;
++ }
+ }
+
+ QVector<Category *> AlpineApkBackend::category() const
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+index 2ec8b00b..07b6b7be 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
+@@ -22,8 +22,9 @@
+ #define AlpineApkBackend_H
+
+ #include <resources/AbstractResourcesBackend.h>
+-#include <QVariantList>
++
+ #include <QFutureWatcher>
++#include <QVariantList>
+
+ #include <QtApk>
+
+@@ -32,6 +33,7 @@
+ class AlpineApkReviewsBackend;
+ class AlpineApkUpdater;
+ class AlpineApkResource;
++class AppstreamDataDownloader;
+ class KJob;
+ class QTimer;
+
+@@ -66,8 +68,13 @@ public Q_SLOTS:
+
+ private Q_SLOTS:
+ void finishCheckForUpdates();
++ void loadAppStreamComponents();
++ void parseAppStreamMetadata();
++ void reloadAppStreamMetadata();
++ void fillResourcesAndApplyAppStreamData();
+ void loadResources();
+ void onLoadResourcesFinished();
++ void onAppstreamDataDownloaded();
+
+ public:
+ QtApk::Database *apkdb() { return &m_apkdb; }
+@@ -86,6 +93,7 @@ private:
+ QList<AppStream::Component> m_appStreamComponents;
+ // QVector<QString> m_collectedCategories;
+ QFutureWatcher<void> m_voidFutureWatcher;
++ AppstreamDataDownloader *m_appstreamDownloader;
+ };
+
+ #endif // AlpineApkBackend_H
+--
+GitLab
+
+
+From 3dd8723919afdf3c19e1e75d4a8c72dae98dc082 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 1 Feb 2021 14:24:25 +0300
+Subject: [PATCH 57/62] AlpineApkUpdater: fix progress reporting for system
+ upgrade
+
+Connect to progress updates from helper and implement
+AlpineApkUpdater::progress() getter function.
+---
+ .../AlpineApkBackend/AlpineApkUpdater.cpp | 23 +++++++++++++++----
+ .../AlpineApkBackend/AlpineApkUpdater.h | 3 ++-
+ 2 files changed, 21 insertions(+), 5 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 0bbbce1c..30399369 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -91,8 +91,7 @@ bool AlpineApkUpdater::hasUpdates() const
+
+ qreal AlpineApkUpdater::progress() const
+ {
+- qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+- return 0.0;
++ return m_upgradeProgress;
+ }
+
+ void AlpineApkUpdater::removeResources(const QList<AbstractResource *> &apps)
+@@ -171,10 +170,15 @@ void AlpineApkUpdater::start()
+ // run upgrade with elevated privileges
+ KAuth::ExecuteJob *reply = ActionFactory::createUpgradeAction();
+ if (!reply) return;
++
+ QObject::connect(reply, &KAuth::ExecuteJob::result,
+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
++ // qOverload is needed because of conflict with getter named percent()
++ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
++ this, &AlpineApkUpdater::handleKAuthUpgradeHelperProgress);
+
+ m_progressing = true;
++ m_upgradeProgress = 0.0;
+ Q_EMIT progressingChanged(m_progressing);
+
+ reply->start();
+@@ -239,6 +243,17 @@ void AlpineApkUpdater::handleKAuthUpdateHelperProgress(KJob *job, unsigned long
+ Q_EMIT progressChanged(static_cast<qreal>(percent));
+ }
+
++void AlpineApkUpdater::handleKAuthUpgradeHelperProgress(KJob *job, unsigned long percent)
++{
++ Q_UNUSED(job)
++ qCDebug(LOG_ALPINEAPK) << " upgrade progress: " << percent;
++ qreal newProgress = static_cast<qreal>(percent);
++ if (newProgress != m_upgradeProgress) {
++ m_upgradeProgress = newProgress;
++ Q_EMIT progressChanged(m_upgradeProgress);
++ }
++}
++
+ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+ {
+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
+@@ -258,8 +273,8 @@ void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
+ handleKAuthHelperError(reply, replyData);
+ }
+
+- // we are not in the state "Fetching updates" now, update UI
+- Q_EMIT checkForUpdatesFinished();
++ m_progressing = false;
++ Q_EMIT progressingChanged(m_progressing);
+ }
+
+ void AlpineApkUpdater::handleKAuthHelperError(
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+index 0ee2fcb4..6ca3ce07 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
+@@ -159,6 +159,7 @@ public Q_SLOTS:
+ void handleKAuthUpdateHelperProgress(KJob *job, unsigned long percent);
+ // upgrade
+ void handleKAuthUpgradeHelperReply(KJob *job);
++ void handleKAuthUpgradeHelperProgress(KJob *job, unsigned long percent);
+
+ //void transactionRemoved(Transaction* t);
+ //void cleanup();
+@@ -186,7 +187,7 @@ private:
+ // QSet<AbstractResource*> m_upgradeable;
+ // QSet<AbstractResource*> m_pendingResources;
+ bool m_progressing = false;
+-// qreal m_progress;
++ qreal m_upgradeProgress = 0.0;
+ // QDateTime m_lastUpdate;
+ // QTimer m_timer;
+ // bool m_canCancel = false;
+--
+GitLab
+
+
+From 92f4a2bf194c8bee7002f880eafa2e08b0f32ba7 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Mon, 1 Feb 2021 14:26:29 +0300
+Subject: [PATCH 58/62] AlpineApkUpdater: remove useless debug prints and
+ comments
+
+---
+ .../backends/AlpineApkBackend/AlpineApkUpdater.cpp | 8 --------
+ 1 file changed, 8 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+index 30399369..14df959c 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
+@@ -40,8 +40,6 @@ AlpineApkUpdater::AlpineApkUpdater(AbstractResourcesBackend *parent)
+
+ void AlpineApkUpdater::prepare()
+ {
+- qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+-
+ QtApk::Database *db = m_backend->apkdb();
+
+ if (db->isOpen()) {
+@@ -85,7 +83,6 @@ void AlpineApkUpdater::prepare()
+
+ bool AlpineApkUpdater::hasUpdates() const
+ {
+- // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
+ return (m_updatesCount > 0);
+ }
+
+@@ -96,22 +93,18 @@ qreal AlpineApkUpdater::progress() const
+
+ void AlpineApkUpdater::removeResources(const QList<AbstractResource *> &apps)
+ {
+- // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+ const QSet<AbstractResource *> checkSet = kToSet(apps);
+ m_markedToUpdate -= checkSet;
+ }
+
+ void AlpineApkUpdater::addResources(const QList<AbstractResource *> &apps)
+ {
+- //Q_UNUSED(apps)
+- //qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+ const QSet<AbstractResource *> checkSet = kToSet(apps);
+ m_markedToUpdate += checkSet;
+ }
+
+ QList<AbstractResource *> AlpineApkUpdater::toUpdate() const
+ {
+- // qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
+ return m_allUpdateable.values();
+ }
+
+@@ -191,7 +184,6 @@ void AlpineApkUpdater::proceed()
+
+ int AlpineApkUpdater::updatesCount()
+ {
+- // qDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_updatesCount;
+ return m_updatesCount;
+ }
+
+--
+GitLab
+
+
+From b545c0833c9032abf671e3519216ed24e67b74b8 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Wed, 24 Feb 2021 14:51:27 +0300
+Subject: [PATCH 59/62] AlpineApkSourcesBackend: port away from QAction
+
+follow-up to 4bf3f59adb82e2ef38df98073ee39e6d20e56921
+---
+ .../AlpineApkBackend/AlpineApkSourcesBackend.cpp | 14 ++++++++------
+ .../AlpineApkBackend/AlpineApkSourcesBackend.h | 9 ++++++---
+ 2 files changed, 14 insertions(+), 9 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+index a126483a..3db9e607 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
+@@ -18,6 +18,8 @@
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
++#include "resources/DiscoverAction.h"
++
+ #include "AlpineApkSourcesBackend.h"
+ #include "AlpineApkAuthActionFactory.h"
+ #include "alpineapk_backend_logging.h" // generated by ECM
+@@ -36,16 +38,16 @@
+ AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *parent)
+ : AbstractSourcesBackend(parent)
+ , m_sourcesModel(new QStandardItemModel(this))
+- , m_refreshAction(new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")),
+- QStringLiteral("Reload"), this))
+- , m_saveAction(new QAction(QIcon::fromTheme(QStringLiteral("document-save")),
+- QStringLiteral("Save"), this))
++ , m_refreshAction(new DiscoverAction(QIcon::fromTheme(QStringLiteral("view-refresh")),
++ QStringLiteral("Reload"), this))
++ , m_saveAction(new DiscoverAction(QIcon::fromTheme(QStringLiteral("document-save")),
++ QStringLiteral("Save"), this))
+ // ^^ unfortunately QML side ignores icons for custom actions
+ {
+ loadSources();
+- QObject::connect(m_refreshAction, &QAction::triggered,
++ QObject::connect(m_refreshAction, &DiscoverAction::triggered,
+ this, &AlpineApkSourcesBackend::loadSources);
+- QObject::connect(m_saveAction, &QAction::triggered,
++ QObject::connect(m_saveAction, &DiscoverAction::triggered,
+ this, &AlpineApkSourcesBackend::saveSources);
+ // track enabling/disabling repo source
+ QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged,
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+index eacda22d..c655d3d1 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
+@@ -21,11 +21,14 @@
+ #ifndef ALPINEAPKSOURCESBACKEND_H
+ #define ALPINEAPKSOURCESBACKEND_H
+
+-#include <resources/AbstractSourcesBackend.h>
++#include "resources/AbstractSourcesBackend.h"
++
+ #include <QStandardItemModel>
+
+ #include <QtApkRepository.h>
+
++class DiscoverAction;
++
+ class AlpineApkSourcesBackend : public AbstractSourcesBackend
+ {
+ public:
+@@ -49,8 +52,8 @@ private:
+ void onItemChanged(QStandardItem* item);
+
+ QStandardItemModel *m_sourcesModel = nullptr;
+- QAction *m_refreshAction = nullptr;
+- QAction *m_saveAction = nullptr;
++ DiscoverAction *m_refreshAction = nullptr;
++ DiscoverAction *m_saveAction = nullptr;
+ QVector<QtApk::Repository> m_repos;
+ };
+
+--
+GitLab
+
+
+From 9becc1e6738c0ce5f562ab39128d56e65d3b43b9 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 25 Feb 2021 07:39:50 +0300
+Subject: [PATCH 60/62] Use shorter backend name: it's now used in Install
+ button
+
+See https://invent.kde.org/plasma/discover/-/commit/
+ce89d530de3414f25bb4b48a1933a52bf37be922
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+index 4bfe165b..f40fc355 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
+@@ -485,7 +485,7 @@ void AlpineApkBackend::finishCheckForUpdates()
+
+ QString AlpineApkBackend::displayName() const
+ {
+- return i18nc("Backend plugin display name", "Alpine APK backend");
++ return i18nc("Backend plugin display name", "Alpine APK");
+ }
+
+ bool AlpineApkBackend::hasApplications() const
+--
+GitLab
+
+
+From dc889612ab753018fa634c588220fd7cff4e2775 Mon Sep 17 00:00:00 2001
+From: Alexey Min <alexey.min@gmail.com>
+Date: Thu, 25 Feb 2021 07:40:41 +0300
+Subject: [PATCH 61/62] AlpineApkResource: use Alpine Linux logo for backend
+ logo
+
+---
+ libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+index 8f493a49..45934706 100644
+--- a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
++++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
+@@ -325,7 +325,7 @@ QString AlpineApkResource::author() const
+
+ QString AlpineApkResource::sourceIcon() const
+ {
+- return QStringLiteral("player-time");
++ return QStringLiteral("alpine-linux-logo-icon");
+ }
+
+ QDate AlpineApkResource::releaseDate() const
+--
+GitLab
+
+
+From 9af13ac3ed6ae302b51ab08eced5db959afaac7d Mon Sep 17 00:00:00 2001
+From: Alexey Minnekhanov <alexeymin@postmarketos.org>
+Date: Sat, 15 May 2021 05:52:54 +0300
+Subject: [PATCH 62/62] KAuth helper: don't ask for password
+
+Allow everyone to install/remove/upgrade packages without asking
+for password. Polkit-kde auth dialog is not mobile friendly.
+
+Fixes https://gitlab.com/postmarketOS/pmaports/-/issues/1036
+---
+ .../org.kde.discover.alpineapkbackend.actions | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+index c9bb5f9f..f9db78db 100644
+--- a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
++++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
+@@ -1,5 +1,5 @@
+ [org.kde.discover.alpineapkbackend.pkgmgmt]
+ Name=Package management
+-Description=Install or remove packages, upgrade system
+-Policy=auth_admin
++Description=Install/remove/upgrade packages
++Policy=yes
+ Persistence=session
+--
+GitLab
+
diff --git a/community/discover/0001-port-away-from-qaction.patch b/community/discover/0001-port-away-from-qaction.patch
deleted file mode 100644
index 4d01767004..0000000000
--- a/community/discover/0001-port-away-from-qaction.patch
+++ /dev/null
@@ -1,858 +0,0 @@
-From 4bf3f59adb82e2ef38df98073ee39e6d20e56921 Mon Sep 17 00:00:00 2001
-From: Aleix Pol <aleixpol@kde.org>
-Date: Fri, 5 Feb 2021 20:13:03 +0100
-Subject: [PATCH] Port away from QAction
-
-It's big part of the reason why we use QtWidgets and it seems to add
-some unnecessary overhead.
----
- discover/DiscoverDeclarativePlugin.cpp | 4 +-
- exporter/main.cpp | 4 +-
- libdiscover/ActionsModel.cpp | 80 -------------------
- libdiscover/ActionsModel.h | 46 -----------
- libdiscover/CMakeLists.txt | 4 +-
- .../backends/DummyBackend/DummyBackend.cpp | 1 -
- .../DummyBackend/DummySourcesBackend.cpp | 6 +-
- .../DummyBackend/DummySourcesBackend.h | 4 +-
- .../backends/DummyBackend/tests/DummyTest.cpp | 1 -
- .../DummyBackend/tests/UpdateDummyTest.cpp | 1 -
- .../FlatpakBackend/FlatpakBackend.cpp | 1 -
- .../FlatpakBackend/FlatpakSourcesBackend.cpp | 8 +-
- .../FlatpakBackend/FlatpakSourcesBackend.h | 3 +-
- .../FlatpakBackend/tests/FlatpakTest.cpp | 4 +-
- .../backends/FwupdBackend/FwupdBackend.h | 3 +-
- .../FwupdBackend/FwupdSourcesBackend.cpp | 2 -
- .../PackageKitBackend/PackageKitBackend.cpp | 1 -
- .../PackageKitSourcesBackend.cpp | 8 +-
- .../PackageKitBackend/PackageKitUpdater.cpp | 1 -
- .../backends/SnapBackend/CMakeLists.txt | 2 +-
- .../backends/SnapBackend/SnapBackend.cpp | 1 -
- .../resources/AbstractSourcesBackend.h | 3 +-
- libdiscover/resources/DiscoverAction.cpp | 75 +++++++++++++++++
- libdiscover/resources/DiscoverAction.h | 59 ++++++++++++++
- libdiscover/resources/ResourcesModel.cpp | 8 +-
- libdiscover/resources/ResourcesModel.h | 8 +-
- .../resources/ResourcesUpdatesModel.cpp | 2 +-
- libdiscover/resources/SourcesModel.cpp | 1 -
- update/main.cpp | 4 +-
- 29 files changed, 172 insertions(+), 173 deletions(-)
- delete mode 100644 libdiscover/ActionsModel.cpp
- delete mode 100644 libdiscover/ActionsModel.h
- create mode 100644 libdiscover/resources/DiscoverAction.cpp
- create mode 100644 libdiscover/resources/DiscoverAction.h
-
-diff --git a/discover/DiscoverDeclarativePlugin.cpp b/discover/DiscoverDeclarativePlugin.cpp
-index 0f754258..525a000e 100644
---- a/discover/DiscoverDeclarativePlugin.cpp
-+++ b/discover/DiscoverDeclarativePlugin.cpp
-@@ -11,6 +11,7 @@
- #include <Transaction/TransactionListener.h>
- #include <Transaction/TransactionModel.h>
- #include <Transaction/Transaction.h>
-+#include <resources/DiscoverAction.h>
- #include <resources/ResourcesUpdatesModel.h>
- #include <resources/AbstractResource.h>
- #include <resources/ResourcesModel.h>
-@@ -24,7 +25,6 @@
- #include <UpdateModel/UpdateModel.h>
- #include <ScreenshotsModel.h>
- #include <ApplicationAddonsModel.h>
--#include <ActionsModel.h>
- #include <qqml.h>
- #include <QQmlEngine>
- #include <QQmlContext>
-@@ -48,10 +48,10 @@ void DiscoverDeclarativePlugin::registerTypes(const char* /*uri*/)
- qmlRegisterType<ReviewsModel>("org.kde.discover", 2, 0, "ReviewsModel");
- qmlRegisterType<ApplicationAddonsModel>("org.kde.discover", 2, 0, "ApplicationAddonsModel");
- qmlRegisterType<ScreenshotsModel>("org.kde.discover", 2, 0, "ScreenshotsModel");
-- qmlRegisterType<ActionsModel>("org.kde.discover", 2, 0, "ActionsModel");
- qmlRegisterType<UpdateModel>("org.kde.discover", 2, 0, "UpdateModel");
- qmlRegisterType<ReadFile>("org.kde.discover", 2, 0, "ReadFile");
-
-+ qmlRegisterUncreatableType<DiscoverAction>("org.kde.discover", 2, 0, "DiscoverAction", QStringLiteral("Use QQC Action"));
- qmlRegisterUncreatableType<QAction>("org.kde.discover", 2, 0, "QAction", QStringLiteral("Use QQC Action"));
- qmlRegisterUncreatableType<AbstractResource>("org.kde.discover", 2, 0, "AbstractResource", QStringLiteral("should come from the ResourcesModel"));
- qmlRegisterUncreatableType<AbstractSourcesBackend>("org.kde.discover", 2, 0, "AbstractSourcesBackend", QStringLiteral("should come from the SourcesModel"));
-diff --git a/exporter/main.cpp b/exporter/main.cpp
-index 565d5b91..b4307956 100644
---- a/exporter/main.cpp
-+++ b/exporter/main.cpp
-@@ -4,7 +4,7 @@
- * SPDX-License-Identifier: LGPL-2.0-or-later
- */
-
--#include <QApplication>
-+#include <QGuiApplication>
- #include <QCommandLineParser>
- #include <KLocalizedString>
- #include <KAboutData>
-@@ -40,7 +40,7 @@ int main(int argc, char** argv)
- exp.setExportPath(QUrl::fromUserInput(parser.positionalArguments().at(0), QString(), QUrl::AssumeLocalFile));
- }
-
-- QObject::connect(&exp, &MuonExporter::exportDone, &app, &QApplication::quit);
-+ QObject::connect(&exp, &MuonExporter::exportDone, &app, &QCoreApplication::quit);
-
- return app.exec();
- }
-diff --git a/libdiscover/ActionsModel.cpp b/libdiscover/ActionsModel.cpp
-deleted file mode 100644
-index 832a311a..00000000
---- a/libdiscover/ActionsModel.cpp
-+++ /dev/null
-@@ -1,80 +0,0 @@
--/*
-- * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
-- *
-- * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-- */
--
--#include "ActionsModel.h"
--#include "resources/ResourcesModel.h"
--#include "utils.h"
--#include <QAction>
--#include "libdiscover_debug.h"
--
--ActionsModel::ActionsModel(QObject* parent)
-- : QAbstractListModel(parent)
-- , m_priority(-1)
--{
-- connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ActionsModel::reload);
--}
--
--QHash< int, QByteArray > ActionsModel::roleNames() const
--{
-- return { { Qt::UserRole, "action" }};
--}
--
--QVariant ActionsModel::data(const QModelIndex& index, int role) const
--{
-- if(!index.isValid() || role!=Qt::UserRole)
-- return QVariant();
-- return QVariant::fromValue<QObject*>(m_filteredActions[index.row()]);
--}
--
--int ActionsModel::rowCount(const QModelIndex& parent) const
--{
-- return parent.isValid() ? 0 : m_filteredActions.count();
--}
--
--void ActionsModel::setActions(const QVariant& actions)
--{
-- if (m_actions == actions) {
-- return;
-- }
-- m_actions = actions;
--
-- reload();
-- Q_EMIT actionsChanged(m_actions);
--}
--
--void ActionsModel::reload()
--{
-- QList<QAction*> actions = m_actions.value<QList<QAction*>>();
-- if (m_priority>=0) {
-- actions = kFilter<QList<QAction*>>(actions, [this](QAction* action){ return action->priority() == m_priority; });
-- }
-- actions = kFilter<QList<QAction*>>(actions, [](QAction* action){ return action->isVisible(); });
-- if (actions == m_filteredActions)
-- return;
--
--
-- beginResetModel();
-- m_filteredActions = actions;
-- endResetModel();
--
-- for(auto a : qAsConst(m_filteredActions)) {
-- connect(a, &QAction::changed, this, &ActionsModel::reload, Qt::UniqueConnection);
-- }
--}
--
--int ActionsModel::filterPriority() const
--{
-- return m_priority;
--}
--
--void ActionsModel::setFilterPriority(int p)
--{
-- if (m_priority != p) {
-- m_priority = p;
-- reload();
-- Q_EMIT filterPriorityChanged();
-- }
--}
-diff --git a/libdiscover/ActionsModel.h b/libdiscover/ActionsModel.h
-deleted file mode 100644
-index 3e5b449e..00000000
---- a/libdiscover/ActionsModel.h
-+++ /dev/null
-@@ -1,46 +0,0 @@
--/*
-- * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
-- *
-- * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-- */
--
--#ifndef ACTIONSMODEL_H
--#define ACTIONSMODEL_H
--
--#include <QAbstractListModel>
--#include <QQmlParserStatus>
--#include "discovercommon_export.h"
--
--class QAction;
--
--class DISCOVERCOMMON_EXPORT ActionsModel : public QAbstractListModel
--{
-- Q_OBJECT
-- Q_PROPERTY(QVariant actions READ actions WRITE setActions NOTIFY actionsChanged)
-- Q_PROPERTY(int filterPriority READ filterPriority WRITE setFilterPriority NOTIFY filterPriorityChanged)
-- public:
-- explicit ActionsModel(QObject* parent = nullptr);
--
-- QHash<int, QByteArray> roleNames() const override;
-- QVariant data(const QModelIndex& index, int role) const override;
-- int rowCount(const QModelIndex& parent = QModelIndex()) const override;
--
-- void setFilterPriority(int p);
-- int filterPriority() const;
--
-- void setActions(const QVariant& actions);
-- QVariant actions() const { return m_actions; }
--
-- Q_SIGNALS:
-- void actionsChanged(const QVariant& actions);
-- void filterPriorityChanged();
--
-- private:
-- void reload();
--
-- QVariant m_actions;
-- QList<QAction*> m_filteredActions;
-- int m_priority;
--};
--
--#endif
-diff --git a/libdiscover/CMakeLists.txt b/libdiscover/CMakeLists.txt
-index 1097ea84..b1c62366 100644
---- a/libdiscover/CMakeLists.txt
-+++ b/libdiscover/CMakeLists.txt
-@@ -20,6 +20,7 @@ set(discovercommon_SRCS
- Transaction/TransactionModel.cpp
- UpdateModel/UpdateItem.cpp
- UpdateModel/UpdateModel.cpp
-+ resources/DiscoverAction.cpp
- resources/ResourcesModel.cpp
- resources/ResourcesProxyModel.cpp
- resources/PackageState.cpp
-@@ -31,7 +32,6 @@ set(discovercommon_SRCS
- resources/AbstractBackendUpdater.cpp
- resources/AbstractSourcesBackend.cpp
- resources/StoredResultsStream.cpp
-- ActionsModel.cpp
- DiscoverBackendsFactory.cpp
- ScreenshotsModel.cpp
- ApplicationAddonsModel.cpp
-@@ -66,7 +66,7 @@ target_link_libraries(DiscoverCommon
- PUBLIC
- Qt5::Core
- Qt5::Qml
-- Qt5::Widgets
-+ Qt5::Gui
- KF5::I18n
- KF5::ItemModels
- PRIVATE
-diff --git a/libdiscover/backends/DummyBackend/DummyBackend.cpp b/libdiscover/backends/DummyBackend/DummyBackend.cpp
-index 0ac8c805..6bca925c 100644
---- a/libdiscover/backends/DummyBackend/DummyBackend.cpp
-+++ b/libdiscover/backends/DummyBackend/DummyBackend.cpp
-@@ -21,7 +21,6 @@
- #include <QDebug>
- #include <QThread>
- #include <QTimer>
--#include <QAction>
-
- DISCOVER_BACKEND_PLUGIN(DummyBackend)
-
-diff --git a/libdiscover/backends/DummyBackend/DummySourcesBackend.cpp b/libdiscover/backends/DummyBackend/DummySourcesBackend.cpp
-index e5f3109e..955ea815 100644
---- a/libdiscover/backends/DummyBackend/DummySourcesBackend.cpp
-+++ b/libdiscover/backends/DummyBackend/DummySourcesBackend.cpp
-@@ -6,17 +6,17 @@
-
- #include "DummySourcesBackend.h"
- #include <QDebug>
--#include <QAction>
-+#include "resources/DiscoverAction.h"
-
- DummySourcesBackend::DummySourcesBackend(AbstractResourcesBackend * parent)
- : AbstractSourcesBackend(parent)
- , m_sources(new QStandardItemModel(this))
-- , m_testAction(new QAction(QIcon::fromTheme(QStringLiteral("kalgebra")), QStringLiteral("DummyAction"), this))
-+ , m_testAction(new DiscoverAction(QIcon::fromTheme(QStringLiteral("kalgebra")), QStringLiteral("DummyAction"), this))
- {
- for (int i = 0; i<10; ++i)
- addSource(QStringLiteral("DummySource%1").arg(i));
-
-- connect(m_testAction, &QAction::triggered, [](){ qDebug() << "action triggered!"; });
-+ connect(m_testAction, &DiscoverAction::triggered, [](){ qDebug() << "action triggered!"; });
- connect(m_sources, &QStandardItemModel::itemChanged, this, [](QStandardItem* item) { qDebug() << "DummySource changed" << item << item->checkState(); });
- }
-
-diff --git a/libdiscover/backends/DummyBackend/DummySourcesBackend.h b/libdiscover/backends/DummyBackend/DummySourcesBackend.h
-index 3b7e874b..00280cea 100644
---- a/libdiscover/backends/DummyBackend/DummySourcesBackend.h
-+++ b/libdiscover/backends/DummyBackend/DummySourcesBackend.h
-@@ -10,6 +10,8 @@
- #include <resources/AbstractSourcesBackend.h>
- #include <QStandardItemModel>
-
-+class DiscoverAction;
-+
- class DummySourcesBackend : public AbstractSourcesBackend
- {
- public:
-@@ -29,7 +31,7 @@ private:
- QStandardItem* sourceForId(const QString& id) const;
-
- QStandardItemModel* m_sources;
-- QAction* m_testAction;
-+ DiscoverAction* m_testAction;
- };
-
- #endif // DUMMYSOURCESBACKEND_H
-diff --git a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp
-index 3f4ce17b..00c4d678 100644
---- a/libdiscover/backends/DummyBackend/tests/DummyTest.cpp
-+++ b/libdiscover/backends/DummyBackend/tests/DummyTest.cpp
-@@ -20,7 +20,6 @@
- #include <QTest>
-
- #include <QtTest>
--#include <QAction>
-
- QTEST_MAIN(DummyTest)
-
-diff --git a/libdiscover/backends/DummyBackend/tests/UpdateDummyTest.cpp b/libdiscover/backends/DummyBackend/tests/UpdateDummyTest.cpp
-index 9e370306..207c0915 100644
---- a/libdiscover/backends/DummyBackend/tests/UpdateDummyTest.cpp
-+++ b/libdiscover/backends/DummyBackend/tests/UpdateDummyTest.cpp
-@@ -18,7 +18,6 @@
-
- #include <QTest>
- #include <QtTest>
--#include <QAction>
-
- class UpdateDummyTest
- : public QObject
-diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp
-index c1e07efa..2ea37db9 100644
---- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp
-+++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp
-@@ -28,7 +28,6 @@
- #include <KConfigGroup>
- #include <KSharedConfig>
-
--#include <QAction>
- #include <QtConcurrentRun>
- #include <QDebug>
- #include <QDir>
-diff --git a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp
-index c66c74ae..c3ee3482 100644
---- a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp
-+++ b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.cpp
-@@ -15,11 +15,11 @@
- #include <QDebug>
- #include <QNetworkAccessManager>
- #include <QNetworkReply>
--#include <QAction>
-
- #include <glib.h>
- #include <QTemporaryFile>
- #include <QStandardPaths>
-+#include <resources/DiscoverAction.h>
- #include <resources/StoredResultsStream.h>
-
- class FlatpakSourceItem : public QStandardItem
-@@ -37,12 +37,12 @@ FlatpakSourcesBackend::FlatpakSourcesBackend(const QVector<FlatpakInstallation *
- : AbstractSourcesBackend(parent)
- , m_preferredInstallation(installations.constFirst())
- , m_sources(new QStandardItemModel(this))
-- , m_flathubAction(new QAction(i18n("Add Flathub"), this))
-+ , m_flathubAction(new DiscoverAction(i18n("Add Flathub"), this))
- , m_noSourcesItem(new QStandardItem(QStringLiteral("-")))
- {
- m_flathubAction->setObjectName(QStringLiteral("flathub"));
- m_flathubAction->setToolTip(i18n("Makes it possible to easily install the applications listed in https://flathub.org"));
-- connect(m_flathubAction, &QAction::triggered, this, [this](){
-+ connect(m_flathubAction, &DiscoverAction::triggered, this, [this](){
- addSource(QStringLiteral("https://flathub.org/repo/flathub.flatpakrepo"));
- });
- for (auto installation : installations) {
-@@ -302,7 +302,7 @@ void FlatpakSourcesBackend::addRemote(FlatpakRemote *remote, FlatpakInstallation
-
- const auto theActions = actions();
- for(const QVariant& act: theActions) {
-- QAction* action = qobject_cast<QAction*>(act.value<QObject*>());
-+ DiscoverAction* action = qobject_cast<DiscoverAction*>(act.value<QObject*>());
- if (action->objectName() == id) {
- action->setEnabled(false);
- action->setVisible(false);
-diff --git a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.h b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.h
-index b2a6c7c2..08cb9e37 100644
---- a/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.h
-+++ b/libdiscover/backends/FlatpakBackend/FlatpakSourcesBackend.h
-@@ -17,6 +17,7 @@ extern "C" {
- #include <flatpak.h>
- }
-
-+class DiscoverAction;
- class FlatpakResource;
- class FlatpakSourcesBackend : public AbstractSourcesBackend
- {
-@@ -52,7 +53,7 @@ private:
-
- FlatpakInstallation *m_preferredInstallation;
- QStandardItemModel* m_sources;
-- QAction* const m_flathubAction;
-+ DiscoverAction* const m_flathubAction;
- QStandardItem* m_noSourcesItem;
- QStack<std::function<void()>> m_proceedFunctions;
- };
-diff --git a/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp b/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp
-index 810b2af4..627bb602 100644
---- a/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp
-+++ b/libdiscover/backends/FlatpakBackend/tests/FlatpakTest.cpp
-@@ -8,6 +8,7 @@
- #include <resources/ResourcesProxyModel.h>
- #include <resources/AbstractBackendUpdater.h>
- #include <resources/SourcesModel.h>
-+#include <resources/DiscoverAction.h>
- #include <ApplicationAddonsModel.h>
- #include <ReviewsBackend/ReviewsModel.h>
- #include <Transaction/TransactionModel.h>
-@@ -15,7 +16,6 @@
-
- #include <QTest>
- #include <QtTest>
--#include <QAction>
-
- class FlatpakTest
- : public QObject
-@@ -64,7 +64,7 @@ private Q_SLOTS:
- QSignalSpy initializedSpy(m_appBackend, SIGNAL(initialized()));
- if (m->rowCount() == 1) {
- QSignalSpy spy(m, &SourcesModel::rowsInserted);
-- qobject_cast<QAction*>(bk->actions().constFirst().value<QObject*>())->trigger();
-+ qobject_cast<DiscoverAction*>(bk->actions().constFirst().value<QObject*>())->trigger();
- QVERIFY(spy.count() || spy.wait(20000));
- }
- QVERIFY(initializedSpy.count() || initializedSpy.wait(20000));
-diff --git a/libdiscover/backends/FwupdBackend/FwupdBackend.h b/libdiscover/backends/FwupdBackend/FwupdBackend.h
-index c886384e..b0b11ea7 100644
---- a/libdiscover/backends/FwupdBackend/FwupdBackend.h
-+++ b/libdiscover/backends/FwupdBackend/FwupdBackend.h
-@@ -14,7 +14,6 @@
- #include <QDir>
- #include <QDebug>
- #include <QTimer>
--#include <QAction>
- #include <QMimeDatabase>
- #include <QVariantList>
- #include <QSet>
-@@ -30,7 +29,7 @@ extern "C" {
- }
- #include <glib-2.0/glib-object.h>
-
--class QAction;
-+class DiscoverAction;
- class StandardBackendUpdater;
- class FwupdResource;
- class FwupdBackend : public AbstractResourcesBackend
-diff --git a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp
-index acf941eb..426781bf 100644
---- a/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp
-+++ b/libdiscover/backends/FwupdBackend/FwupdSourcesBackend.cpp
-@@ -7,11 +7,9 @@
-
- #include "FwupdSourcesBackend.h"
-
--#include <QAction>
- #include <QString>
- #include <KLocalizedString>
-
--
- class FwupdSourcesModel : public QStandardItemModel
- {
- Q_OBJECT
-diff --git a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp
-index af75f7e2..8b44619d 100644
---- a/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp
-+++ b/libdiscover/backends/PackageKitBackend/PackageKitBackend.cpp
-@@ -24,7 +24,6 @@
- #include <QDebug>
- #include <QStandardPaths>
- #include <QFile>
--#include <QAction>
- #include <QMimeDatabase>
- #include <QFileSystemWatcher>
- #include <QFutureWatcher>
-diff --git a/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp b/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp
-index 0130aba4..b6607fa0 100644
---- a/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp
-+++ b/libdiscover/backends/PackageKitBackend/PackageKitSourcesBackend.cpp
-@@ -9,10 +9,10 @@
- #include <KLocalizedString>
- #include <KDesktopFile>
- #include <PackageKit/Daemon>
--#include <QAction>
- #include <QProcess>
- #include <QDebug>
- #include <QRegularExpression>
-+#include <resources/DiscoverAction.h>
- #include <resources/SourcesModel.h>
- #include <resources/AbstractResourcesBackend.h>
- #include "PackageKitBackend.h"
-@@ -45,14 +45,14 @@ private:
- PackageKitSourcesBackend* m_backend;
- };
-
--static QAction* createActionForService(const QString &servicePath, QObject* parent)
-+static DiscoverAction* createActionForService(const QString &servicePath, QObject* parent)
- {
-- QAction* action = new QAction(parent);
-+ DiscoverAction* action = new DiscoverAction(parent);
- KDesktopFile parser(servicePath);
- action->setIcon(QIcon::fromTheme(parser.readIcon()));
- action->setText(parser.readName());
- action->setToolTip(parser.readComment());
-- QObject::connect(action, &QAction::triggered, action, [servicePath](){
-+ QObject::connect(action, &DiscoverAction::triggered, action, [servicePath](){
- bool b = QProcess::startDetached(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF5 "/discover/runservice"), {servicePath});
- if (!b)
- qWarning() << "Could not start" << servicePath;
-diff --git a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp
-index 661b6732..ea6bdb5f 100644
---- a/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp
-+++ b/libdiscover/backends/PackageKitBackend/PackageKitUpdater.cpp
-@@ -9,7 +9,6 @@
- #include <PackageKit/Daemon>
- #include <PackageKit/Offline>
- #include <QDebug>
--#include <QAction>
- #include <QSet>
-
- #include <KLocalizedString>
-diff --git a/libdiscover/backends/SnapBackend/CMakeLists.txt b/libdiscover/backends/SnapBackend/CMakeLists.txt
-index c75cd6aa..b2a3b1be 100644
---- a/libdiscover/backends/SnapBackend/CMakeLists.txt
-+++ b/libdiscover/backends/SnapBackend/CMakeLists.txt
-@@ -1,7 +1,7 @@
- add_subdirectory(libsnapclient)
-
- add_library(snap-backend MODULE SnapResource.cpp SnapBackend.cpp SnapTransaction.cpp snapui.qrc)
--target_link_libraries(snap-backend Qt5::Core Qt5::Concurrent KF5::CoreAddons KF5::ConfigCore Discover::Common Snapd::Core)
-+target_link_libraries(snap-backend Qt5::Gui Qt5::Core Qt5::Concurrent KF5::CoreAddons KF5::ConfigCore Discover::Common Snapd::Core)
-
- if ("${Snapd_VERSION}" VERSION_GREATER 1.40)
- target_compile_definitions(snap-backend PRIVATE -DSNAP_COMMON_IDS -DSNAP_CHANNELS)
-diff --git a/libdiscover/backends/SnapBackend/SnapBackend.cpp b/libdiscover/backends/SnapBackend/SnapBackend.cpp
-index 0f3c25cd..c14ac1cd 100644
---- a/libdiscover/backends/SnapBackend/SnapBackend.cpp
-+++ b/libdiscover/backends/SnapBackend/SnapBackend.cpp
-@@ -23,7 +23,6 @@
- #include <QDebug>
- #include <QThread>
- #include <QTimer>
--#include <QAction>
- #include <QStandardItemModel>
- #include <QtConcurrentMap>
- #include <QtConcurrentRun>
-diff --git a/libdiscover/resources/AbstractSourcesBackend.h b/libdiscover/resources/AbstractSourcesBackend.h
-index 12d3d926..34ccb15d 100644
---- a/libdiscover/resources/AbstractSourcesBackend.h
-+++ b/libdiscover/resources/AbstractSourcesBackend.h
-@@ -10,7 +10,6 @@
- #include <QObject>
- #include "discovercommon_export.h"
-
--class QAction;
- class QAbstractItemModel;
- class AbstractResourcesBackend;
-
-@@ -20,7 +19,7 @@ class DISCOVERCOMMON_EXPORT AbstractSourcesBackend : public QObject
- Q_PROPERTY(AbstractResourcesBackend* resourcesBackend READ resourcesBackend CONSTANT)
- Q_PROPERTY(QAbstractItemModel* sources READ sources CONSTANT)
- Q_PROPERTY(QString idDescription READ idDescription CONSTANT)
-- Q_PROPERTY(QVariantList actions READ actions CONSTANT) //TODO Make it a QVector<QAction*> again when we depend on newer than Qt 5.12
-+ Q_PROPERTY(QVariantList actions READ actions CONSTANT) //TODO Make it a QVector<DiscoverAction*> again when we depend on newer than Qt 5.12
- Q_PROPERTY(bool supportsAdding READ supportsAdding CONSTANT)
- Q_PROPERTY(bool canMoveSources READ canMoveSources CONSTANT)
- Q_PROPERTY(bool canFilterSources READ canFilterSources CONSTANT)
-diff --git a/libdiscover/resources/DiscoverAction.cpp b/libdiscover/resources/DiscoverAction.cpp
-new file mode 100644
-index 00000000..f4d41a2d
---- /dev/null
-+++ b/libdiscover/resources/DiscoverAction.cpp
-@@ -0,0 +1,75 @@
-+/*
-+ * SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
-+ *
-+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-+ */
-+
-+#include "DiscoverAction.h"
-+
-+DiscoverAction::DiscoverAction(QObject* parent)
-+ : QObject(parent)
-+{
-+}
-+
-+DiscoverAction::DiscoverAction(const QIcon icon, const QString &text, QObject* parent)
-+ : QObject(parent)
-+ , m_text(text)
-+ , m_icon(icon)
-+{
-+}
-+
-+DiscoverAction::DiscoverAction(const QString &text, QObject* parent)
-+ : QObject(parent)
-+ , m_text(text)
-+{
-+}
-+
-+void DiscoverAction::setEnabled(bool enabled)
-+{
-+ if (enabled == m_isEnabled)
-+ return;
-+
-+ m_isEnabled = enabled;
-+ Q_EMIT enabledChanged(enabled);
-+}
-+
-+void DiscoverAction::setVisible(bool visible)
-+{
-+ if (visible == m_isVisible)
-+ return;
-+
-+ m_isVisible = visible;
-+ Q_EMIT visibleChanged(visible);
-+}
-+
-+void DiscoverAction::setIcon(const QIcon& icon)
-+{
-+ if (icon.name() == m_icon.name() && !icon.name().isEmpty())
-+ return;
-+
-+ m_icon = icon;
-+ Q_EMIT iconChanged(icon);
-+}
-+
-+void DiscoverAction::setText(const QString& text)
-+{
-+ if (text != m_text)
-+ return;
-+
-+ m_text = text;
-+ Q_EMIT textChanged(text);
-+}
-+
-+void DiscoverAction::setToolTip(const QString& toolTip)
-+{
-+ if (toolTip != m_toolTip)
-+ return;
-+
-+ m_toolTip = toolTip;
-+ Q_EMIT toolTipChanged(toolTip);
-+}
-+
-+void DiscoverAction::trigger()
-+{
-+ Q_EMIT triggered();
-+}
-diff --git a/libdiscover/resources/DiscoverAction.h b/libdiscover/resources/DiscoverAction.h
-new file mode 100644
-index 00000000..c84099a0
---- /dev/null
-+++ b/libdiscover/resources/DiscoverAction.h
-@@ -0,0 +1,59 @@
-+/*
-+ * SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
-+ *
-+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-+ */
-+
-+#pragma once
-+
-+#include <QObject>
-+#include <QIcon>
-+#include "discovercommon_export.h"
-+
-+/**
-+ * An action class that doesn't need QtWidgets
-+ */
-+class DISCOVERCOMMON_EXPORT DiscoverAction : public QObject
-+{
-+ Q_OBJECT
-+ Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
-+ Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY textChanged)
-+ Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY iconChanged)
-+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
-+ Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged)
-+public:
-+ DiscoverAction(QObject* parent = nullptr);
-+ DiscoverAction(const QString &text, QObject* parent = nullptr);
-+ DiscoverAction(const QIcon icon, const QString &text, QObject* parent = nullptr);
-+
-+ void setText(const QString &text);
-+ void setToolTip(const QString &toolTip);
-+ void setIcon(const QIcon &icon);
-+ void setEnabled(bool enabled);
-+ void setVisible(bool enabled);
-+
-+ bool isVisible() const { return m_isVisible; }
-+ bool isEnabled() const { return m_isEnabled; }
-+ QString text() const { return m_text; }
-+ QString toolTip() const { return m_toolTip; }
-+ QIcon icon() const { return m_icon; }
-+
-+public Q_SLOTS:
-+ void trigger();
-+
-+Q_SIGNALS:
-+ void triggered();
-+
-+ void textChanged(const QString &text);
-+ void toolTipChanged(const QString &toolTip);
-+ void iconChanged(const QIcon &icon);
-+ void visibleChanged(bool isVisible);
-+ void enabledChanged(bool isEnabled);
-+
-+private:
-+ bool m_isVisible = true;
-+ bool m_isEnabled = true;
-+ QString m_text;
-+ QString m_toolTip;
-+ QIcon m_icon;
-+};
-diff --git a/libdiscover/resources/ResourcesModel.cpp b/libdiscover/resources/ResourcesModel.cpp
-index 2f897884..e7bfc321 100644
---- a/libdiscover/resources/ResourcesModel.cpp
-+++ b/libdiscover/resources/ResourcesModel.cpp
-@@ -19,8 +19,9 @@
- #include "libdiscover_debug.h"
- #include <functional>
- #include <QCoreApplication>
-+#include <QIcon>
- #include <QThread>
--#include <QAction>
-+#include <resources/DiscoverAction.h>
- #include <QMetaProperty>
- #include <KLocalizedString>
- #include <KSharedConfig>
-@@ -84,15 +85,14 @@ void ResourcesModel::init(bool load)
- QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection);
-
-
-- m_updateAction = new QAction(this);
-+ m_updateAction = new DiscoverAction(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);
- m_fetchingUpdatesProgress.reevaluate();
- });
-- connect(m_updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates);
-+ connect(m_updateAction, &DiscoverAction::triggered, this, &ResourcesModel::checkForUpdates);
-
- connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
- }
-diff --git a/libdiscover/resources/ResourcesModel.h b/libdiscover/resources/ResourcesModel.h
-index 9640d103..cb6b0181 100644
---- a/libdiscover/resources/ResourcesModel.h
-+++ b/libdiscover/resources/ResourcesModel.h
-@@ -14,7 +14,7 @@
- #include "discovercommon_export.h"
- #include "AbstractResourcesBackend.h"
-
--class QAction;
-+class DiscoverAction;
-
- class DISCOVERCOMMON_EXPORT AggregatedResultsStream : public ResultsStream
- {
-@@ -71,7 +71,7 @@ class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject
- Q_PROPERTY(bool isFetching READ isFetching NOTIFY fetchingChanged)
- Q_PROPERTY(QVariantList backends READ backendsVariant NOTIFY backendsChanged)
- Q_PROPERTY(AbstractResourcesBackend* currentApplicationBackend READ currentApplicationBackend WRITE setCurrentApplicationBackend NOTIFY currentApplicationBackendChanged)
-- Q_PROPERTY(QAction* updateAction READ updateAction CONSTANT)
-+ Q_PROPERTY(DiscoverAction* updateAction READ updateAction CONSTANT)
- Q_PROPERTY(int fetchingUpdatesProgress READ fetchingUpdatesProgress NOTIFY fetchingUpdatesProgressChanged)
- Q_PROPERTY(QString applicationSourceName READ applicationSourceName NOTIFY currentApplicationBackendChanged)
- public:
-@@ -100,7 +100,7 @@ class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject
- void setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool writeConfig = true);
- AbstractResourcesBackend* currentApplicationBackend() const;
-
-- QAction* updateAction() const { return m_updateAction; }
-+ DiscoverAction* updateAction() const { return m_updateAction; }
- int fetchingUpdatesProgress() const { return m_fetchingUpdatesProgress.m_value; }
-
- public Q_SLOTS:
-@@ -137,7 +137,7 @@ class DISCOVERCOMMON_EXPORT ResourcesModel : public QObject
- bool m_isFetching;
- QVector< AbstractResourcesBackend* > m_backends;
- int m_initializingBackends;
-- QAction* m_updateAction = nullptr;
-+ DiscoverAction* m_updateAction = nullptr;
- AbstractResourcesBackend* m_currentApplicationBackend;
- QTimer* m_allInitializedEmitter;
-
-diff --git a/libdiscover/resources/ResourcesUpdatesModel.cpp b/libdiscover/resources/ResourcesUpdatesModel.cpp
-index a7f936eb..b8df29ff 100644
---- a/libdiscover/resources/ResourcesUpdatesModel.cpp
-+++ b/libdiscover/resources/ResourcesUpdatesModel.cpp
-@@ -187,7 +187,7 @@ void ResourcesUpdatesModel::updateAll()
- setTransaction(m_transaction);
- TransactionModel::global()->addTransaction(m_transaction);
- Q_FOREACH (AbstractBackendUpdater* upd, updaters) {
-- QMetaObject::invokeMethod(upd, "start", Qt::QueuedConnection);
-+ QMetaObject::invokeMethod(upd, &AbstractBackendUpdater::start, Qt::QueuedConnection);
- }
-
- QMetaObject::invokeMethod(this, [this](){
-diff --git a/libdiscover/resources/SourcesModel.cpp b/libdiscover/resources/SourcesModel.cpp
-index 8d1f985a..93e43784 100644
---- a/libdiscover/resources/SourcesModel.cpp
-+++ b/libdiscover/resources/SourcesModel.cpp
-@@ -7,7 +7,6 @@
- #include "SourcesModel.h"
- #include <QtGlobal>
- #include "libdiscover_debug.h"
--#include <QAction>
- #include "resources/AbstractResourcesBackend.h"
- #include "resources/AbstractSourcesBackend.h"
-
-diff --git a/update/main.cpp b/update/main.cpp
-index c40ad326..a3c12c64 100644
---- a/update/main.cpp
-+++ b/update/main.cpp
-@@ -4,7 +4,7 @@
- * SPDX-License-Identifier: LGPL-2.0-or-later
- */
-
--#include <QApplication>
-+#include <QGuiApplication>
- #include <QCommandLineParser>
- #include <KLocalizedString>
- #include <KAboutData>
-@@ -14,7 +14,7 @@
-
- int main(int argc, char** argv)
- {
-- QApplication app(argc, argv);
-+ QGuiApplication app(argc, argv);
- app.setQuitOnLastWindowClosed(false);
- KLocalizedString::setApplicationDomain("plasma-discover-update");
- KAboutData about(QStringLiteral("discoverupdate"), i18n("Discover Update"), version, {},
-
diff --git a/community/discover/0002-Add-support-for-Alpine-Linux-apk-backend.patch b/community/discover/0002-Add-support-for-Alpine-Linux-apk-backend.patch
deleted file mode 100644
index 1b28a0eeee..0000000000
--- a/community/discover/0002-Add-support-for-Alpine-Linux-apk-backend.patch
+++ /dev/null
@@ -1,3330 +0,0 @@
-From 6fbb948082bad66a821c8332e2fb9cb7965065f8 Mon Sep 17 00:00:00 2001
-From: Alexey Minnekhanov <alexeymin@postmarketos.org>
-Date: Sun, 12 Jan 2020 01:02:39 +0300
-Subject: [PATCH] Add support for Alpine Linux apk backend
-
-Alpine Package Keeper (apk) is package manager
-for Alpine Linux.
----
- discover/FeaturedModel.cpp | 6 +-
- .../AlpineApkAuthActionFactory.cpp | 118 ++++
- .../AlpineApkAuthActionFactory.h | 41 ++
- .../AlpineApkBackend/AlpineApkAuthHelper.cpp | 302 +++++++++++
- .../AlpineApkBackend/AlpineApkAuthHelper.h | 66 +++
- .../AlpineApkBackend/AlpineApkBackend.cpp | 503 ++++++++++++++++++
- .../AlpineApkBackend/AlpineApkBackend.h | 99 ++++
- .../AlpineApkBackend/AlpineApkResource.cpp | 341 ++++++++++++
- .../AlpineApkBackend/AlpineApkResource.h | 93 ++++
- .../AlpineApkReviewsBackend.cpp | 35 ++
- .../AlpineApkReviewsBackend.h | 52 ++
- .../AlpineApkSourcesBackend.cpp | 195 +++++++
- .../AlpineApkSourcesBackend.h | 57 ++
- .../AlpineApkBackend/AlpineApkTransaction.cpp | 141 +++++
- .../AlpineApkBackend/AlpineApkTransaction.h | 49 ++
- .../AlpineApkBackend/AlpineApkUpdater.cpp | 295 ++++++++++
- .../AlpineApkBackend/AlpineApkUpdater.h | 197 +++++++
- .../AppstreamDataDownloader.cpp | 303 +++++++++++
- .../AppstreamDataDownloader.h | 139 +++++
- .../backends/AlpineApkBackend/CMakeLists.txt | 85 +++
- .../org.kde.discover.alpineapkbackend.actions | 5 +
- libdiscover/backends/CMakeLists.txt | 10 +
- 22 files changed, 3129 insertions(+), 3 deletions(-)
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
- create mode 100644 libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
- create mode 100644 libdiscover/backends/AlpineApkBackend/CMakeLists.txt
- create mode 100644 libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
-
-diff --git a/discover/FeaturedModel.cpp b/discover/FeaturedModel.cpp
-index 1393e206..038f22c1 100644
---- a/discover/FeaturedModel.cpp
-+++ b/discover/FeaturedModel.cpp
-@@ -98,13 +98,13 @@ void FeaturedModel::refresh()
- setUris(uris);
- }
-
--void FeaturedModel::setUris(const QVector<QUrl>& uris)
-+void FeaturedModel::setUris(const QVector<QUrl> &uris)
- {
- if (!m_backend)
- return;
-
-- QSet<ResultsStream*> streams;
-- foreach(const auto &uri, uris) {
-+ QSet<ResultsStream *> streams;
-+ for (const QUrl &uri: uris) {
- AbstractResourcesBackend::Filters filter;
- filter.resourceUrl = uri;
- streams << m_backend->search(filter);
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
-new file mode 100644
-index 00000000..972f8ec5
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.cpp
-@@ -0,0 +1,118 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include <KLocalizedString>
-+#include <kauth_version.h>
-+
-+#include "AlpineApkAuthActionFactory.h"
-+#include "alpineapk_backend_logging.h"
-+
-+namespace ActionFactory {
-+
-+static KAuth::Action createAlpineApkKAuthAction()
-+{
-+ KAuth::Action action(QStringLiteral("org.kde.discover.alpineapkbackend.pkgmgmt"));
-+ action.setHelperId(QStringLiteral("org.kde.discover.alpineapkbackend"));
-+ if (!action.isValid()) {
-+ qCWarning(LOG_ALPINEAPK) << "Created KAuth action is not valid!";
-+ return action;
-+ }
-+
-+ // set action description
-+ // setDetails deprecated since KF 5.68, use setDetailsV2() with DetailsMap.
-+#if KAUTH_VERSION < QT_VERSION_CHECK(5, 68, 0)
-+ action.setDetails(i18n("Package management"));
-+#else
-+ static const KAuth::Action::DetailsMap details{
-+ { KAuth::Action::AuthDetail::DetailMessage, i18n("Package management") }
-+ };
-+ action.setDetailsV2(details);
-+#endif
-+
-+ // change default timeout to 1 minute, bcause default DBus timeout
-+ // of 25 seconds is not enough
-+ action.setTimeout(1 * 60 * 1000);
-+
-+ return action;
-+}
-+
-+KAuth::ExecuteJob *createUpdateAction(const QString &fakeRoot)
-+{
-+ KAuth::Action action = createAlpineApkKAuthAction();
-+ if (!action.isValid()) {
-+ return nullptr;
-+ }
-+ // update-action specific details
-+ action.setTimeout(2 * 60 * 1000); // 2 minutes
-+ action.addArgument(QLatin1String("pkgAction"), QLatin1String("update"));
-+ action.addArgument(QLatin1String("fakeRoot"), fakeRoot);
-+ return action.execute();
-+}
-+
-+KAuth::ExecuteJob *createUpgradeAction(bool onlySimulate)
-+{
-+ KAuth::Action action = createAlpineApkKAuthAction();
-+ if (!action.isValid()) {
-+ return nullptr;
-+ }
-+ action.setTimeout(3 * 60 * 60 * 1000); // 3 hours, system upgrade can take really long
-+ action.addArgument(QLatin1String("pkgAction"), QLatin1String("upgrade"));
-+ action.addArgument(QLatin1String("onlySimulate"), onlySimulate);
-+ return action.execute();
-+}
-+
-+KAuth::ExecuteJob *createAddAction(const QString &pkgName)
-+{
-+ KAuth::Action action = createAlpineApkKAuthAction();
-+ if (!action.isValid()) {
-+ return nullptr;
-+ }
-+ action.setTimeout(1 * 60 * 60 * 1000); // 1 hour, in case package is really big?
-+ action.addArgument(QLatin1String("pkgAction"), QLatin1String("add"));
-+ action.addArgument(QLatin1String("pkgName"), pkgName);
-+ return action.execute();
-+}
-+
-+KAuth::ExecuteJob *createDelAction(const QString &pkgName)
-+{
-+ KAuth::Action action = createAlpineApkKAuthAction();
-+ if (!action.isValid()) {
-+ return nullptr;
-+ }
-+ action.setTimeout(1 * 60 * 60 * 1000); // although deletion is almost instant
-+ action.addArgument(QLatin1String("pkgAction"), QLatin1String("del"));
-+ action.addArgument(QLatin1String("pkgName"), pkgName);
-+ return action.execute();
-+}
-+
-+KAuth::ExecuteJob *createRepoconfigAction(const QVariant &repoUrls)
-+{
-+ KAuth::Action action = createAlpineApkKAuthAction();
-+ if (!action.isValid()) {
-+ return nullptr;
-+ }
-+ // should be instant, writes few lines to /etc/apk/repositories
-+ action.setTimeout(1 * 60 * 1000); // 1 minute
-+ action.addArgument(QLatin1String("pkgAction"), QLatin1String("repoconfig"));
-+ action.addArgument(QLatin1String("repoList"), repoUrls);
-+ return action.execute();
-+}
-+
-+} // namespace ActionFactory
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
-new file mode 100644
-index 00000000..ff5667f8
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthActionFactory.h
-@@ -0,0 +1,41 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef AlpineApkAuthActionFactory_H
-+#define AlpineApkAuthActionFactory_H
-+
-+#include <QString>
-+#include <QVariant>
-+
-+#include <KAuthAction>
-+#include <KAuthActionReply>
-+#include <KAuthExecuteJob>
-+
-+namespace ActionFactory {
-+
-+KAuth::ExecuteJob *createUpdateAction(const QString &fakeRoot);
-+KAuth::ExecuteJob *createUpgradeAction(bool onlySimulate = false);
-+KAuth::ExecuteJob *createAddAction(const QString &pkgName);
-+KAuth::ExecuteJob *createDelAction(const QString &pkgName);
-+KAuth::ExecuteJob *createRepoconfigAction(const QVariant &repoUrls);
-+
-+} // namespace ActionFactory
-+
-+#endif
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
-new file mode 100644
-index 00000000..97affc01
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.cpp
-@@ -0,0 +1,302 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include <QProcess>
-+#include <QDebug>
-+#include <QLoggingCategory>
-+#include <QSocketNotifier>
-+#include <QScopedPointer>
-+#include <QFile>
-+
-+#include <KAuthHelperSupport>
-+#include <kauth_version.h>
-+
-+#include "AlpineApkAuthHelper.h"
-+
-+#ifdef QT_DEBUG
-+Q_LOGGING_CATEGORY(LOG_AUTHHELPER, "org.kde.discover.alpineapkbackend.authhelper", QtDebugMsg)
-+#else
-+Q_LOGGING_CATEGORY(LOG_AUTHHELPER, "org.kde.discover.alpineapkbackend.authhelper", QtWarningMsg)
-+#endif
-+
-+using namespace KAuth;
-+
-+AlpineApkAuthHelper::AlpineApkAuthHelper() {}
-+
-+AlpineApkAuthHelper::~AlpineApkAuthHelper()
-+{
-+ closeDatabase();
-+}
-+
-+bool AlpineApkAuthHelper::openDatabase(const QVariantMap &args, bool readwrite)
-+{
-+ // is already opened?
-+ if (m_apkdb.isOpen()) {
-+ return true;
-+ }
-+
-+ // maybe set fakeRoot (needs to be done before Database::open()
-+ const QString fakeRoot = args.value(QLatin1String("fakeRoot"), QString()).toString();
-+ if (!fakeRoot.isEmpty()) {
-+ m_apkdb.setFakeRoot(fakeRoot);
-+ }
-+
-+ // calculate flags to use during open
-+ QtApk::DbOpenFlags fl = QtApk::QTAPK_OPENF_ENABLE_PROGRESSFD;
-+ if (readwrite) {
-+ fl |= QtApk::QTAPK_OPENF_READWRITE;
-+ }
-+
-+ if (!m_apkdb.open(fl)) {
-+ return false;
-+ }
-+ return true;
-+}
-+
-+void AlpineApkAuthHelper::closeDatabase()
-+{
-+ // close database only if opened
-+ if (m_apkdb.isOpen()) {
-+ // this also stops bg thread
-+ m_apkdb.close();
-+ }
-+}
-+
-+void AlpineApkAuthHelper::setupTransactionPostCreate(QtApk::Transaction *trans)
-+{
-+ m_currentTransaction = trans; // remember current transaction here
-+
-+ // receive progress notifications
-+ QObject::connect(trans, &QtApk::Transaction::progressChanged,
-+ this, &AlpineApkAuthHelper::reportProgress);
-+
-+ // receive error messages
-+ QObject::connect(trans, &QtApk::Transaction::errorOccured,
-+ this, &AlpineApkAuthHelper::onTransactionError);
-+
-+ // what to do when transaction is complete
-+ QObject::connect(trans, &QtApk::Transaction::finished,
-+ this, &AlpineApkAuthHelper::onTransactionFinished);
-+
-+ if (!m_loop) {
-+ m_loop = new QEventLoop(this);
-+ }
-+}
-+
-+void AlpineApkAuthHelper::reportProgress(float percent)
-+{
-+ int p = static_cast<int>(percent);
-+ if (p < 0) p = 0;
-+ if (p > 100) p = 100;
-+ HelperSupport::progressStep(p);
-+}
-+
-+void AlpineApkAuthHelper::onTransactionError(const QString &msg)
-+{
-+ qCWarning(LOG_AUTHHELPER).nospace() << "ERROR occured in transaction \""
-+ << m_currentTransaction->desc()
-+ << "\": " << msg;
-+ // construct error message to use in helper reply
-+ const QString errMsg = m_currentTransaction->desc() + QLatin1String(" failed: ") + msg;
-+ m_actionReply.setErrorDescription(errMsg);
-+ m_actionReply.setData({
-+ { QLatin1String("errorString"), errMsg }
-+ });
-+ m_trans_ok = false;
-+}
-+
-+void AlpineApkAuthHelper::onTransactionFinished()
-+{
-+ m_lastChangeset = m_currentTransaction->changeset();
-+ m_currentTransaction->deleteLater();
-+ m_currentTransaction = nullptr;
-+ m_loop->quit();
-+}
-+
-+// single entry point for all package management actions
-+ActionReply AlpineApkAuthHelper::pkgmgmt(const QVariantMap &args)
-+{
-+ m_actionReply = ActionReply::HelperErrorReply();
-+ HelperSupport::progressStep(0);
-+
-+ // actual package management action to perform is passed in "pkgAction" argument
-+ if (!args.contains(QLatin1String("pkgAction"))) {
-+ m_actionReply.setError(ActionReply::InvalidActionError);
-+ m_actionReply.setErrorDescription(QLatin1String("Please pass \'pkgAction\' argument."));
-+ HelperSupport::progressStep(100);
-+ return m_actionReply;
-+ }
-+
-+ const QString pkgAction = args.value(QLatin1String("pkgAction")).toString();
-+
-+ if (pkgAction == QStringLiteral("update")) {
-+ update(args);
-+ } else if (pkgAction == QStringLiteral("add")) {
-+ add(args);
-+ } else if (pkgAction == QStringLiteral("del")) {
-+ del(args);
-+ } else if (pkgAction == QStringLiteral("upgrade")) {
-+ upgrade(args);
-+ } else if (pkgAction == QStringLiteral("repoconfig")) {
-+ repoconfig(args);
-+ } else {
-+ // error: unknown pkgAction
-+ m_actionReply.setError(ActionReply::NoSuchActionError);
-+ m_actionReply.setErrorDescription(QLatin1String("Please pass a valid \'pkgAction\' argument. "
-+ "Action \"%1\" is not recognized.").arg(pkgAction));
-+ }
-+
-+ HelperSupport::progressStep(100);
-+ return m_actionReply;
-+}
-+
-+void AlpineApkAuthHelper::update(const QVariantMap &args)
-+{
-+ if (!openDatabase(args)) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
-+ return;
-+ }
-+
-+ m_trans_ok = true;
-+ QtApk::Transaction *trans = m_apkdb.updatePackageIndex();
-+ setupTransactionPostCreate(trans);
-+
-+ trans->start();
-+ m_loop->exec();
-+
-+ if (m_trans_ok) {
-+ int updatesCount = m_apkdb.upgradeablePackagesCount();
-+ m_actionReply = ActionReply::SuccessReply();
-+ m_actionReply.setData({
-+ { QLatin1String("updatesCount"), updatesCount }
-+ });
-+ }
-+}
-+
-+void AlpineApkAuthHelper::add(const QVariantMap &args)
-+{
-+ if (!openDatabase(args)) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
-+ return;
-+ }
-+
-+ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
-+ if (pkgName.isEmpty()) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for adding!"));
-+ return;
-+ }
-+
-+ m_trans_ok = true;
-+ QtApk::Transaction *trans = m_apkdb.add(pkgName);
-+ setupTransactionPostCreate(trans);
-+
-+ trans->start();
-+ m_loop->exec();
-+
-+ if (m_trans_ok) {
-+ m_actionReply = ActionReply::SuccessReply();
-+ }
-+}
-+
-+void AlpineApkAuthHelper::del(const QVariantMap &args)
-+{
-+ if (!openDatabase(args)) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
-+ return;
-+ }
-+
-+ const QString pkgName = args.value(QLatin1String("pkgName"), QString()).toString();
-+ if (pkgName.isEmpty()) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Specify pkgName for removing!"));
-+ return;
-+ }
-+
-+ const bool delRdepends = args.value(QLatin1String("delRdepends"), false).toBool();
-+
-+ QtApk::DbDelFlags delFlags = QtApk::QTAPK_DEL_DEFAULT;
-+ if (delRdepends) {
-+ delFlags = QtApk::QTAPK_DEL_RDEPENDS;
-+ }
-+
-+ m_trans_ok = true;
-+ QtApk::Transaction *trans = m_apkdb.del(pkgName, delFlags);
-+ setupTransactionPostCreate(trans);
-+
-+ trans->start();
-+ m_loop->exec();
-+
-+ if (m_trans_ok) {
-+ m_actionReply = ActionReply::SuccessReply();
-+ }
-+}
-+
-+void AlpineApkAuthHelper::upgrade(const QVariantMap &args)
-+{
-+ if (!openDatabase(args)) {
-+ m_actionReply.setErrorDescription(QStringLiteral("Failed to open database!"));
-+ return;
-+ }
-+
-+ bool onlySimulate = args.value(QLatin1String("onlySimulate"), false).toBool();
-+ QtApk::DbUpgradeFlags flags = QtApk::QTAPK_UPGRADE_DEFAULT;
-+ if (onlySimulate) {
-+ flags = QtApk::QTAPK_UPGRADE_SIMULATE;
-+ qCDebug(LOG_AUTHHELPER) << "Simulating upgrade run.";
-+ }
-+
-+ m_trans_ok = true;
-+
-+ QtApk::Transaction *trans = m_apkdb.upgrade(flags);
-+ setupTransactionPostCreate(trans);
-+
-+ trans->start();
-+ m_loop->exec();
-+
-+ if (m_trans_ok) {
-+ m_actionReply = ActionReply::SuccessReply();
-+ QVariantMap replyData;
-+ const QVector<QtApk::ChangesetItem> ch = m_lastChangeset.changes();
-+ QVector<QVariant> chVector;
-+ QVector<QtApk::Package> pkgVector;
-+ for (const QtApk::ChangesetItem &it: ch) {
-+ pkgVector << it.newPackage;
-+ }
-+ replyData.insert(QLatin1String("changes"), QVariant::fromValue(pkgVector));
-+ replyData.insert(QLatin1String("onlySimulate"), onlySimulate);
-+ m_actionReply.setData(replyData);
-+ }
-+}
-+
-+void AlpineApkAuthHelper::repoconfig(const QVariantMap &args)
-+{
-+ if (args.contains(QLatin1String("repoList"))) {
-+ const QVariant v = args.value(QLatin1String("repoList"));
-+ const QVector<QtApk::Repository> repoVec = v.value<QVector<QtApk::Repository>>();
-+ if (QtApk::Database::saveRepositories(repoVec)) {
-+ m_actionReply = ActionReply::SuccessReply(); // OK
-+ } else {
-+ m_actionReply.setErrorDescription(QStringLiteral("Failed to write repositories config!"));
-+ }
-+ } else {
-+ m_actionReply.setErrorDescription(QStringLiteral("repoList parameter is missing in request!"));
-+ }
-+}
-+
-+KAUTH_HELPER_MAIN("org.kde.discover.alpineapkbackend", AlpineApkAuthHelper)
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
-new file mode 100644
-index 00000000..240e6ed3
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkAuthHelper.h
-@@ -0,0 +1,66 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include <QEventLoop>
-+#include <QObject>
-+#include <QVariant>
-+#include <KAuthActionReply>
-+
-+#include <QtApk>
-+
-+using namespace KAuth;
-+
-+class AlpineApkAuthHelper : public QObject
-+{
-+ Q_OBJECT
-+public:
-+ AlpineApkAuthHelper();
-+ ~AlpineApkAuthHelper() override;
-+
-+public Q_SLOTS:
-+ // single entry point for all package management operations
-+ ActionReply pkgmgmt(const QVariantMap &args);
-+
-+protected:
-+ // helpers
-+ bool openDatabase(const QVariantMap &args, bool readwrite = true);
-+ void closeDatabase();
-+ void setupTransactionPostCreate(QtApk::Transaction *trans);
-+
-+ // individual pakckage management actions
-+ void update(const QVariantMap &args);
-+ void add(const QVariantMap &args);
-+ void del(const QVariantMap &args);
-+ void upgrade(const QVariantMap &args);
-+ void repoconfig(const QVariantMap &args);
-+
-+protected Q_SLOTS:
-+ void reportProgress(float percent);
-+ void onTransactionError(const QString &msg);
-+ void onTransactionFinished();
-+
-+private:
-+ QtApk::DatabaseAsync m_apkdb; // runs transactions in bg thread
-+ QtApk::Transaction *m_currentTransaction = nullptr;
-+ QEventLoop *m_loop = nullptr; // event loop that will run and wait while bg transaction is in progress
-+ ActionReply m_actionReply; // return value for main action slots
-+ bool m_trans_ok = true; // flag to indicate if bg transaction was successful
-+ QtApk::Changeset m_lastChangeset; // changeset from last completed transaction
-+};
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
-new file mode 100644
-index 00000000..e1a4c387
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
-@@ -0,0 +1,502 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkBackend.h"
-+#include "AlpineApkResource.h"
-+#include "AlpineApkReviewsBackend.h"
-+#include "AlpineApkTransaction.h"
-+#include "AlpineApkSourcesBackend.h"
-+#include "AlpineApkUpdater.h"
-+#include "AppstreamDataDownloader.h"
-+#include "alpineapk_backend_logging.h" // generated by ECM
-+
-+#include "resources/SourcesModel.h"
-+#include "Transaction/Transaction.h"
-+#include "Category/Category.h"
-+
-+#include <KLocalizedString>
-+
-+#include <AppStreamQt/pool.h>
-+
-+#include <QtConcurrentRun>
-+#include <QDebug>
-+#include <QFuture>
-+#include <QFutureWatcher>
-+#include <QLoggingCategory>
-+#include <QSet>
-+#include <QThread>
-+#include <QThreadPool>
-+#include <QTimer>
-+
-+DISCOVER_BACKEND_PLUGIN(AlpineApkBackend)
-+
-+AlpineApkBackend::AlpineApkBackend(QObject *parent)
-+ : AbstractResourcesBackend(parent)
-+ , m_updater(new AlpineApkUpdater(this))
-+ , m_reviews(new AlpineApkReviewsBackend(this))
-+ , m_updatesTimeoutTimer(new QTimer(this))
-+{
-+#ifndef QT_DEBUG
-+ const_cast<QLoggingCategory &>(LOG_ALPINEAPK()).setEnabled(QtDebugMsg, false);
-+#endif
-+
-+ // connections with our updater
-+ QObject::connect(m_updater, &AlpineApkUpdater::updatesCountChanged,
-+ this, &AlpineApkBackend::updatesCountChanged);
-+ QObject::connect(m_updater, &AlpineApkUpdater::checkForUpdatesFinished,
-+ this, &AlpineApkBackend::finishCheckForUpdates);
-+ QObject::connect(m_updater, &AlpineApkUpdater::fetchingUpdatesProgressChanged,
-+ this, &AlpineApkBackend::setFetchingUpdatesProgress);
-+
-+ // safety measure: make sure update check process can finish in some finite time
-+ QObject::connect(m_updatesTimeoutTimer, &QTimer::timeout,
-+ this, &AlpineApkBackend::finishCheckForUpdates);
-+ m_updatesTimeoutTimer->setTimerType(Qt::CoarseTimer);
-+ m_updatesTimeoutTimer->setSingleShot(true);
-+ m_updatesTimeoutTimer->setInterval(5 * 60 * 1000); // 5 minutes
-+
-+ // load packages data in a separate thread; it takes a noticeable amount of time
-+ // and this way UI is not blocked here
-+ m_fetching = true; // we are busy!
-+ QFuture<void> loadResFuture = QtConcurrent::run(QThreadPool::globalInstance(), this,
-+ &AlpineApkBackend::loadResources);
-+
-+ QObject::connect(&m_voidFutureWatcher, &QFutureWatcher<void>::finished,
-+ this, &AlpineApkBackend::onLoadResourcesFinished);
-+ m_voidFutureWatcher.setFuture(loadResFuture);
-+
-+ SourcesModel::global()->addSourcesBackend(new AlpineApkSourcesBackend(this));
-+}
-+
-+// this fills in m_appStreamComponents
-+void AlpineApkBackend::loadAppStreamComponents()
-+{
-+ AppStream::Pool *appStreamPool = new AppStream::Pool();
-+ appStreamPool->setFlags(AppStream::Pool::FlagReadCollection |
-+ AppStream::Pool::FlagReadMetainfo |
-+ AppStream::Pool::FlagReadDesktopFiles);
-+ appStreamPool->setCacheFlags(AppStream::Pool::CacheFlagUseUser |
-+ AppStream::Pool::CacheFlagUseSystem);
-+
-+ // hey hey! cool stuff
-+ appStreamPool->addMetadataLocation(AppstreamDataDownloader::getAppStreamCacheDir());
-+
-+ if (!appStreamPool->load()) {
-+ qCWarning(LOG_ALPINEAPK) << "backend: Failed to load appstream data:"
-+ << appStreamPool->lastError();
-+ } else {
-+ m_appStreamComponents = appStreamPool->components();
-+ qCDebug(LOG_ALPINEAPK) << "backend: loaded AppStream metadata OK:"
-+ << m_appStreamComponents.size() << "components.";
-+ // collect all categories present in appstream metadata
-+ // QSet<QString> collectedCategories;
-+ // for (const AppStream::Component &component : m_appStreamComponents) {
-+ // const QStringList cats = component.categories();
-+ // for (const QString &cat : cats) {
-+ // collectedCategories.insert(cat);
-+ // }
-+ // }
-+ // for (const QString &cat : collectedCategories) {
-+ // qCDebug(LOG_ALPINEAPK) << " collected category: " << cat;
-+ // m_collectedCategories << cat;
-+ // }
-+ }
-+ delete appStreamPool;
-+}
-+
-+// this uses m_appStreamComponents and m_availablePackages
-+// to fill in m_resourcesAppstreamData
-+void AlpineApkBackend::parseAppStreamMetadata()
-+{
-+ if (m_availablePackages.size() > 0) {
-+
-+ for (const QtApk::Package &pkg: qAsConst(m_availablePackages)) {
-+
-+ // try to find appstream data for this package
-+ AppStream::Component appstreamComponent;
-+ for (const auto& appsC : qAsConst(m_appStreamComponents)) {
-+ // find result which package name is exactly the one we want
-+ if (appsC.packageNames().contains(pkg.name)) {
-+ // workaround for kate (Kate Sessions is found first, but
-+ // package name = "kate" too, bugged metadata?)
-+ if (pkg.name == QStringLiteral("kate")) {
-+ // qCDebug(LOG_ALPINEAPK) << appsC.packageNames() << appsC.id();
-+ // ^^ ("kate") "org.kde.plasma.katesessions"
-+ if (appsC.id() != QStringLiteral("org.kde.kate")) {
-+ continue;
-+ }
-+ }
-+ appstreamComponent = appsC;
-+ break; // exit for() loop
-+ }
-+ }
-+
-+ const QString key = pkg.name.toLower();
-+ m_resourcesAppstreamData.insert(key, appstreamComponent);
-+ }
-+ }
-+}
-+
-+static AbstractResource::Type toDiscoverResourceType(const AppStream::Component &component)
-+{
-+ AbstractResource::Type resType = AbstractResource::Type::Technical; // default
-+ // determine resource type here
-+ switch (component.kind()) {
-+ case AppStream::Component::KindDesktopApp:
-+ case AppStream::Component::KindConsoleApp:
-+ case AppStream::Component::KindWebApp:
-+ resType = AbstractResource::Type::Application;
-+ break;
-+ case AppStream::Component::KindAddon:
-+ resType = AbstractResource::Type::Addon;
-+ break;
-+ default:
-+ resType = AbstractResource::Type::Technical;
-+ break;
-+ }
-+ return resType;
-+}
-+
-+void AlpineApkBackend::fillResourcesAndApplyAppStreamData()
-+{
-+ // now the tricky part - we need to reapply appstream component metadata to each resource
-+ if (m_availablePackages.size() > 0) {
-+ for (const QtApk::Package &pkg: m_availablePackages) {
-+ const QString key = pkg.name.toLower();
-+
-+ AppStream::Component &appsComponent = m_resourcesAppstreamData[key];
-+ const AbstractResource::Type resType = toDiscoverResourceType(appsComponent);
-+
-+ AlpineApkResource *res = m_resources.value(key, nullptr);
-+ if (res == nullptr) {
-+ // during first run of this function during initial load
-+ // m_resources hash is empty, so we need to insert new items
-+ res = new AlpineApkResource(pkg, appsComponent, resType, this);
-+ res->setCategoryName(QStringLiteral("alpine_packages"));
-+ res->setOriginSource(QStringLiteral("apk"));
-+ res->setSection(QStringLiteral("dummy"));
-+ m_resources.insert(key, res);
-+ QObject::connect(res, &AlpineApkResource::stateChanged,
-+ this, &AlpineApkBackend::updatesCountChanged);
-+ } else {
-+ // this is not an initial run, just update existing resource
-+ res->setAppStreamData(appsComponent);
-+ }
-+ }
-+ }
-+}
-+
-+void AlpineApkBackend::reloadAppStreamMetadata()
-+{
-+ // mark us as "Loading..."
-+ m_fetching = true;
-+ emit fetchingChanged();
-+
-+ loadAppStreamComponents();
-+ parseAppStreamMetadata();
-+ fillResourcesAndApplyAppStreamData();
-+
-+ // mark us as "done loading"
-+ m_fetching = false;
-+ emit fetchingChanged();
-+}
-+
-+// this function is executed in the background thread
-+void AlpineApkBackend::loadResources()
-+{
-+ Q_EMIT this->passiveMessage(i18n("Loading, please wait..."));
-+
-+ qCDebug(LOG_ALPINEAPK) << "backend: loading AppStream metadata...";
-+
-+ loadAppStreamComponents();
-+
-+ qCDebug(LOG_ALPINEAPK) << "backend: populating resources...";
-+
-+ if (m_apkdb.open(QtApk::QTAPK_OPENF_READONLY)) {
-+ m_availablePackages = m_apkdb.getAvailablePackages();
-+ m_installedPackages = m_apkdb.getInstalledPackages();
-+ m_apkdb.close();
-+ }
-+
-+ parseAppStreamMetadata();
-+
-+ qCDebug(LOG_ALPINEAPK) << " available" << m_availablePackages.size()
-+ << "packages";
-+ qCDebug(LOG_ALPINEAPK) << " installed" << m_installedPackages.size()
-+ << "packages";
-+}
-+
-+void AlpineApkBackend::onLoadResourcesFinished()
-+{
-+ qCDebug(LOG_ALPINEAPK) << "backend: appstream data loaded and sorted; fill in resources";
-+
-+ fillResourcesAndApplyAppStreamData();
-+
-+ // update "installed/not installed" state
-+ if (m_installedPackages.size() > 0) {
-+ for (const QtApk::Package &pkg: m_installedPackages) {
-+ const QString key = pkg.name.toLower();
-+ if (m_resources.contains(key)) {
-+ m_resources.value(key)->setState(AbstractResource::Installed);
-+ }
-+ }
-+ }
-+
-+ qCDebug(LOG_ALPINEAPK) << "backend: resources loaded.";
-+
-+ m_fetching = false;
-+ emit fetchingChanged();
-+ // ^^ this causes the UI to update "Featured" page and show
-+ // to user that we actually have loaded packages data
-+
-+ // schedule check for updates 1 sec after we've loaded all resources
-+ QTimer::singleShot(1000, this, &AlpineApkBackend::checkForUpdates);
-+
-+ // AppStream appdata downloader can download updated metadata files
-+ // in a background thread. When potential download is finished,
-+ // appstream data will be reloaded.
-+ m_appstreamDownloader = new AppstreamDataDownloader(nullptr);
-+ QObject::connect(m_appstreamDownloader, &AppstreamDataDownloader::downloadFinished,
-+ this, &AlpineApkBackend::onAppstreamDataDownloaded, Qt::QueuedConnection);
-+ m_appstreamDownloader->start();
-+}
-+
-+void AlpineApkBackend::onAppstreamDataDownloaded()
-+{
-+ if (m_appstreamDownloader) {
-+ if (m_appstreamDownloader->cacheWasUpdated()) {
-+ // it means we need to reload previously loaded appstream metadata
-+ // m_fetching is true if loadResources() is still executing
-+ // in a background thread
-+ if (!m_fetching) {
-+ qCDebug(LOG_ALPINEAPK) << "AppStream metadata was updated; re-applying it to all resources";
-+ reloadAppStreamMetadata();
-+ } else {
-+ qCWarning(LOG_ALPINEAPK) << "AppStream metadata was updated, but cannot apply it: still fetching";
-+ // it should not really happen, but if it happens,
-+ // then downloaded metadata will be used on the next
-+ // discover launch anyway.
-+ }
-+ }
-+ delete m_appstreamDownloader;
-+ m_appstreamDownloader = nullptr;
-+ }
-+}
-+
-+QVector<Category *> AlpineApkBackend::category() const
-+{
-+ static QPair<FilterType, QString> s_apkFlt(
-+ FilterType::CategoryFilter, QLatin1String("alpine_packages"));
-+
-+ // Display a single root category
-+ // we could add more, but Alpine apk does not have this concept
-+ static Category *s_rootCat = new Category(
-+ i18nc("Root category name", "Alpine Linux packages"),
-+ QStringLiteral("package-x-generic"), // icon
-+ { s_apkFlt }, // orFilters - include packages that match filter
-+ { displayName() }, // pluginName
-+ {}, // subCategories - none
-+ QUrl(), // decoration (what is it?)
-+ false // isAddons
-+ );
-+
-+ return { s_rootCat };
-+
-+// static QVector<Category *> s_cats;
-+// if (s_cats.isEmpty()) {
-+// // fill only once
-+// s_cats << s_rootCat;
-+// for (const QString &scat : m_collectedCategories) {
-+// Category *cat = new Category(
-+// scat, // name
-+// QStringLiteral("package-x-generic"), // icon
-+// {}, // orFilters
-+// { displayName() }, // pluginName
-+// {}, // subcategories
-+// QUrl(), // decoration
-+// false // isAddons
-+// );
-+// s_cats << cat;
-+// }
-+// }
-+// return s_cats;
-+ // ^^ causes deep hang in discover in recalculating QML bindings
-+}
-+
-+int AlpineApkBackend::updatesCount() const
-+{
-+ return m_updater->updatesCount();
-+}
-+
-+ResultsStream *AlpineApkBackend::search(const AbstractResourcesBackend::Filters &filter)
-+{
-+ QVector<AbstractResource*> ret;
-+ if (!filter.resourceUrl.isEmpty()) {
-+ return findResourceByPackageName(filter.resourceUrl);
-+ } else {
-+ for (AbstractResource *resource: qAsConst(m_resources)) {
-+ // skip technical package types (not apps/addons)
-+ // that are not upgradeable
-+ // (does not work because for now all Alpine packages are "technical"
-+ // if (resource->type() == AbstractResource::Technical
-+ // && filter.state != AbstractResource::Upgradeable) {
-+ // continue;
-+ // }
-+
-+ // skip not-requested states
-+ if (resource->state() < filter.state) {
-+ continue;
-+ }
-+
-+ if(resource->name().contains(filter.search, Qt::CaseInsensitive)
-+ || resource->comment().contains(filter.search, Qt::CaseInsensitive)) {
-+ ret += resource;
-+ }
-+ }
-+ }
-+ return new ResultsStream(QStringLiteral("AlpineApkStream"), ret);
-+}
-+
-+ResultsStream *AlpineApkBackend::findResourceByPackageName(const QUrl &searchUrl)
-+{
-+// if (search.isLocalFile()) {
-+// AlpineApkResource* res = new AlpineApkResource(
-+// search.fileName(), AbstractResource::Technical, this);
-+// res->setSize(666);
-+// res->setState(AbstractResource::None);
-+// m_resources.insert(res->packageName(), res);
-+// connect(res, &AlpineApkResource::stateChanged, this, &AlpineApkBackend::updatesCountChanged);
-+// return new ResultsStream(QStringLiteral("AlpineApkStream-local"), { res });
-+// }
-+
-+ AlpineApkResource *result = nullptr;
-+
-+ // QUrl("appstream://org.kde.krita.desktop")
-+ // smart workaround for appstream URLs - handle "featured" apps
-+ if (searchUrl.scheme() == QLatin1String("appstream")) {
-+ // remove leading "org.kde."
-+ QString pkgName = searchUrl.host();
-+ if (pkgName.startsWith(QLatin1String("org.kde."))) {
-+ pkgName = pkgName.mid(8);
-+ }
-+ // remove trailing ".desktop"
-+ if (pkgName.endsWith(QLatin1String(".desktop"))) {
-+ pkgName = pkgName.left(pkgName.length() - 8);
-+ }
-+ // now we can search for "krita" package
-+ result = m_resources.value(pkgName);
-+ }
-+
-+ // QUrl("apk://krita")
-+ // handle packages from Alpine repos
-+ if (searchUrl.scheme() == QLatin1String("apk")) {
-+ const QString pkgName = searchUrl.host();
-+ result = m_resources.value(pkgName);
-+ }
-+
-+ if (!result) {
-+ return new ResultsStream(QStringLiteral("AlpineApkStream"), {});
-+ }
-+ return new ResultsStream(QStringLiteral("AlpineApkStream"), { result });
-+}
-+
-+AbstractBackendUpdater *AlpineApkBackend::backendUpdater() const
-+{
-+ return m_updater;
-+}
-+
-+AbstractReviewsBackend *AlpineApkBackend::reviewsBackend() const
-+{
-+ return m_reviews;
-+}
-+
-+Transaction* AlpineApkBackend::installApplication(AbstractResource *app, const AddonList &addons)
-+{
-+ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
-+ addons, Transaction::InstallRole);
-+}
-+
-+Transaction* AlpineApkBackend::installApplication(AbstractResource *app)
-+{
-+ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
-+ Transaction::InstallRole);
-+}
-+
-+Transaction* AlpineApkBackend::removeApplication(AbstractResource *app)
-+{
-+ return new AlpineApkTransaction(qobject_cast<AlpineApkResource *>(app),
-+ Transaction::RemoveRole);
-+}
-+
-+int AlpineApkBackend::fetchingUpdatesProgress() const
-+{
-+ if (!m_fetching) return 100;
-+ return m_fetchProgress;
-+}
-+
-+void AlpineApkBackend::checkForUpdates()
-+{
-+ if (m_fetching) {
-+ qCDebug(LOG_ALPINEAPK) << "backend: checkForUpdates(): already fetching";
-+ return;
-+ }
-+
-+ qCDebug(LOG_ALPINEAPK) << "backend: start checkForUpdates()";
-+
-+ // safety measure - finish updates check in some time
-+ m_updatesTimeoutTimer->start();
-+
-+ // let our updater do the job
-+ m_updater->startCheckForUpdates();
-+
-+ // update UI
-+ m_fetching = true;
-+ m_fetchProgress = 0;
-+ emit fetchingChanged();
-+ emit fetchingUpdatesProgressChanged();
-+}
-+
-+void AlpineApkBackend::finishCheckForUpdates()
-+{
-+ m_updatesTimeoutTimer->stop(); // stop safety timer
-+ // update UI
-+ m_fetching = false;
-+ emit fetchingChanged();
-+ emit fetchingUpdatesProgressChanged();
-+}
-+
-+QString AlpineApkBackend::displayName() const
-+{
-+ return i18nc("Backend plugin display name", "Alpine APK backend");
-+}
-+
-+bool AlpineApkBackend::hasApplications() const
-+{
-+ return true;
-+}
-+
-+void AlpineApkBackend::setFetchingUpdatesProgress(int percent)
-+{
-+ m_fetchProgress = percent;
-+ emit fetchingUpdatesProgressChanged();
-+}
-+
-+// needed because DISCOVER_BACKEND_PLUGIN(AlpineApkBackend) contains Q_OBJECT
-+#include "AlpineApkBackend.moc"
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
-new file mode 100644
-index 00000000..07b6b7be
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.h
-@@ -0,0 +1,99 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef AlpineApkBackend_H
-+#define AlpineApkBackend_H
-+
-+#include <resources/AbstractResourcesBackend.h>
-+
-+#include <QFutureWatcher>
-+#include <QVariantList>
-+
-+#include <QtApk>
-+
-+#include <AppStreamQt/component.h>
-+
-+class AlpineApkReviewsBackend;
-+class AlpineApkUpdater;
-+class AlpineApkResource;
-+class AppstreamDataDownloader;
-+class KJob;
-+class QTimer;
-+
-+class AlpineApkBackend : public AbstractResourcesBackend
-+{
-+ Q_OBJECT
-+
-+public:
-+ explicit AlpineApkBackend(QObject *parent = nullptr);
-+
-+ QVector<Category *> category() const override;
-+ int updatesCount() const override;
-+ AbstractBackendUpdater *backendUpdater() const override;
-+ AbstractReviewsBackend *reviewsBackend() const override;
-+ ResultsStream *search(const AbstractResourcesBackend::Filters &filter) override;
-+ ResultsStream *findResourceByPackageName(const QUrl &search);
-+ QHash<QString, AlpineApkResource *> resources() const { return m_resources; }
-+ QHash<QString, AlpineApkResource *> *resourcesPtr() { return &m_resources; }
-+ bool isValid() const override { return true; } // No external file dependencies that could cause runtime errors
-+
-+ Transaction *installApplication(AbstractResource *app) override;
-+ Transaction *installApplication(AbstractResource *app, const AddonList &addons) override;
-+ Transaction *removeApplication(AbstractResource *app) override;
-+ bool isFetching() const override { return m_fetching; }
-+ int fetchingUpdatesProgress() const override;
-+ void checkForUpdates() override;
-+ QString displayName() const override;
-+ bool hasApplications() const override;
-+
-+public Q_SLOTS:
-+ void setFetchingUpdatesProgress(int percent);
-+
-+private Q_SLOTS:
-+ void finishCheckForUpdates();
-+ void loadAppStreamComponents();
-+ void parseAppStreamMetadata();
-+ void reloadAppStreamMetadata();
-+ void fillResourcesAndApplyAppStreamData();
-+ void loadResources();
-+ void onLoadResourcesFinished();
-+ void onAppstreamDataDownloaded();
-+
-+public:
-+ QtApk::Database *apkdb() { return &m_apkdb; }
-+
-+private:
-+ QHash<QString, AlpineApkResource *> m_resources;
-+ QHash<QString, AppStream::Component> m_resourcesAppstreamData;
-+ AlpineApkUpdater *m_updater;
-+ AlpineApkReviewsBackend *m_reviews;
-+ QtApk::Database m_apkdb;
-+ QVector<QtApk::Package> m_availablePackages;
-+ QVector<QtApk::Package> m_installedPackages;
-+ bool m_fetching = false;
-+ int m_fetchProgress = 0;
-+ QTimer *m_updatesTimeoutTimer;
-+ QList<AppStream::Component> m_appStreamComponents;
-+ // QVector<QString> m_collectedCategories;
-+ QFutureWatcher<void> m_voidFutureWatcher;
-+ AppstreamDataDownloader *m_appstreamDownloader;
-+};
-+
-+#endif // AlpineApkBackend_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
-new file mode 100644
-index 00000000..8f493a49
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.cpp
-@@ -0,0 +1,341 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkResource.h"
-+#include "alpineapk_backend_logging.h" // generated by ECM
-+
-+#include <AppStreamQt/icon.h>
-+#include <AppStreamQt/pool.h>
-+#include <AppStreamQt/release.h>
-+
-+#include <QFileInfo>
-+#include <QIcon>
-+#include <QProcess>
-+
-+// libdiscover
-+#include "appstream/AppStreamUtils.h"
-+#include "config-paths.h"
-+#include "Transaction/AddonList.h"
-+
-+AlpineApkResource::AlpineApkResource(const QtApk::Package &apkPkg,
-+ AppStream::Component &component,
-+ AbstractResource::Type typ,
-+ AbstractResourcesBackend *parent)
-+ : AbstractResource(parent)
-+ , m_state(AbstractResource::State::None)
-+ , m_type(typ)
-+ , m_pkg(apkPkg)
-+ , m_appsC(component)
-+{
-+}
-+
-+QList<PackageState> AlpineApkResource::addonsInformation()
-+{
-+ return m_addons;
-+}
-+
-+QString AlpineApkResource::availableVersion() const
-+{
-+ return m_availableVersion;
-+}
-+
-+QStringList AlpineApkResource::categories()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.categories();
-+ }
-+ return { m_category };
-+}
-+
-+QString AlpineApkResource::comment()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.summary();
-+ }
-+ return m_pkg.description;
-+}
-+
-+int AlpineApkResource::size()
-+{
-+ return static_cast<int>(m_pkg.size);
-+}
-+
-+QUrl AlpineApkResource::homepage()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.url(AppStream::Component::UrlKindHomepage);
-+ }
-+ return QUrl::fromUserInput(m_pkg.url);
-+}
-+
-+QUrl AlpineApkResource::helpURL()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.url(AppStream::Component::UrlKindHelp);
-+ }
-+ return QUrl();
-+}
-+
-+QUrl AlpineApkResource::bugURL()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.url(AppStream::Component::UrlKindBugtracker);
-+ }
-+ return QUrl();
-+}
-+
-+QUrl AlpineApkResource::donationURL()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.url(AppStream::Component::UrlKindDonation);
-+ }
-+ return QUrl();
-+}
-+
-+///xdg-compatible icon name to represent the resource, url or QIcon
-+QVariant AlpineApkResource::icon() const
-+{
-+ if (hasAppStreamData()) {
-+ const QList<AppStream::Icon> icns = m_appsC.icons();
-+ if (icns.size() == 0) {
-+ return QStringLiteral("package-x-generic");
-+ }
-+ QIcon ico;
-+ const AppStream::Icon &appIco = icns.first();
-+
-+ switch (appIco.kind()) {
-+ case AppStream::Icon::KindStock:
-+ // we can create icons of this type directly from theme
-+ ico = QIcon::fromTheme(appIco.name());
-+ break;
-+ case AppStream::Icon::KindLocal:
-+ case AppStream::Icon::KindCached: {
-+ // try from predefined standard Alpine path
-+ const QString appstreamIconsPath = QLatin1String("/usr/share/app-info/icons/");
-+ const QString path = appstreamIconsPath + appIco.url().path();
-+ if (QFileInfo::exists(path)) {
-+ ico.addFile(path, appIco.size());
-+ } else {
-+ const QString altPath = appstreamIconsPath +
-+ QStringLiteral("%1x%2/").arg(appIco.size().width()).arg(appIco.size().height()) +
-+ appIco.url().path();
-+ if (QFileInfo::exists(altPath)) {
-+ ico.addFile(altPath, appIco.size());
-+ }
-+ }
-+ } break;
-+ default: break;
-+ }
-+
-+ // return icon only if we successfully loaded it
-+ if (!ico.isNull()) {
-+ return QVariant::fromValue<QIcon>(ico);
-+ }
-+
-+ // try to load from icon theme by package name, this is better
-+ // than nothing and works surprisingly well for many packages
-+ ico = QIcon::fromTheme(m_pkg.name);
-+ if (!ico.isNull()) {
-+ return QVariant::fromValue<QIcon>(ico);
-+ }
-+ }
-+ return QStringLiteral("package-x-generic");
-+}
-+
-+QString AlpineApkResource::installedVersion() const
-+{
-+ return m_pkg.version;
-+}
-+
-+QJsonArray AlpineApkResource::licenses()
-+{
-+ return {
-+ QJsonObject {
-+ { QStringLiteral("name"), m_pkg.license },
-+ { QStringLiteral("url"), QStringLiteral("https://spdx.org/license-list") },
-+ }
-+ };
-+}
-+
-+QString AlpineApkResource::longDescription()
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.description();
-+ }
-+ return m_pkg.description;
-+}
-+
-+QString AlpineApkResource::name() const
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.name();
-+ }
-+ return m_pkg.name;
-+}
-+
-+QString AlpineApkResource::origin() const
-+{
-+ return m_originSoruce;
-+}
-+
-+QString AlpineApkResource::packageName() const
-+{
-+ return m_pkg.name;
-+}
-+
-+QString AlpineApkResource::section()
-+{
-+ return m_sectionName;
-+}
-+
-+AbstractResource::State AlpineApkResource::state()
-+{
-+ return m_state;
-+}
-+
-+void AlpineApkResource::fetchChangelog()
-+{
-+ if (hasAppStreamData()) {
-+ emit changelogFetched(AppStreamUtils::changelogToHtml(m_appsC));
-+ }
-+}
-+
-+void AlpineApkResource::fetchScreenshots()
-+{
-+ if (hasAppStreamData()) {
-+ const QPair<QList<QUrl>, QList<QUrl> > sc = AppStreamUtils::fetchScreenshots(m_appsC);
-+ Q_EMIT screenshotsFetched(sc.first, sc.second);
-+ }
-+}
-+
-+QString AlpineApkResource::appstreamId() const
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.id();
-+ }
-+ return QString();
-+}
-+
-+void AlpineApkResource::setState(AbstractResource::State state)
-+{
-+ m_state = state;
-+ emit stateChanged();
-+}
-+
-+void AlpineApkResource::setCategoryName(const QString &categoryName)
-+{
-+ m_category = categoryName;
-+}
-+
-+void AlpineApkResource::setOriginSource(const QString &originSource)
-+{
-+ m_originSoruce = originSource;
-+}
-+
-+void AlpineApkResource::setSection(const QString &sectionName)
-+{
-+ m_sectionName = sectionName;
-+}
-+
-+void AlpineApkResource::setAddons(const AddonList &addons)
-+{
-+ const QStringList addonsToInstall = addons.addonsToInstall();
-+ for (const QString &toInstall : addonsToInstall) {
-+ setAddonInstalled(toInstall, true);
-+ }
-+ const QStringList addonsToRemove = addons.addonsToRemove();
-+ for (const QString &toRemove : addonsToRemove) {
-+ setAddonInstalled(toRemove, false);
-+ }
-+}
-+
-+void AlpineApkResource::setAddonInstalled(const QString &addon, bool installed)
-+{
-+ for(PackageState &elem : m_addons) {
-+ if(elem.name() == addon) {
-+ elem.setInstalled(installed);
-+ }
-+ }
-+}
-+
-+void AlpineApkResource::setAvailableVersion(const QString &av)
-+{
-+ m_availableVersion = av;
-+}
-+
-+bool AlpineApkResource::hasAppStreamData() const
-+{
-+ return !m_appsC.id().isEmpty();
-+}
-+
-+void AlpineApkResource::setAppStreamData(const AppStream::Component &component)
-+{
-+ m_appsC = component;
-+}
-+
-+bool AlpineApkResource::canExecute() const
-+{
-+ if (hasAppStreamData()) {
-+ return (m_appsC.kind() == AppStream::Component::KindDesktopApp &&
-+ (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable));
-+ }
-+ return false;
-+}
-+
-+void AlpineApkResource::invokeApplication() const
-+{
-+ const QString desktopFile = QLatin1String("/usr/share/applications/") + appstreamId();
-+ if (QFile::exists(desktopFile)) {
-+ QProcess::startDetached(QStringLiteral("kstart5"), {QStringLiteral("--service"), desktopFile});
-+ }
-+}
-+
-+QUrl AlpineApkResource::url() const
-+{
-+ if (hasAppStreamData()) {
-+ return QUrl(QStringLiteral("appstream://") + appstreamId());
-+ }
-+ return QUrl(QLatin1String("apk://") + packageName());
-+}
-+
-+QString AlpineApkResource::author() const
-+{
-+ if (hasAppStreamData()) {
-+ return m_appsC.developerName();
-+ }
-+ return m_pkg.maintainer;
-+}
-+
-+QString AlpineApkResource::sourceIcon() const
-+{
-+ return QStringLiteral("player-time");
-+}
-+
-+QDate AlpineApkResource::releaseDate() const
-+{
-+ if (hasAppStreamData()) {
-+ if (!m_appsC.releases().isEmpty()) {
-+ auto release = m_appsC.releases().constFirst();
-+ return release.timestamp().date();
-+ }
-+ }
-+ // just build date is fine, too
-+ return m_pkg.buildTime.date();
-+}
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
-new file mode 100644
-index 00000000..5304a877
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkResource.h
-@@ -0,0 +1,93 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef ALPINEAPKRESOURCE_H
-+#define ALPINEAPKRESOURCE_H
-+
-+#include <resources/AbstractResource.h>
-+#include <QtApkPackage.h>
-+#include <AppStreamQt/component.h>
-+
-+class AddonList;
-+
-+class AlpineApkResource : public AbstractResource
-+{
-+ Q_OBJECT
-+
-+public:
-+ explicit AlpineApkResource(const QtApk::Package &apkPkg,
-+ AppStream::Component &component,
-+ AbstractResource::Type typ,
-+ AbstractResourcesBackend *parent);
-+
-+ QList<PackageState> addonsInformation() override;
-+ QString section() override;
-+ QString origin() const override;
-+ QString longDescription() override;
-+ QString availableVersion() const override;
-+ QString installedVersion() const override;
-+ QJsonArray licenses() override;
-+ int size() override;
-+ QUrl homepage() override;
-+ QUrl helpURL() override;
-+ QUrl bugURL() override;
-+ QUrl donationURL() override;
-+ QStringList categories() override;
-+ AbstractResource::State state() override;
-+ QVariant icon() const override;
-+ QString comment() override;
-+ QString name() const override;
-+ QString packageName() const override;
-+ AbstractResource::Type type() const override { return m_type; }
-+ bool canExecute() const override;
-+ void invokeApplication() const override;
-+ void fetchChangelog() override;
-+ void fetchScreenshots() override;
-+ QString appstreamId() const override;
-+ QUrl url() const override;
-+ QString author() const override;
-+ QString sourceIcon() const override;
-+ QDate releaseDate() const override;
-+
-+ void setState(State state);
-+ void setCategoryName(const QString &categoryName);
-+ void setOriginSource(const QString &originSource);
-+ void setSection(const QString &sectionName);
-+ void setAddons(const AddonList &addons);
-+ void setAddonInstalled(const QString &addon, bool installed);
-+ void setAvailableVersion(const QString &av);
-+ void setAppStreamData(const AppStream::Component &component);
-+
-+private:
-+ bool hasAppStreamData() const;
-+
-+public:
-+ AbstractResource::State m_state;
-+ const AbstractResource::Type m_type;
-+ QtApk::Package m_pkg;
-+ QString m_availableVersion;
-+ QString m_category;
-+ QString m_originSoruce;
-+ QString m_sectionName;
-+ QList<PackageState> m_addons;
-+ AppStream::Component m_appsC;
-+};
-+
-+#endif // ALPINEAPKRESOURCE_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
-new file mode 100644
-index 00000000..fd7ad47f
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.cpp
-@@ -0,0 +1,35 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkReviewsBackend.h"
-+#include "AlpineApkBackend.h"
-+#include "resources/AbstractResource.h"
-+
-+AlpineApkReviewsBackend::AlpineApkReviewsBackend(AlpineApkBackend *parent)
-+ : AbstractReviewsBackend(parent)
-+{
-+}
-+
-+void AlpineApkReviewsBackend::fetchReviews(AbstractResource *app, int page)
-+{
-+ Q_UNUSED(page)
-+ static const QVector<ReviewPtr> reviews;
-+ Q_EMIT reviewsReady(app, reviews, false);
-+}
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
-new file mode 100644
-index 00000000..435f845b
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkReviewsBackend.h
-@@ -0,0 +1,52 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef ALPINEAPKREVIEWSBACKEND_H
-+#define ALPINEAPKREVIEWSBACKEND_H
-+
-+#include "ReviewsBackend/AbstractReviewsBackend.h"
-+
-+class AlpineApkBackend;
-+
-+class AlpineApkReviewsBackend : public AbstractReviewsBackend
-+{
-+ Q_OBJECT
-+
-+public:
-+ explicit AlpineApkReviewsBackend(AlpineApkBackend *parent = nullptr);
-+
-+ QString userName() const override { return QStringLiteral("dummy"); }
-+ void login() override {}
-+ void logout() override {}
-+ void registerAndLogin() override {}
-+
-+ Rating *ratingForApplication(AbstractResource *) const override { return nullptr; }
-+ bool hasCredentials() const override { return false; }
-+ void deleteReview(Review *) override {}
-+ void fetchReviews(AbstractResource *app, int page = 1) override;
-+ bool isFetching() const override { return false; }
-+ bool isReviewable() const override { return false; }
-+ void submitReview(AbstractResource *, const QString &, const QString &, const QString &) override {}
-+ void flagReview(Review *, const QString&, const QString&) override {}
-+ void submitUsefulness(Review *, bool) override {}
-+ bool isResourceSupported(AbstractResource *) const override { return false; }
-+};
-+
-+#endif // ALPINEAPKREVIEWSBACKEND_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
-new file mode 100644
-index 00000000..f0148335
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.cpp
-@@ -0,0 +1,195 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkSourcesBackend.h"
-+#include "AlpineApkAuthActionFactory.h"
-+#include "alpineapk_backend_logging.h" // generated by ECM
-+
-+#include <QDebug>
-+#include <QVector>
-+#include "resources/DiscoverAction.h"
-+
-+// KF5
-+#include <KAuthExecuteJob>
-+#include <KLocalizedString>
-+
-+// libapk-qt
-+#include <QtApk>
-+
-+AlpineApkSourcesBackend::AlpineApkSourcesBackend(AbstractResourcesBackend *parent)
-+ : AbstractSourcesBackend(parent)
-+ , m_sourcesModel(new QStandardItemModel(this))
-+ , m_refreshAction(new DiscoverAction(QIcon::fromTheme(QStringLiteral("view-refresh")),
-+ QStringLiteral("Reload"), this))
-+ , m_saveAction(new DiscoverAction(QIcon::fromTheme(QStringLiteral("document-save")),
-+ QStringLiteral("Save"), this))
-+ // ^^ unfortunately QML side ignores icons for custom actions
-+{
-+ loadSources();
-+ QObject::connect(m_refreshAction, &DiscoverAction::triggered,
-+ this, &AlpineApkSourcesBackend::loadSources);
-+ QObject::connect(m_saveAction, &DiscoverAction::triggered,
-+ this, &AlpineApkSourcesBackend::saveSources);
-+ // track enabling/disabling repo source
-+ QObject::connect(m_sourcesModel, &QStandardItemModel::itemChanged,
-+ this, &AlpineApkSourcesBackend::onItemChanged);
-+}
-+
-+QAbstractItemModel *AlpineApkSourcesBackend::sources()
-+{
-+ return m_sourcesModel;
-+}
-+
-+QStandardItem *AlpineApkSourcesBackend::sourceForId(const QString& id) const
-+{
-+ for (int i = 0; i < m_sourcesModel->rowCount(); ++i) {
-+ QStandardItem *item = m_sourcesModel->item(i, 0);
-+ if (item->data(AbstractSourcesBackend::IdRole) == id) {
-+ return item;
-+ }
-+ }
-+ return nullptr;
-+}
-+
-+bool AlpineApkSourcesBackend::addSource(const QString &id)
-+{
-+ m_repos.append(QtApk::Repository(id, QString(), true));
-+ fillModelFromRepos();
-+ return true;
-+}
-+
-+void AlpineApkSourcesBackend::loadSources()
-+{
-+ m_repos = QtApk::Database::getRepositories();
-+ fillModelFromRepos();
-+}
-+
-+void AlpineApkSourcesBackend::fillModelFromRepos()
-+{
-+ m_sourcesModel->clear();
-+ for (const QtApk::Repository &repo: m_repos) {
-+ if (repo.url.isEmpty()) {
-+ continue;
-+ }
-+ qCDebug(LOG_ALPINEAPK) << "source backend: Adding source:" << repo.url << repo.enabled;
-+ QStandardItem *it = new QStandardItem(repo.url);
-+ it->setData(repo.url, AbstractSourcesBackend::IdRole);
-+ it->setData(repo.comment, Qt::ToolTipRole);
-+ it->setCheckable(true);
-+ it->setCheckState(repo.enabled ? Qt::Checked : Qt::Unchecked);
-+ m_sourcesModel->appendRow(it);
-+ }
-+}
-+
-+void AlpineApkSourcesBackend::saveSources()
-+{
-+ const QVariant repoUrls = QVariant::fromValue<QVector<QtApk::Repository>>(m_repos);
-+
-+ // run with elevated privileges
-+ KAuth::ExecuteJob *reply = ActionFactory::createRepoconfigAction(repoUrls);
-+ if (!reply) return;
-+
-+ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this] (KJob *job) {
-+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
-+ if (reply->error() != 0) {
-+ const QString errMessage = reply->errorString();
-+ qCWarning(LOG_ALPINEAPK) << "KAuth helper returned error:"
-+ << reply->error() << errMessage;
-+ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
-+ Q_EMIT passiveMessage(i18n("Authorization denied"));
-+ } else {
-+ Q_EMIT passiveMessage(i18n("Error: ") + errMessage);
-+ }
-+ }
-+ this->loadSources();
-+ });
-+
-+ reply->start();
-+}
-+
-+void AlpineApkSourcesBackend::onItemChanged(QStandardItem *item)
-+{
-+ // update internal storage vector and reload model from it
-+ // otherwise checks state are not updated in UI
-+ const Qt::CheckState cs = item->checkState();
-+ const QModelIndex idx = m_sourcesModel->indexFromItem(item);
-+ m_repos[idx.row()].enabled = (cs == Qt::Checked);
-+ fillModelFromRepos();
-+}
-+
-+bool AlpineApkSourcesBackend::removeSource(const QString &id)
-+{
-+ const QStandardItem *it = sourceForId(id);
-+ if (!it) {
-+ qCWarning(LOG_ALPINEAPK) << "source backend: couldn't find " << id;
-+ return false;
-+ }
-+ m_repos.remove(it->row());
-+ return m_sourcesModel->removeRow(it->row());
-+}
-+
-+QString AlpineApkSourcesBackend::idDescription()
-+{
-+ return i18nc("Adding repo", "Enter Alpine repository URL, for example: "
-+ "http://dl-cdn.alpinelinux.org/alpine/edge/testing/");
-+}
-+
-+QVariantList AlpineApkSourcesBackend::actions() const
-+{
-+ static const QVariantList s_actions {
-+ QVariant::fromValue<QObject *>(m_saveAction),
-+ QVariant::fromValue<QObject *>(m_refreshAction),
-+ };
-+ return s_actions;
-+}
-+
-+bool AlpineApkSourcesBackend::supportsAdding() const
-+{
-+ return true;
-+}
-+
-+bool AlpineApkSourcesBackend::canMoveSources() const
-+{
-+ return true;
-+}
-+
-+bool AlpineApkSourcesBackend::moveSource(const QString& sourceId, int delta)
-+{
-+ int row = sourceForId(sourceId)->row();
-+ QList<QStandardItem *> prevRow = m_sourcesModel->takeRow(row);
-+ if (prevRow.isEmpty()) {
-+ return false;
-+ }
-+
-+ const int destRow = row + delta;
-+ m_sourcesModel->insertRow(destRow, prevRow);
-+ if (destRow == 0 || row == 0) {
-+ Q_EMIT firstSourceIdChanged();
-+ }
-+ if (destRow == (m_sourcesModel->rowCount() - 1)
-+ || row == (m_sourcesModel->rowCount() - 1)) {
-+ Q_EMIT lastSourceIdChanged();
-+ }
-+
-+ // swap also items in internal storage vector
-+ m_repos.swapItemsAt(row, destRow);
-+
-+ return true;
-+}
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
-new file mode 100644
-index 00000000..45697d73
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
-@@ -0,0 +1,59 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef ALPINEAPKSOURCESBACKEND_H
-+#define ALPINEAPKSOURCESBACKEND_H
-+
-+#include <resources/AbstractSourcesBackend.h>
-+#include <QStandardItemModel>
-+
-+#include <QtApkRepository.h>
-+
-+class DiscoverAction;
-+
-+class AlpineApkSourcesBackend : public AbstractSourcesBackend
-+{
-+public:
-+ explicit AlpineApkSourcesBackend(AbstractResourcesBackend *parent);
-+
-+ QAbstractItemModel *sources() override;
-+ bool addSource(const QString &id) override;
-+ bool removeSource(const QString &id) override;
-+ QString idDescription() override;
-+ QVariantList actions() const override;
-+ bool supportsAdding() const override;
-+ bool canMoveSources() const override;
-+ bool moveSource(const QString &sourceId, int delta) override;
-+
-+private:
-+ QStandardItem *sourceForId(const QString &id) const;
-+ bool addSourceFull(const QString &id, const QString &comment, bool enabled);
-+ void loadSources();
-+ void saveSources();
-+ void fillModelFromRepos();
-+ void onItemChanged(QStandardItem* item);
-+
-+ QStandardItemModel *m_sourcesModel = nullptr;
-+ DiscoverAction *m_refreshAction = nullptr;
-+ DiscoverAction *m_saveAction = nullptr;
-+ QVector<QtApk::Repository> m_repos;
-+};
-+
-+#endif // ALPINEAPKSOURCESBACKEND_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
-new file mode 100644
-index 00000000..26bf1bd0
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.cpp
-@@ -0,0 +1,141 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkTransaction.h"
-+#include "AlpineApkBackend.h"
-+#include "AlpineApkResource.h"
-+#include "AlpineApkAuthActionFactory.h"
-+#include "alpineapk_backend_logging.h" // generated by ECM
-+
-+// Qt
-+#include <QDebug>
-+#include <QTimer>
-+
-+// KF5
-+#include <KAuthExecuteJob>
-+#include <KLocalizedString>
-+
-+AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *res, Role role)
-+ : AlpineApkTransaction(res, {}, role)
-+{
-+}
-+
-+AlpineApkTransaction::AlpineApkTransaction(AlpineApkResource *res, const AddonList &addons, Transaction::Role role)
-+ : Transaction(res->backend(), res, role, addons)
-+ , m_resource(res)
-+ , m_backend(static_cast<AlpineApkBackend *>(res->backend()))
-+{
-+ setCancellable(false);
-+ setStatus(QueuedStatus);
-+ // seems like Discover's transactions are supposed to start
-+ // automatically; no dedicated method to start transaction?
-+ startTransaction();
-+}
-+
-+void AlpineApkTransaction::proceed()
-+{
-+ startTransaction();
-+}
-+
-+void AlpineApkTransaction::cancel()
-+{
-+ setStatus(CancelledStatus);
-+}
-+
-+void AlpineApkTransaction::startTransaction()
-+{
-+ KAuth::ExecuteJob *reply = nullptr;
-+ switch(role()) {
-+ case InstallRole:
-+ reply = ActionFactory::createAddAction(m_resource->m_pkg.name);
-+ break;
-+ case RemoveRole:
-+ reply = ActionFactory::createDelAction(m_resource->m_pkg.name);
-+ break;
-+ case ChangeAddonsRole:
-+ qCWarning(LOG_ALPINEAPK) << "Addons are not supported by Alpine APK Backend!";
-+ break;
-+ }
-+
-+ if (!reply) {
-+ return;
-+ }
-+
-+ // get result of this job
-+ QObject::connect(reply, &KAuth::ExecuteJob::result, this, [this](KJob *job) {
-+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
-+ const QVariantMap &replyData = reply->data();
-+ if (reply->error() == 0) {
-+ finishTransactionOK();
-+ } else {
-+ QString message = replyData.value(QLatin1String("errorString"),
-+ reply->errorString()).toString();
-+ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
-+ message = i18n("Error: Authorization denied");
-+ }
-+ finishTransactionWithError(message);
-+ }
-+ });
-+
-+ // get progress reports for this job
-+ QObject::connect(reply, QOverload<KJob*, unsigned long>::of(&KAuth::ExecuteJob::percent), this,
-+ [this](KJob *job, unsigned long percent) {
-+ Q_UNUSED(job)
-+ if (percent >= 40 && role() == InstallRole) {
-+ setStatus(CommittingStatus);
-+ }
-+ setProgress(static_cast<int>(percent));
-+ });
-+
-+ setProgress(0);
-+ if (role() == InstallRole) {
-+ setStatus(DownloadingStatus);
-+ } else {
-+ setStatus(CommittingStatus);
-+ }
-+
-+ reply->start();
-+}
-+
-+void AlpineApkTransaction::finishTransactionOK()
-+{
-+ AbstractResource::State newState;
-+ switch(role()) {
-+ case InstallRole:
-+ case ChangeAddonsRole:
-+ newState = AbstractResource::Installed;
-+ break;
-+ case RemoveRole:
-+ newState = AbstractResource::None;
-+ break;
-+ }
-+ m_resource->setAddons(addons());
-+ m_resource->setState(newState);
-+ setStatus(DoneStatus);
-+ deleteLater();
-+}
-+
-+void AlpineApkTransaction::finishTransactionWithError(const QString &errMsg)
-+{
-+ qCWarning(LOG_ALPINEAPK) << "Transaction finished with error:" << errMsg;
-+ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + errMsg);
-+ setStatus(DoneWithErrorStatus);
-+ deleteLater();
-+}
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
-new file mode 100644
-index 00000000..cab1f6b9
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkTransaction.h
-@@ -0,0 +1,49 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef ALPINEAPKTRANSACTION_H
-+#define ALPINEAPKTRANSACTION_H
-+
-+#include <Transaction/Transaction.h>
-+
-+class AlpineApkBackend;
-+class AlpineApkResource;
-+
-+class AlpineApkTransaction : public Transaction
-+{
-+Q_OBJECT
-+public:
-+ AlpineApkTransaction(AlpineApkResource *res, Role role);
-+ AlpineApkTransaction(AlpineApkResource *res, const AddonList &list, Role role);
-+
-+ void cancel() override;
-+ void proceed() override;
-+
-+private Q_SLOTS:
-+ void startTransaction();
-+ void finishTransactionOK();
-+ void finishTransactionWithError(const QString &errMsg);
-+
-+private:
-+ AlpineApkResource *m_resource;
-+ AlpineApkBackend *m_backend;
-+};
-+
-+#endif // ALPINEAPKTRANSACTION_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
-new file mode 100644
-index 00000000..14df959c
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.cpp
-@@ -0,0 +1,295 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include "AlpineApkUpdater.h"
-+#include "AlpineApkResource.h"
-+#include "AlpineApkBackend.h"
-+#include "AlpineApkAuthActionFactory.h"
-+#include "alpineapk_backend_logging.h"
-+#include "utils.h"
-+
-+#include <KAuthExecuteJob>
-+#include <KLocalizedString>
-+
-+#include <QtApk>
-+
-+
-+AlpineApkUpdater::AlpineApkUpdater(AbstractResourcesBackend *parent)
-+ : AbstractBackendUpdater(parent)
-+ , m_backend(static_cast<AlpineApkBackend *>(parent))
-+{
-+ //
-+}
-+
-+void AlpineApkUpdater::prepare()
-+{
-+ QtApk::Database *db = m_backend->apkdb();
-+
-+ if (db->isOpen()) {
-+ return;
-+ }
-+
-+ // readonly is fine for a simulation of upgrade
-+ if (!db->open(QtApk::QTAPK_OPENF_READONLY)) {
-+ emit passiveMessage(i18n("Failed to open APK database!"));
-+ return;
-+ }
-+
-+ if (!db->upgrade(QtApk::QTAPK_UPGRADE_SIMULATE, &m_upgradeable)) {
-+ emit passiveMessage(i18n("Failed to get a list of packages to upgrade!"));
-+ db->close();
-+ return;
-+ }
-+ // close DB ASAP
-+ db->close();
-+
-+ m_updatesCount = m_upgradeable.changes().size();
-+ qCDebug(LOG_ALPINEAPK) << "updater: prepare: updates count" << m_updatesCount;
-+
-+ m_allUpdateable.clear();
-+ m_markedToUpdate.clear();
-+ QHash<QString, AlpineApkResource *> *resources = m_backend->resourcesPtr();
-+ for (const QtApk::ChangesetItem &it : qAsConst(m_upgradeable.changes())) {
-+ const QtApk::Package &oldPkg = it.oldPackage;
-+ const QString newVersion = it.newPackage.version;
-+ AlpineApkResource *res = resources->value(oldPkg.name);
-+ if (res) {
-+ res->setAvailableVersion(newVersion);
-+ m_allUpdateable.insert(res);
-+ m_markedToUpdate.insert(res);
-+ }
-+ }
-+
-+ // emitting this signal here leads to infinite recursion
-+ // emit updatesCountChanged(m_updatesCount);
-+}
-+
-+bool AlpineApkUpdater::hasUpdates() const
-+{
-+ return (m_updatesCount > 0);
-+}
-+
-+qreal AlpineApkUpdater::progress() const
-+{
-+ return m_upgradeProgress;
-+}
-+
-+void AlpineApkUpdater::removeResources(const QList<AbstractResource *> &apps)
-+{
-+ const QSet<AbstractResource *> checkSet = kToSet(apps);
-+ m_markedToUpdate -= checkSet;
-+}
-+
-+void AlpineApkUpdater::addResources(const QList<AbstractResource *> &apps)
-+{
-+ const QSet<AbstractResource *> checkSet = kToSet(apps);
-+ m_markedToUpdate += checkSet;
-+}
-+
-+QList<AbstractResource *> AlpineApkUpdater::toUpdate() const
-+{
-+ return m_allUpdateable.values();
-+}
-+
-+QDateTime AlpineApkUpdater::lastUpdate() const
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+ return QDateTime();
-+}
-+
-+bool AlpineApkUpdater::isCancelable() const
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+ return false;
-+}
-+
-+bool AlpineApkUpdater::isProgressing() const
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO << m_progressing;
-+ return m_progressing;
-+}
-+
-+bool AlpineApkUpdater::isMarked(AbstractResource *res) const
-+{
-+ return m_markedToUpdate.contains(res);
-+}
-+
-+void AlpineApkUpdater::fetchChangelog() const
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+}
-+
-+double AlpineApkUpdater::updateSize() const
-+{
-+ double sum = 0.0;
-+ for (AbstractResource *res : m_markedToUpdate) {
-+ sum += res->size();
-+ }
-+ return sum;
-+}
-+
-+quint64 AlpineApkUpdater::downloadSpeed() const
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+ return 0;
-+}
-+
-+void AlpineApkUpdater::cancel()
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+}
-+
-+void AlpineApkUpdater::start()
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+
-+ // run upgrade with elevated privileges
-+ KAuth::ExecuteJob *reply = ActionFactory::createUpgradeAction();
-+ if (!reply) return;
-+
-+ QObject::connect(reply, &KAuth::ExecuteJob::result,
-+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperReply);
-+ // qOverload is needed because of conflict with getter named percent()
-+ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
-+ this, &AlpineApkUpdater::handleKAuthUpgradeHelperProgress);
-+
-+ m_progressing = true;
-+ m_upgradeProgress = 0.0;
-+ Q_EMIT progressingChanged(m_progressing);
-+
-+ reply->start();
-+}
-+
-+void AlpineApkUpdater::proceed()
-+{
-+ qCDebug(LOG_ALPINEAPK) << Q_FUNC_INFO;
-+}
-+
-+int AlpineApkUpdater::updatesCount()
-+{
-+ return m_updatesCount;
-+}
-+
-+void AlpineApkUpdater::startCheckForUpdates()
-+{
-+ QtApk::Database *db = m_backend->apkdb();
-+
-+ // run updates check with elevated privileges to access
-+ // system package manager files
-+ KAuth::ExecuteJob *reply = ActionFactory::createUpdateAction(db->fakeRoot());
-+ if (!reply) return;
-+ QObject::connect(reply, &KAuth::ExecuteJob::result,
-+ this, &AlpineApkUpdater::handleKAuthUpdateHelperReply);
-+ // qOverload is needed because of conflict with getter named percent()
-+ QObject::connect(reply, QOverload<KJob *, unsigned long>::of(&KAuth::ExecuteJob::percent),
-+ this, &AlpineApkUpdater::handleKAuthUpdateHelperProgress);
-+
-+ m_progressing = true;
-+ Q_EMIT progressingChanged(m_progressing);
-+ Q_EMIT progressChanged(0);
-+
-+ reply->start();
-+}
-+
-+void AlpineApkUpdater::handleKAuthUpdateHelperReply(KJob *job)
-+{
-+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
-+ const QVariantMap &replyData = reply->data();
-+ if (reply->error() == 0) {
-+ m_updatesCount = replyData.value(QLatin1String("updatesCount")).toInt();
-+ qCDebug(LOG_ALPINEAPK) << "KAuth helper update reply received, updatesCount:" << m_updatesCount;
-+ Q_EMIT updatesCountChanged(m_updatesCount);
-+ } else {
-+ handleKAuthHelperError(reply, replyData);
-+ }
-+
-+ m_progressing = false;
-+ Q_EMIT progressingChanged(m_progressing);
-+
-+ // we are not in the state "Fetching updates" now, update UI
-+ Q_EMIT checkForUpdatesFinished();
-+}
-+
-+void AlpineApkUpdater::handleKAuthUpdateHelperProgress(KJob *job, unsigned long percent)
-+{
-+ Q_UNUSED(job)
-+ qCDebug(LOG_ALPINEAPK) << " fetch updates progress: " << percent;
-+ Q_EMIT fetchingUpdatesProgressChanged(percent);
-+ Q_EMIT progressChanged(static_cast<qreal>(percent));
-+}
-+
-+void AlpineApkUpdater::handleKAuthUpgradeHelperProgress(KJob *job, unsigned long percent)
-+{
-+ Q_UNUSED(job)
-+ qCDebug(LOG_ALPINEAPK) << " upgrade progress: " << percent;
-+ qreal newProgress = static_cast<qreal>(percent);
-+ if (newProgress != m_upgradeProgress) {
-+ m_upgradeProgress = newProgress;
-+ Q_EMIT progressChanged(m_upgradeProgress);
-+ }
-+}
-+
-+void AlpineApkUpdater::handleKAuthUpgradeHelperReply(KJob *job)
-+{
-+ KAuth::ExecuteJob *reply = static_cast<KAuth::ExecuteJob *>(job);
-+ const QVariantMap &replyData = reply->data();
-+ if (reply->error() == 0) {
-+ QVariant pkgsV = replyData.value(QLatin1String("changes"));
-+ bool onlySimulate = replyData.value(QLatin1String("onlySimulate"), false).toBool();
-+ if (onlySimulate) {
-+ qCDebug(LOG_ALPINEAPK) << "KAuth helper upgrade reply received, simulation mode";
-+ QVector<QtApk::Package> pkgVector = pkgsV.value<QVector<QtApk::Package>>();
-+ qCDebug(LOG_ALPINEAPK) << " num changes:" << pkgVector.size();
-+ for (const QtApk::Package &pkg : pkgVector) {
-+ qCDebug(LOG_ALPINEAPK) << " " << pkg.name << pkg.version;
-+ }
-+ }
-+ } else {
-+ handleKAuthHelperError(reply, replyData);
-+ }
-+
-+ m_progressing = false;
-+ Q_EMIT progressingChanged(m_progressing);
-+}
-+
-+void AlpineApkUpdater::handleKAuthHelperError(
-+ KAuth::ExecuteJob *reply,
-+ const QVariantMap &replyData)
-+{
-+ // error message should be received as part of JSON reply from helper
-+ QString message = replyData.value(QLatin1String("errorString"),
-+ reply->errorString()).toString();
-+ if (reply->error() == KAuth::ActionReply::Error::AuthorizationDeniedError) {
-+ qCWarning(LOG_ALPINEAPK) << "updater: KAuth helper returned AuthorizationDeniedError";
-+ Q_EMIT passiveMessage(i18n("Authorization denied"));
-+ } else {
-+ // if received error message is empty, try other ways to get error text for user
-+ // there are multiple ways to get error messages in kauth/kjob
-+ if (message.isEmpty()) {
-+ message = reply->errorString();
-+ if (message.isEmpty()) {
-+ message = reply->errorText();
-+ }
-+ }
-+ qCDebug(LOG_ALPINEAPK) << "updater: KAuth helper returned error:" << message << reply->error();
-+ Q_EMIT passiveMessage(i18n("Error") + QStringLiteral(":\n") + message);
-+ }
-+}
-+
-diff --git a/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
-new file mode 100644
-index 00000000..6ca3ce07
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkUpdater.h
-@@ -0,0 +1,197 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef ALPINEAPKUPDATER_H
-+#define ALPINEAPKUPDATER_H
-+
-+#include "resources/AbstractBackendUpdater.h"
-+#include "resources/AbstractResourcesBackend.h"
-+
-+#include <QSet>
-+#include <QDateTime>
-+#include <QTimer>
-+#include <QVariant>
-+#include <QMap>
-+#include <QVector>
-+
-+#include <QtApkChangeset.h>
-+
-+class AbstractResourcesBackend;
-+class AlpineApkBackend;
-+class KJob;
-+namespace KAuth {
-+ class ExecuteJob;
-+}
-+
-+class AlpineApkUpdater : public AbstractBackendUpdater
-+{
-+ Q_OBJECT
-+ Q_PROPERTY(int updatesCount READ updatesCount NOTIFY updatesCountChanged)
-+
-+public:
-+ explicit AlpineApkUpdater(AbstractResourcesBackend *parent = nullptr);
-+
-+ /**
-+ * This method is called, when Muon switches to the updates view.
-+ * Here the backend should mark all upgradeable packages as to be upgraded.
-+ */
-+ void prepare() override;
-+
-+ /**
-+ * @returns true if the backend contains packages which can be updated
-+ */
-+ bool hasUpdates() const override;
-+ /**
-+ * @returns the progress of the update in percent
-+ */
-+ qreal progress() const override;
-+
-+ /**
-+ * This method is used to remove resources from the list of packages
-+ * marked to be upgraded. It will potentially be called before \start.
-+ */
-+ void removeResources(const QList<AbstractResource*> &apps) override;
-+
-+ /**
-+ * This method is used to add resource to the list of packages marked to be upgraded.
-+ * It will potentially be called before \start.
-+ */
-+ void addResources(const QList<AbstractResource*> &apps) override;
-+
-+ /**
-+ * @returns the list of updateable resources in the system
-+ */
-+ QList<AbstractResource *> toUpdate() const override;
-+
-+ /**
-+ * @returns the QDateTime when the last update happened
-+ */
-+ QDateTime lastUpdate() const override;
-+
-+ /**
-+ * @returns whether the updater can currently be canceled or not
-+ * @see cancelableChanged
-+ */
-+ bool isCancelable() const override;
-+
-+ /**
-+ * @returns whether the updater is currently running or not
-+ * this property decides, if there will be progress reporting in the GUI.
-+ * This has to stay true during the whole transaction!
-+ * @see progressingChanged
-+ */
-+ bool isProgressing() const override;
-+
-+ /**
-+ * @returns whether @p res is marked for update
-+ */
-+ bool isMarked(AbstractResource* res) const override;
-+
-+ void fetchChangelog() const override;
-+
-+ /**
-+ * @returns the size of all the packages set to update combined
-+ */
-+ double updateSize() const override;
-+
-+ /**
-+ * @returns the speed at which we are downloading
-+ */
-+ quint64 downloadSpeed() const override;
-+
-+public Q_SLOTS:
-+ /**
-+ * If \isCancelable is true during the transaction, this method has
-+ * to be implemented and will potentially be called when the user
-+ * wants to cancel the update.
-+ */
-+ void cancel() override;
-+
-+ /**
-+ * This method starts the update. All packages which are in \toUpdate
-+ * are going to be updated.
-+ *
-+ * From this moment on the AbstractBackendUpdater should continuously update
-+ * the other methods to show its progress.
-+ *
-+ * @see progress
-+ * @see progressChanged
-+ * @see isProgressing
-+ * @see progressingChanged
-+ */
-+ void start() override;
-+
-+ /**
-+ * Answers a proceed request
-+ */
-+ void proceed() override;
-+
-+Q_SIGNALS:
-+ void checkForUpdatesFinished();
-+ void updatesCountChanged(int updatesCount);
-+ void fetchingUpdatesProgressChanged(int progress);
-+ //void cancelTransaction();
-+
-+public Q_SLOTS:
-+ int updatesCount();
-+ void startCheckForUpdates();
-+
-+ // KAuth handler slots
-+ // update
-+ void handleKAuthUpdateHelperReply(KJob *job);
-+ void handleKAuthUpdateHelperProgress(KJob *job, unsigned long percent);
-+ // upgrade
-+ void handleKAuthUpgradeHelperReply(KJob *job);
-+ void handleKAuthUpgradeHelperProgress(KJob *job, unsigned long percent);
-+
-+ //void transactionRemoved(Transaction* t);
-+ //void cleanup();
-+
-+public:
-+ QVector<QtApk::ChangesetItem> &changes() { return m_upgradeable.changes(); }
-+ const QVector<QtApk::ChangesetItem> &changes() const { return m_upgradeable.changes(); }
-+
-+protected:
-+ void handleKAuthHelperError(KAuth::ExecuteJob *reply, const QVariantMap &replyData);
-+
-+private:
-+ AlpineApkBackend *const m_backend;
-+ int m_updatesCount = 0;
-+ QtApk::Changeset m_upgradeable;
-+ QSet<AbstractResource *> m_allUpdateable;
-+ QSet<AbstractResource *> m_markedToUpdate;
-+// void resourcesChanged(AbstractResource* res, const QVector<QByteArray>& props);
-+// void refreshUpdateable();
-+// void transactionAdded(Transaction* newTransaction);
-+// void transactionProgressChanged();
-+// void refreshProgress();
-+// QVector<Transaction*> transactions() const;
-+
-+// QSet<AbstractResource*> m_upgradeable;
-+// QSet<AbstractResource*> m_pendingResources;
-+ bool m_progressing = false;
-+ qreal m_upgradeProgress = 0.0;
-+// QDateTime m_lastUpdate;
-+// QTimer m_timer;
-+// bool m_canCancel = false;
-+};
-+
-+
-+#endif // ALPINEAPKUPDATER_H
-diff --git a/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
-new file mode 100644
-index 00000000..16587994
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.cpp
-@@ -0,0 +1,303 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#include <QDateTime>
-+#include <QDir>
-+#include <QEventLoop>
-+#include <QFile>
-+#include <QFileInfo>
-+#include <QJsonArray>
-+#include <QJsonDocument>
-+#include <QJsonParseError>
-+#include <QJsonObject>
-+#include <QNetworkAccessManager>
-+#include <QNetworkReply>
-+#include <QNetworkRequest>
-+#include <QStandardPaths>
-+#include <QThreadPool>
-+#include <QUrl>
-+#include <QtConcurrentRun>
-+
-+#include "AppstreamDataDownloader.h"
-+#include "alpineapk_backend_logging.h"
-+
-+namespace DiscoverVersion {
-+// contains static QLatin1String version("5.20.5"); definition
-+// autogenerated from top CMakeLists.txt
-+#include "../../../DiscoverVersion.h"
-+}
-+
-+#ifdef ALPINE_LINUX_BUILD
-+/**
-+ * @brief ApkAppstreamDataDownloader::getApkArch
-+ * Reads current configured system apk architecture
-+ * from "/etc/apk/arch" file.
-+ * @return "x86_64" / "armhf" / "armv7" / "aarch64" and so on
-+ */
-+static QString getApkArch()
-+{
-+ static QString s_retArch;
-+ if (!s_retArch.isEmpty()) {
-+ return s_retArch;
-+ }
-+ QFile archFile(QStringLiteral("/etc/apk/arch"));
-+ if (!archFile.open(QIODevice::ReadOnly)) {
-+ // TODO: we could try to guess at compile time by checking presence of
-+ // defines like __x86_64__ (check with: "gcc -march=native -dM -E - </dev/null")
-+ // but that seems like outside of scope of this small function
-+ return s_retArch;
-+ }
-+ s_retArch = QString::fromUtf8(archFile.readAll()).trimmed();
-+ archFile.close();
-+ return s_retArch;
-+}
-+
-+static inline void replaceCARCH(QString &in, const QString &arch)
-+{
-+ // URLs in JSON look like this:
-+ // https://appstream.alpinelinux.org/data/edge/main/Components-main-@CARCH@.xml.gz
-+ // we need to replace @CARCH@ with a real arch value
-+ in.replace(QLatin1String("@CARCH@"), arch);
-+}
-+#endif
-+
-+QString AppstreamDataDownloader::getAppStreamCacheDir()
-+{
-+ QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
-+ // ^^ "~/.cache/discover"
-+ cachePath += QStringLiteral("/appstream_data");
-+ QDir cacheDir(cachePath);
-+ if (!cacheDir.exists()) {
-+ const bool ok = cacheDir.mkpath(QStringLiteral("."));
-+ if (ok) {
-+ qCDebug(LOG_ALPINEAPK) << "Created appstream data cache dir:" << cachePath;
-+ } else {
-+ qCWarning(LOG_ALPINEAPK) << "Failed to create appstream data cache dir:" << cachePath;
-+ }
-+ }
-+ return cachePath;
-+}
-+
-+AppstreamDataDownloader::AppstreamDataDownloader(QObject *parent)
-+ : QObject(parent)
-+{
-+}
-+
-+void AppstreamDataDownloader::setCacheExpirePeriodSecs(qint64 secs)
-+{
-+ m_cacheExpireSeconds = secs;
-+}
-+
-+void AppstreamDataDownloader::loadUrlsJson(const QString &jsonPath)
-+{
-+ const QString jsonBaseName = QFileInfo(jsonPath).baseName();
-+ QFile jsonFile(jsonPath);
-+ if (!jsonFile.open(QIODevice::ReadOnly)) {
-+ qCWarning(LOG_ALPINEAPK) << "Failed to open JSON:" << jsonPath << "for reading!";
-+ Q_EMIT downloadFinished();
-+ return;
-+ }
-+ const QByteArray jsonBa = jsonFile.readAll();
-+ jsonFile.close();
-+
-+ QJsonParseError jsonError;
-+ const QJsonDocument jDoc = QJsonDocument::fromJson(jsonBa, &jsonError);
-+ if (jDoc.isNull()) {
-+ qCWarning(LOG_ALPINEAPK) << "Failed to parse JSON:" << jsonPath << "!";
-+ qCWarning(LOG_ALPINEAPK) << jsonError.errorString();
-+ Q_EMIT downloadFinished();
-+ return;
-+ }
-+ // JSON structure:
-+ // {
-+ // "urls": [
-+ // "https://...", "https://...", "https://..."
-+ // ]
-+ // }
-+ const QJsonObject rootObj = jDoc.object();
-+ const QJsonArray urls = rootObj.value(QLatin1String("urls")).toArray();
-+ for (const QJsonValue &urlValue : urls) {
-+ QString url = urlValue.toString();
-+#ifdef ALPINE_LINUX_BUILD
-+ replaceCARCH(url, getApkArch());
-+#endif
-+ m_urls.append(url);
-+ // prefixes are used to avoid name clashes with similar URL paths
-+ // from other JSON files. json file basename is used as prefix
-+ m_urlPrefixes.insert(url, jsonBaseName);
-+ }
-+}
-+
-+QString AppstreamDataDownloader::getLocalFileSavePath(const QUrl &urlToDownload)
-+{
-+ // we are adding a prefix here to local file name to avoid possible
-+ // file name clashes with files from other JSONs
-+ const QString urlPrefix = m_urlPrefixes.value(urlToDownload.toString(), QString());
-+ const QFileInfo urlInfo(urlToDownload.path());
-+ const QString localCacheFile = AppstreamDataDownloader::getAppStreamCacheDir()
-+ + QDir::separator()
-+ + urlPrefix + QLatin1Char('_')
-+ + urlInfo.fileName();
-+ // aka "~/.cache/discover/appstream_data/urlPrefix_fileName.xml.gz"
-+ return localCacheFile;
-+}
-+
-+void AppstreamDataDownloader::start()
-+{
-+ m_urls.clear();
-+ // load json files with appdata URLs configuration
-+ const QString path = QStandardPaths::locate(
-+ QStandardPaths::GenericDataLocation,
-+ QLatin1String("libdiscover/external-appstream-urls"),
-+ QStandardPaths::LocateDirectory);
-+ if (path.isEmpty()) {
-+ qCWarning(LOG_ALPINEAPK) << "external-appstream-urls directory does not exist.";
-+ return;
-+ }
-+
-+ QDir jsonsDir(path);
-+ // search for all JSON files in that directory and load each one
-+ QFileInfoList fileList = jsonsDir.entryInfoList({QStringLiteral("*.json")}, QDir::Files);
-+ for (const QFileInfo &fi : fileList) {
-+ qCDebug(LOG_ALPINEAPK) << " reading URLs JSON: " << fi.absoluteFilePath();
-+ loadUrlsJson(fi.absoluteFilePath());
-+ }
-+
-+ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: urls:" << m_urls;
-+
-+ // check if download is needed at all, maybe all files are already up to date?
-+
-+ getAppStreamCacheDir(); // can create a cache dir if not exists
-+
-+ const QDateTime dtNow = QDateTime::currentDateTime();
-+ m_urlsToDownload.clear();
-+ for (const QString &url : m_urls) {
-+ const QUrl urlToDownload(url, QUrl::TolerantMode);
-+ const QString localCacheFile = getLocalFileSavePath(urlToDownload);
-+ const QFileInfo localFi(localCacheFile);
-+ if (localFi.exists()) {
-+ int modifiedSecsAgo = localFi.lastModified().secsTo(dtNow);
-+ if (modifiedSecsAgo >= m_cacheExpireSeconds) {
-+ m_urlsToDownload.append(url);
-+ }
-+ qCDebug(LOG_ALPINEAPK) << " appstream metadata file: " << localFi.fileName()
-+ << " was last modified " << modifiedSecsAgo << " seconds ago";
-+ } else {
-+ // locally downloaded file does not even exist, we need to download it
-+ m_urlsToDownload.append(url);
-+ qCDebug(LOG_ALPINEAPK) << " appstream metadata file: " << localFi.fileName()
-+ << " does not exist, queued for downloading";
-+ }
-+ }
-+
-+ if (m_urlsToDownload.size() > 0) {
-+ // some files are outdated; download is needed
-+ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: We will need to download "
-+ << m_urlsToDownload.size() << " file(s)";
-+
-+ // start downloader in a background thread
-+ QFuture<void> downloaderFuture = QtConcurrent::run(
-+ QThreadPool::globalInstance(), this, &AppstreamDataDownloader::download);
-+
-+ // directly connect signal to signal
-+ QObject::connect(&m_voidFutureWatcher, &QFutureWatcher<void>::finished,
-+ this, &AppstreamDataDownloader::downloadFinished);
-+ m_voidFutureWatcher.setFuture(downloaderFuture);
-+ } else {
-+ // no need to download anything
-+ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: All appstream data files "
-+ "are up to date, not downloading anything";
-+ Q_EMIT downloadFinished();
-+ return;
-+ }
-+}
-+
-+// this function runs in background thread
-+void AppstreamDataDownloader::download()
-+{
-+ QNetworkAccessManager nam;
-+ QList<QNetworkReply *> replies;
-+ QEventLoop loop;
-+
-+ // start a HTTP GET request for each URL
-+ const QStringList urls = m_urlsToDownload;
-+ const QString discoverVersion(QStringLiteral("plasma-discover %1").arg(DiscoverVersion::version));
-+ for (const QString &url : urls) {
-+ const QUrl uurl(url, QUrl::TolerantMode);
-+ QNetworkRequest req(uurl);
-+ req.setHeader(QNetworkRequest::UserAgentHeader, discoverVersion);
-+ replies.push_back(nam.get(req));
-+ }
-+
-+ for (QNetworkReply *rep : replies) {
-+ // lambda that stops the loop when all requests have finished
-+ // intentionaly use contextless lambda, it is not called otherwise
-+ QObject::connect(rep, &QNetworkReply::finished, [&loop, &replies, rep] () {
-+ const int numReplies = replies.size();
-+ int numFinished = 0;
-+ for (QNetworkReply *arep : replies) {
-+ if (arep->isFinished()) {
-+ numFinished++;
-+ }
-+ }
-+ if (numFinished >= numReplies) {
-+ loop.quit();
-+ }
-+ qCDebug(LOG_ALPINEAPK).nospace()
-+ << "appstream_downloader: " << rep->url()
-+ << " request finished (" << numFinished << "/" << numReplies << ")";
-+ });
-+ }
-+
-+ // wait for all requests to finish
-+ loop.exec();
-+
-+ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: all downloads have finished!";
-+
-+ int numErrors = 0;
-+ for (QNetworkReply *rep : replies) {
-+ const QString localCacheFile = getLocalFileSavePath(rep->url());
-+
-+ if (rep->error() == QNetworkReply::NoError) {
-+ // read received reply contents and save it to file
-+ const QByteArray data = rep->readAll();
-+ QFile fout(localCacheFile);
-+ if (fout.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
-+ fout.write(data);
-+ fout.close();
-+ m_cacheWasUpdated = true;
-+ qCDebug(LOG_ALPINEAPK) << "appstream_downloader: saved: " << localCacheFile;
-+ } else {
-+ qCWarning(LOG_ALPINEAPK) << "appstream_downloader: failed to save:" << localCacheFile;
-+ }
-+ } else {
-+ // download failed for some reason
-+ QFileInfo urlinfo(rep->url().path());
-+ qCWarning(LOG_ALPINEAPK) << "appstream_downloader: failed to download"
-+ << urlinfo.fileName() << rep->errorString();
-+ numErrors++;
-+ }
-+ }
-+
-+ // cleanup: delete all replies objects
-+ for (QNetworkReply *arep : replies) {
-+ arep->deleteLater();
-+ }
-+}
-diff --git a/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
-new file mode 100644
-index 00000000..05771982
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/AppstreamDataDownloader.h
-@@ -0,0 +1,139 @@
-+/***************************************************************************
-+ * Copyright © 2020 Alexey Min <alexey.min@gmail.com> *
-+ * *
-+ * 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 <http://www.gnu.org/licenses/>. *
-+ ***************************************************************************/
-+
-+#ifndef AlpineAppstreamDataDownloader_H
-+#define AlpineAppstreamDataDownloader_H
-+
-+#include <QFutureWatcher>
-+#include <QHash>
-+#include <QList>
-+#include <QObject>
-+#include <QString>
-+#include <QUrl>
-+
-+/**
-+ * @brief The AppstreamDataDownloader class
-+ *
-+ * @details The job of this class is to download appstream data
-+ * gzipped XMLs from the Web server hosted somewhere (for
-+ * Alpine Linux - at https://appstream.alpinelinux.org).
-+ *
-+ * Some distros (for example, Alpine Linux) do not provide
-+ * appstream data as installable package, instead they host it
-+ * on the internet and you have to download and install them
-+ * manually.
-+ *
-+ * Logic behind this decision is beyond my understanding, but we
-+ * have what we have... Those files are not very large (from few
-+ * kilobytes to couple of megabytes) but we still need them.
-+ *
-+ * URLs to download archives from are stored in JSON files in
-+ * /usr/share/libdiscover/external-appstream-urls/ directory
-+ * (QStandardPaths::GenericDataLocation/libdiscover/external-appstream-urls
-+ * from C++/Qt code, ${DATA_INSTALL_DIR}/libdiscover/external-appstream-urls
-+ * from cmake).
-+ *
-+ * JSON file format:
-+ * ---------------------
-+ * {
-+ * "urls": [ "https://url1", "https://url2", ... ]
-+ * }
-+ * ---------------------
-+ *
-+ * This class can load any amount of those JSON files,
-+ * fetch URLs from them and download all files pointed by
-+ * those URLs to discover's cache directory:
-+ * "~/.cache/discover/appstream_data" (aka QStandardPaths::CacheLocation).
-+ * If files are already present in cache and not outdated,
-+ * they are not downloaded again. Default cache expiration
-+ * time is 2 days.
-+ */
-+class AppstreamDataDownloader: public QObject
-+{
-+ Q_OBJECT
-+public:
-+ explicit AppstreamDataDownloader(QObject *parent = nullptr);
-+
-+ /**
-+ * @brief getAppstreamCacheDir
-+ * @details Use return value of this function to add extra metadata
-+ * directories to AppStream loader, in case of AppStreamQt:
-+ * AppStream::Pool::addMetadataLocation().
-+ * This method creates a cache dir if it does not exist.
-+ * @return directory where downloaded files are stored.
-+ */
-+ static QString getAppStreamCacheDir();
-+
-+ /**
-+ * @brief cacheWasUpdated
-+ * @details Call this after receiving downloadFinished() signal to
-+ * test if there actually was something new downloaded.
-+ *
-+ * @return true, if new files were actually downloaded, or
-+ * false is files already present in cache are up to date.
-+ */
-+ bool cacheWasUpdated() const { return m_cacheWasUpdated; }
-+
-+ /**
-+ * @brief getCacheExpirePeriodSecs
-+ * @return cache expire timeout in seconds
-+ */
-+ qint64 getCacheExpirePeriodSecs() const { return m_cacheExpireSeconds; }
-+
-+ /**
-+ * @brief setCacheExpirePeriodSecs
-+ * @param secs - new cache expiration timeout, in seconds.
-+ */
-+ void setCacheExpirePeriodSecs(qint64 secs);
-+
-+public Q_SLOTS:
-+ /**
-+ * @brief start
-+ * Start the background thread that does all the job.
-+ * downloadFinished() signal will be emitted when everything is done.
-+ * start() may finish immediately if all cached files are
-+ * up to date and no downloads are needed.
-+ */
-+ void start();
-+
-+Q_SIGNALS:
-+ /**
-+ * @brief downloadFinished
-+ * This signal is emitted when download job is finished.
-+ * To check if there were actual downloads performed, call
-+ * cacheWasUpdated().
-+ */
-+ void downloadFinished();
-+
-+private:
-+ QString getLocalFileSavePath(const QUrl &urlTodownload);
-+ void loadUrlsJson(const QString &path);
-+ void download();
-+
-+protected:
-+ qint64 m_cacheExpireSeconds = 2 * 24 * 3600; // 2 days
-+ QStringList m_urls;
-+ QStringList m_urlsToDownload;
-+ QHash<QString, QString> m_urlPrefixes;
-+ QFutureWatcher<void> m_voidFutureWatcher;
-+ bool m_cacheWasUpdated = false;
-+};
-+
-+#endif
-diff --git a/libdiscover/backends/AlpineApkBackend/CMakeLists.txt b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
-new file mode 100644
-index 00000000..8602dce7
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/CMakeLists.txt
-@@ -0,0 +1,85 @@
-+find_package(KF5Auth CONFIG REQUIRED) # Probably should be moved to top CMakeLists
-+
-+set(alpineapkbackend_SRCS
-+ AlpineApkAuthActionFactory.h
-+ AlpineApkAuthActionFactory.cpp
-+ AlpineApkBackend.cpp
-+ AlpineApkBackend.h
-+ AlpineApkResource.cpp
-+ AlpineApkResource.h
-+ AlpineApkReviewsBackend.cpp
-+ AlpineApkReviewsBackend.h
-+ AlpineApkSourcesBackend.cpp
-+ AlpineApkSourcesBackend.h
-+ AlpineApkUpdater.cpp
-+ AlpineApkUpdater.h
-+ AlpineApkTransaction.cpp
-+ AlpineApkTransaction.h
-+ AppstreamDataDownloader.h
-+ AppstreamDataDownloader.cpp
-+)
-+
-+ecm_qt_declare_logging_category(
-+ alpineapkbackend_SRCS # sources_var
-+ HEADER alpineapk_backend_logging.h
-+ IDENTIFIER LOG_ALPINEAPK
-+ CATEGORY_NAME org.kde.plasma.discover.alpineapk
-+ DEFAULT_SEVERITY Debug
-+)
-+
-+add_library(
-+ alpineapk-backend
-+ MODULE
-+ ${alpineapkbackend_SRCS}
-+)
-+
-+target_link_libraries(
-+ alpineapk-backend
-+ PRIVATE
-+ Qt5::Core
-+ Qt5::Widgets
-+ Qt5::Concurrent
-+ KF5::CoreAddons
-+ KF5::ConfigCore
-+ KF5::AuthCore
-+ Discover::Common
-+ ApkQt::ApkQt
-+ AppStreamQt
-+)
-+
-+# KAuth helper exe
-+add_executable(alpineapk_kauth_helper
-+ AlpineApkAuthHelper.cpp
-+ AlpineApkAuthHelper.h
-+ org.kde.discover.alpineapkbackend.actions
-+)
-+set_source_files_properties(
-+ org.kde.discover.alpineapkbackend.actions
-+ PROPERTIES HEADER_FILE_ONLY ON
-+)
-+target_link_libraries(alpineapk_kauth_helper
-+ Qt5::Core
-+ KF5::AuthCore
-+ ApkQt::ApkQt
-+)
-+
-+kauth_install_actions(org.kde.discover.alpineapkbackend org.kde.discover.alpineapkbackend.actions)
-+kauth_install_helper_files(alpineapk_kauth_helper org.kde.discover.alpineapkbackend root)
-+
-+install(
-+ TARGETS alpineapk-backend
-+ DESTINATION ${PLUGIN_INSTALL_DIR}/discover
-+)
-+
-+install(
-+ TARGETS alpineapk_kauth_helper
-+ DESTINATION ${KAUTH_HELPER_INSTALL_DIR}
-+)
-+
-+# add_library(AlpineApkNotifier MODULE AlpineApkNotifier.cpp)
-+
-+# target_link_libraries(AlpineApkNotifier Discover::Notifiers)
-+
-+# set_target_properties(AlpineApkNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
-+
-+# install(TARGETS AlpineApkNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier)
-diff --git a/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
-new file mode 100644
-index 00000000..c9bb5f9f
---- /dev/null
-+++ b/libdiscover/backends/AlpineApkBackend/org.kde.discover.alpineapkbackend.actions
-@@ -0,0 +1,5 @@
-+[org.kde.discover.alpineapkbackend.pkgmgmt]
-+Name=Package management
-+Description=Install or remove packages, upgrade system
-+Policy=auth_admin
-+Persistence=session
-diff --git a/libdiscover/backends/CMakeLists.txt b/libdiscover/backends/CMakeLists.txt
-index 5f87f639..18947339 100644
---- a/libdiscover/backends/CMakeLists.txt
-+++ b/libdiscover/backends/CMakeLists.txt
-@@ -45,4 +45,14 @@ if(BUILD_FwupdBackend AND TARGET PkgConfig::Fwupd)
- add_subdirectory(FwupdBackend)
- endif()
-
-+find_package(ApkQt CONFIG)
-+set_package_properties(ApkQt PROPERTIES
-+ DESCRIPTION "C++/Qt interface library for Alpine package keeper"
-+ URL "https://www.alpinelinux.org"
-+ PURPOSE "Required to build the Alpine APK backend"
-+ TYPE OPTIONAL)
-
-+option(BUILD_AlpineApkBackend "Build Alpine APK support." "ON")
-+if(BUILD_AlpineApkBackend AND ApkQt_FOUND)
-+ add_subdirectory(AlpineApkBackend)
-+endif()
-
diff --git a/community/discover/APKBUILD b/community/discover/APKBUILD
index 3e0849164a..36f48d0562 100644
--- a/community/discover/APKBUILD
+++ b/community/discover/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=discover
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
# armhf blocked by qt5-qtdeclarative
# s390x blocked by flatpak
@@ -47,9 +47,9 @@ case "$pkgver" in
esac
source="
https://download.kde.org/$_rel/plasma/$pkgver/discover-$pkgver.tar.xz
- 0001-port-away-from-qaction.patch
- 0002-Add-support-for-Alpine-Linux-apk-backend.patch
+ 0001-Add-support-for-Alpine-Linux-apk-backend.patch
alpine-appstream-data.json
+ alpine-linux-logo-icon.png
"
subpackages="
$pkgname-backend-apk:backend_apk
@@ -139,6 +139,9 @@ backend_apk() {
sed -i "s/@CARCH@/$CARCH/g" "$srcdir"/alpine-appstream-data.json
install -Dm644 "$srcdir"/alpine-appstream-data.json \
"$subpkgdir"/usr/share/libdiscover/external-appstream-urls/alpine-appstream-data.json
+
+ install -Dm644 -t "$subpkgdir"/usr/share/icons/hicolor/32x32/apps/ \
+ "$srcdir"/alpine-linux-logo-icon.png
}
backend_fwupd() {
@@ -154,8 +157,8 @@ backend_fwupd() {
}
sha512sums="
-933a16dd5f99c18e61fdf77330a78e3c5251d13ce6aa99daa4f7b939320a3ddde2c66ead85e1bc31e7719c0cf657ef46516f35f08863cd6f2be25efcbf156fbf discover-5.21.5.tar.xz
-60537172868d4922266632d1821a0a971fcab572a37a388f06801537b6c4c7ae63cb19443c2e2be8e76acb8fd2811c005a2c625f645f93fdf8cbedaa1afd051d 0001-port-away-from-qaction.patch
-2d7fa6c1540c789d32312d0c61bd758ddd54b749a85a046659f31f63fb75fe0f27e324b4d32ffc8309c2667f3ae195feaa7f8595d301e2889930a19693324433 0002-Add-support-for-Alpine-Linux-apk-backend.patch
+0612ef86b1a49ea06da9a89e5ef537d34d80ad3c9b748d1e91e6e574670eb545094eb52e858747567c82b0d13a064fb27a8378d1eda3059fb1e1958ad30df945 discover-5.22.0.tar.xz
+40019b59c8ad5523ccf64313bf03efb354e8c81074733e62b6a778a3c0dbb699f5d6ba3ee8c950b484fee40bf21c82a5608aa2b0e30bad989fdcf557aa68b583 0001-Add-support-for-Alpine-Linux-apk-backend.patch
f9f73888f8e20b317987da55028bd578de854fb34293466d1bc5deb82e32dc164916f724411de64a42235ecda37205d3f1ba26621ed7ab710b94273acce34aa3 alpine-appstream-data.json
+0766668630cb14f58c840ebdda8b76bd1dec98b4dd61469677d4ad6eb4966809a25dbec2a846502cad0391df5b6ca9d692bb857a6fa203b61a52e6ac044cfbf4 alpine-linux-logo-icon.png
"
diff --git a/community/discover/alpine-linux-logo-icon.png b/community/discover/alpine-linux-logo-icon.png
new file mode 100644
index 0000000000..470c4430d0
--- /dev/null
+++ b/community/discover/alpine-linux-logo-icon.png
Binary files differ
diff --git a/community/drkonqi/APKBUILD b/community/drkonqi/APKBUILD
index 3cb696b4cb..25ad4ae8d1 100644
--- a/community/drkonqi/APKBUILD
+++ b/community/drkonqi/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=drkonqi
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="The KDE crash handler"
# armhf blocked by extra-cmake-modules
@@ -56,5 +56,5 @@ package() {
}
sha512sums="
-ce24e52cff81cf2b9cf685dcce183cdce99110a1469d156726ca5c64e64dee6f064fe4750729619b6b455bc4e28792f92ec305c0f957f994c4e5a0aaa337f649 drkonqi-5.21.5.tar.xz
+4b4fb1277584f5200a08c866c74d7caafcb72af2527504cf569558a2ee9ae0ded6fd43020006936b0e969b8a3649a007866865d142da939407c0a34e2ef37796 drkonqi-5.22.0.tar.xz
"
diff --git a/community/kactivitymanagerd/APKBUILD b/community/kactivitymanagerd/APKBUILD
index 808c64cc02..d32723018a 100644
--- a/community/kactivitymanagerd/APKBUILD
+++ b/community/kactivitymanagerd/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kactivitymanagerd
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="System service to manage user's activities and track the usage patterns"
# armhf blocked by qt5-qtdeclarative
@@ -51,5 +51,5 @@ package() {
rm -r "$pkgdir"/usr/lib/systemd
}
sha512sums="
-097c18f39d5024a05e645e7dcf82ad39df07afb0ad0b010410699e3fa1261002f05553dbc049af63e7882c305d4c2c20d5032e9448354de7c22d9b9371a8c633 kactivitymanagerd-5.21.5.tar.xz
+5f5e2ed9207a7a427f10cd0ee0e9083d56a742c0e3b93d30dd7df55db7da3609479c48261b020d7ac1178c60dfbdc0ddda6b518296f5386264b3a83be6d47396 kactivitymanagerd-5.22.0.tar.xz
"
diff --git a/community/kde-cli-tools/APKBUILD b/community/kde-cli-tools/APKBUILD
index 7b3e9ac063..8b1b3b7629 100644
--- a/community/kde-cli-tools/APKBUILD
+++ b/community/kde-cli-tools/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kde-cli-tools
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Tools based on KDE Frameworks 5 to better interact with the system"
# armhf blocked by extra-cmake-modules
@@ -58,5 +58,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-20859f88044c6af84ba4b666b803a6a3a4fbe33748495d9c186fc0739e7b5464b234166cef4d03f0dd44540919eca4f3ea8a1f5ac7b54d480cc9dadeaa0ec3fc kde-cli-tools-5.21.5.tar.xz
+6905a29e4700c045de1e04975d001835c1cc26a6999cfd20459d0a8ff0123974bd0c5fd7931de3ac7355420a294f066479f2056d56ae4d2280daa5be753a9fe3 kde-cli-tools-5.22.0.tar.xz
"
diff --git a/community/kde-gtk-config/APKBUILD b/community/kde-gtk-config/APKBUILD
index 62aea55cd9..851733a1e0 100644
--- a/community/kde-gtk-config/APKBUILD
+++ b/community/kde-gtk-config/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kde-gtk-config
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="GTK2 and GTK3 Configurator for KDE"
# armhf blocked by qt5-qtdeclarative
@@ -51,5 +51,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-7acd5036cc008fac85cabd9f3a5321dc1c0209a1133d6063342ca1b1235843dce5e37a7712e0cbbd520f09b3ff46b71a94517c61414c8e7c1c35a2d65b428347 kde-gtk-config-5.21.5.tar.xz
+8e0b41523dbfab3677065354c1cbc60b81dee081ea893331fd5f473da21b8db04313bdac793a354d3b0ee895f9e4996a39556932d99e4919fb6fc0aaef9ef233 kde-gtk-config-5.22.0.tar.xz
"
diff --git a/community/kdecoration/APKBUILD b/community/kdecoration/APKBUILD
index fbfb60ec42..dcb10fce29 100644
--- a/community/kdecoration/APKBUILD
+++ b/community/kdecoration/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kdecoration
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plugin based library to create window decorations"
arch="all !armhf"
@@ -40,5 +40,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-ef86f9946cad9de9a5e3767e2dce4f30436c9fb013bf73d5ca31cb852d3dae92cd3906adcb178070f74922f3776d226b8f812ea24eb82a57662c0bf675d38edb kdecoration-5.21.5.tar.xz
+435e90dc984bb7bf1e94672e6f56455f9265a0baf7dcca0da4655c88e008f4e1eb788b106edb5fce8153e6239c1d6d924c60e4804e5c02ef843e1201e432963b kdecoration-5.22.0.tar.xz
"
diff --git a/community/kdeplasma-addons/APKBUILD b/community/kdeplasma-addons/APKBUILD
index 4737f30f58..dc696e4a9e 100644
--- a/community/kdeplasma-addons/APKBUILD
+++ b/community/kdeplasma-addons/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kdeplasma-addons
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="All kind of addons to improve your Plasma experience"
# mips, ppc64le and s390x blocked by qt5-qtwebengine
@@ -63,5 +63,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-7c7afebd726e400c859be7051c14dc2ade658df7995b02f43595be5394f8d79322c7a7edeff6761e3980bc20aaedfc49d063308c4ee9974a400b8f855a89efe1 kdeplasma-addons-5.21.5.tar.xz
+027b54c290011ad0b031515a744ce7ad1b18734054f892c119eddce744078b838583b6125014419c05dea3ba97207b095a487ede0d3e03e2df34d16fe189a543 kdeplasma-addons-5.22.0.tar.xz
"
diff --git a/community/kgamma5/APKBUILD b/community/kgamma5/APKBUILD
index b8e3cb9819..a3b682d101 100644
--- a/community/kgamma5/APKBUILD
+++ b/community/kgamma5/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kgamma5
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Adjust your monitor's gamma settings"
# armhf blocked by extra-cmake-modules
@@ -43,5 +43,5 @@ package() {
}
sha512sums="
-31da4b0e4aceeffaa17b9d9c13a12db8a7009575e92977f5261fea4b578cc8cca1a8f159a4229a7995cac2beb76583c125448a346ac1cdfdbb774bf76b2e34ac kgamma5-5.21.5.tar.xz
+45ad2eebb17b92d32d968cc7abd700b43439ac37e46cbf5a536d323a405325d4030882ce63dddbc7781c1409ae484489f17eaa21faad49fdb809c31d49b12118 kgamma5-5.22.0.tar.xz
"
diff --git a/community/khotkeys/APKBUILD b/community/khotkeys/APKBUILD
index fb9d427cfb..c9e5ea5de9 100644
--- a/community/khotkeys/APKBUILD
+++ b/community/khotkeys/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=khotkeys
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
# armhf blocked by extra-cmake-modules
# s390x blocked by libksysguard
@@ -51,5 +51,5 @@ package() {
}
sha512sums="
-16409a928af050278dda012150e969b60617a66aa000cbb722781788defb02268ac56ef521916e55f257e4b166df8ddffcf14c9c35b49410a79491f197b4aef5 khotkeys-5.21.5.tar.xz
+722e6d67cd1c131c1ee56dd348e4305ae6d8a408ddbaf188bcbccbb5107a6c9e64cf93605165613b2830cfbeb5d0396c9987901bf029fa36f63a47d60b28ae24 khotkeys-5.22.0.tar.xz
"
diff --git a/community/kinfocenter/APKBUILD b/community/kinfocenter/APKBUILD
index a37201e666..737db7654a 100644
--- a/community/kinfocenter/APKBUILD
+++ b/community/kinfocenter/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kinfocenter
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="A utility that provides information about a computer system"
# armhf blocked by extra-cmake-modules
@@ -60,5 +60,5 @@ package() {
}
sha512sums="
-2becdd6f6be00de309467fee1478bec75bc6fa7d8d233b0a5bc8e6124f780add705d549209c842c3cd621bc8931a8ecdecb5a2902ab369bcd476939580bec41a kinfocenter-5.21.5.tar.xz
+ae5754d8c0054b35f78c14e54177480df9271dfcea80d72ecf8da7d89ea5dc9ff57964d88ab64cc15811cf4a15ffe0e5a7b4e186c5a1c1385167d1bc0653fdf1 kinfocenter-5.22.0.tar.xz
"
diff --git a/community/kmenuedit/APKBUILD b/community/kmenuedit/APKBUILD
index f366c11c84..13825b982e 100644
--- a/community/kmenuedit/APKBUILD
+++ b/community/kmenuedit/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kmenuedit
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE menu editor"
# armhf blocked by qt5-qtdeclarative
@@ -49,5 +49,5 @@ package() {
}
sha512sums="
-a375574ac41017b033bde10d7f2bcad980b5c0758855c9787518e1f9a5185b0dbdca775f45593144fe462fe52d01001ba72e775b2b728f7a39087ffeeaae8919 kmenuedit-5.21.5.tar.xz
+005982c2deeec0624e90d590253e5df42bfa86c8550a203ae6ad06160449ffe47fdd11b42c2602f54ebcd9cc10a936793cba55cab768da6c8b27462475b7641d kmenuedit-5.22.0.tar.xz
"
diff --git a/community/kscreen/APKBUILD b/community/kscreen/APKBUILD
index c4926e7a3e..9a27b45913 100644
--- a/community/kscreen/APKBUILD
+++ b/community/kscreen/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kscreen
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE's screen management software"
# armhf blocked by qt5-qtdeclarative
@@ -55,5 +55,5 @@ package() {
}
sha512sums="
-d57a561b22aed2301574e4af37d45046f4fbd360f8eae3465303c29a490f9dcc33a93c7e38890bd2945a4d868c23719a0ac11cfcdd22e613fc8612fa53694784 kscreen-5.21.5.tar.xz
+00212bed49d079f5c318f374604a03e8dcdc128638666b3d5486230494e8fa980e279fda9380f3b0d818e3f1d0878dbe792b84a3a06f8ef1a2dcf9ff775731c9 kscreen-5.22.0.tar.xz
"
diff --git a/community/kscreenlocker/APKBUILD b/community/kscreenlocker/APKBUILD
index 41b3aeba49..e0f23e7764 100644
--- a/community/kscreenlocker/APKBUILD
+++ b/community/kscreenlocker/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kscreenlocker
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Library and components for secure lock screen architecture"
# armhf blocked by extra-cmake-modules
@@ -26,6 +26,7 @@ depends_dev="
kwayland-dev
kwindowsystem-dev
kxmlgui-dev
+ layer-shell-qt-dev
libseccomp-dev
linux-pam-dev
qt5-qtbase-dev
@@ -70,7 +71,7 @@ package() {
}
sha512sums="
-0ed1f1399d493f39562c3655e4f541b65c25beaeac77315af34e49f79f7dfd954099b7ab91f2eab8b1d2f5dc3be8f0a54c7d53d902bc31971bffa6b94476c8af kscreenlocker-5.21.5.tar.xz
+8a9be9993d889ea47a8ec3f77c47e19d1cb44ee390d1c2ad702260a633fa60d01637a11a36de642cf37aefc190d1dfd2a8c614dee87cb8e8837f0144fbc56d42 kscreenlocker-5.22.0.tar.xz
56e87d02d75c4a8cc4ed183faed416fb4972e7f223b8759959c0f5da32e11e657907a1df279d62a44a6a174f5aca8b2ac66a5f3325c5deb92011bcf71eed74c3 kde.pam
565265485dd7466b77966d75a56766216b8bcc187c95a997e531e9481cf50ddbe576071eb0e334421202bcab19aa6de6b93e042447ca4797a24bf97e1d053ffd kde-np.pam
"
diff --git a/community/ksshaskpass/APKBUILD b/community/ksshaskpass/APKBUILD
index a1db9ff73f..76b1c21ec0 100644
--- a/community/ksshaskpass/APKBUILD
+++ b/community/ksshaskpass/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=ksshaskpass
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="ssh-add helper that uses kwallet and kpassworddialog"
# armhf blocked by qt5-qtdeclarative
@@ -44,5 +44,5 @@ package() {
}
sha512sums="
-c342723383d13b8df977bb4ab3c6b50c15007d12fdd27c0e51ad41cf77bbc5ddae404a9fe10669ea22933140a55bc293e7296d8e3e488cbce36158475403d7ff ksshaskpass-5.21.5.tar.xz
+918c098c877fc48e6809e498e81b0ab1968371f58f1f89af65a45fb817829f8d44666c1fee4b84cae8a1f114b59698e48da683bc452151db57df0f2e4063fb3c ksshaskpass-5.22.0.tar.xz
"
diff --git a/community/ksysguard/APKBUILD b/community/ksysguard/APKBUILD
index fd574e6fb3..61e673025b 100644
--- a/community/ksysguard/APKBUILD
+++ b/community/ksysguard/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=ksysguard
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Track and control the processes running in your system"
# armhf blocked by extra-cmake-modules
@@ -31,11 +31,7 @@ makedepends="
networkmanager-qt-dev
"
-case "$pkgver" in
- *.90*) _rel=unstable;;
- *) _rel=stable;;
-esac
-source="https://download.kde.org/$_rel/plasma/$pkgver/ksysguard-$pkgver.tar.xz"
+source="https://download.kde.org/stable/ksysguard/$pkgver/ksysguard-$pkgver.tar.xz"
subpackages="$pkgname-doc $pkgname-lang"
build() {
@@ -56,5 +52,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-53e41ae1fb14d8e3393671e2d42ab7304b144f8938c821402c16c66aa09917e3dfc8ce766df43f1bb9c09e2934a6dcd48a4a4781d379026164b278aaed95858e ksysguard-5.21.5.tar.xz
+07314366506d029206e6fba934eefc8d338e52ab0f8f79747c189a9f856e13f32c7b156bd49ccf1ab2bef5c37fb05924022606bae6bbf2eb4628043d7c2579a2 ksysguard-5.22.0.tar.xz
"
diff --git a/community/kwallet-pam/APKBUILD b/community/kwallet-pam/APKBUILD
index e08bec3732..a9e42929d5 100644
--- a/community/kwallet-pam/APKBUILD
+++ b/community/kwallet-pam/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kwallet-pam
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KWallet PAM integration"
# armhf blocked by extra-cmake-modules
@@ -37,5 +37,5 @@ package() {
}
sha512sums="
-4dab0ac4500c6ec01fef71e2197e59afb39c318dfe1e5727832fed2137b5d961bb1aad9c52d356346794ac1dd9e9451f507032aab29028806d9c04c3c566738d kwallet-pam-5.21.5.tar.xz
+4f82b59c28879df3113c8d67171a50cb4c76728413c242749e5cce92d896f2139436263d07672fc4f2e98d457a7755a9901ddd3257a6c21ac088d24fec0b2026 kwallet-pam-5.22.0.tar.xz
"
diff --git a/community/kwayland-integration/APKBUILD b/community/kwayland-integration/APKBUILD
index 688e012368..280a0773db 100644
--- a/community/kwayland-integration/APKBUILD
+++ b/community/kwayland-integration/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bhushan Shah <bshah@kde.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kwayland-integration
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KWayland integration"
url="https://kde.org/plasma-desktop/"
@@ -37,5 +37,5 @@ package() {
}
sha512sums="
-87e1d71f8a18ebbaadfb76a7e2bdfb47933a500d3cccb589f697e2b368302f3b8c61a0e742cd436119adb3a885aa6d94b8d522befe2908fb2b3816def93bb282 kwayland-integration-5.21.5.tar.xz
+337ec281dd0ce7791d93af123ef1e5bd476328c91edc28df514c87c714b27d0f6a739b55f11e1dbf3ce0f6826ab437f404639b116644234086b855b748ff71d9 kwayland-integration-5.22.0.tar.xz
"
diff --git a/community/kwayland-server/APKBUILD b/community/kwayland-server/APKBUILD
index 73e4f6a2f6..d0d926f743 100644
--- a/community/kwayland-server/APKBUILD
+++ b/community/kwayland-server/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kwayland-server
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Wayland Server Components built on KDE Frameworks"
arch="all !armhf" # armhf blocked by qt5-qtdeclarative
@@ -41,5 +41,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-ff02ee6b1fb7cd193b49a4cbcf98a6b3ab6262e9da4211d91b764d8aaaa858c17863fccee19b649d2966c3a2721ca3b7c55f8a660a91d9eb56b1eec11d0766f5 kwayland-server-5.21.5.tar.xz
+aea5aa7ffecb1c085f733aaf5d4f16fa01c11264ab1e3717c1fed51f9937c3a99cf505664021749c8b203ae1c6b804d856efd9389ec7b26bc90887b3f57aba59 kwayland-server-5.22.0.tar.xz
"
diff --git a/community/kwin/APKBUILD b/community/kwin/APKBUILD
index eea0b3ae3f..702c2d71c9 100644
--- a/community/kwin/APKBUILD
+++ b/community/kwin/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kwin
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="An easy to use, but flexible, composited Window Manager"
# armhf blocked by qt5-qtdeclarative
@@ -107,5 +107,5 @@ package() {
setcap -r "$pkgdir"/usr/bin/kwin_wayland
}
sha512sums="
-b749892b12b027b306836bbd7b770602bff80c0ce6840db3345585a1215b415fe24fb823bbcb399f76999232f100bb2e1898d460160e1a762ea10fd24d6cf63c kwin-5.21.5.tar.xz
+eccd6d674a6ab7820de8f7765b25b55e8554ea5ddc21af90f189789856a047dc4468ced4a897b2e94c084ebb7e85f114e75cb7e63253497d36c76c2b228eb471 kwin-5.22.0.tar.xz
"
diff --git a/community/kwrited/APKBUILD b/community/kwrited/APKBUILD
index 8e0ed18538..4935d1ec8c 100644
--- a/community/kwrited/APKBUILD
+++ b/community/kwrited/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=kwrited
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE daemon listening for wall and write messages"
arch="all !armhf" # qt5-qtdeclarative-dev unavilable on armhf
@@ -37,5 +37,5 @@ package() {
}
sha512sums="
-b187763675cfa386ef51ff6f5bb59458d799c2010d93fd35ad0fa32c1d0898d35927eb77d82dcdfa079bbd1c8c889823cc802742016d767618e6e9e25ac417fe kwrited-5.21.5.tar.xz
+810d79d7a916339a3bf4014863af5f6f0884cdfdecb9768d5daefee53fb19c7ef3897c838feef085e26b8dfa80fba80b14c41e9f337c623e5dd86c2de89ef297 kwrited-5.22.0.tar.xz
"
diff --git a/community/libkscreen/APKBUILD b/community/libkscreen/APKBUILD
index 8a9316728c..36c4c00b12 100644
--- a/community/libkscreen/APKBUILD
+++ b/community/libkscreen/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=libkscreen
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE screen management software"
arch="all !armhf" # armhf blocked by extra-cmake-modules
@@ -45,5 +45,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-283d4cac77f106d24f6eef0ba6f7a7490aff87ef5017b2db7e52ca5fd3066a33ee4e40a190cdb4e26903a79defaa724ee0a3df360464af3f76c3386c341d2d75 libkscreen-5.21.5.tar.xz
+beb4ec7f2cb3fea0dc503c4205fd7181f646a6d17c33197e51d343c55537dbc045069f6cae46d0de0602b8a09efe6b3577aaade09c976e91ee4aa6f00ff98722 libkscreen-5.22.0.tar.xz
"
diff --git a/community/libksysguard/APKBUILD b/community/libksysguard/APKBUILD
index f4536bad68..5bad78a63e 100644
--- a/community/libksysguard/APKBUILD
+++ b/community/libksysguard/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=libksysguard
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE system monitor library"
# armhf blocked by extra-cmake-modules
@@ -23,6 +23,8 @@ depends_dev="
kservice-dev
kwidgetsaddons-dev
kwindowsystem-dev
+ libnl3-dev
+ libpcap-dev
plasma-framework-dev
qt5-qttools-dev
qt5-qtwebchannel-dev
@@ -40,6 +42,8 @@ esac
source="https://download.kde.org/$_rel/plasma/$pkgver/libksysguard-$pkgver.tar.xz"
subpackages="$pkgname-dev $pkgname-lang"
+replaces="ksysguard<5.22"
+
build() {
cmake -B build \
-DCMAKE_BUILD_TYPE=None \
@@ -58,5 +62,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-1626f9489e0bdff3acad087577470818bd04d6d74d6afe95b11d02ecfe306328b3898a573704c11b0a34e73ec7a662b9c609b056be369694769a3dc2e6671fec libksysguard-5.21.5.tar.xz
+87630a8d62ec172e57fc15b51221b66536e74aa8999cc1a2749738ded16922a3c89cd13697b7168ac84af3eee7a03a49afba164b3ce51a33c8900648b9377f2e libksysguard-5.22.0.tar.xz
"
diff --git a/community/milou/APKBUILD b/community/milou/APKBUILD
index a473964653..ad35d4494e 100644
--- a/community/milou/APKBUILD
+++ b/community/milou/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=milou
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="A dedicated search application built on top of Baloo"
# armhf blocked by qt5-qtdeclarative
@@ -46,5 +46,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-49f029d1cd5568aaaae7e7170c98e076c917bd6eeed83ea379a412f2faf2e11ce403b5d31f287cb151a37a1ad567f6f42c5f9f48c9af437d0c1ed339aaed802f milou-5.21.5.tar.xz
+a6a479d68d9efbb9be06c137e39a26635aabcb78ad569c25d018b10dbe79b3c7dddd8d1c5396ebf49fd7d08eb96cba3e75db8c4ad4048940cd31fb9175bcce26 milou-5.22.0.tar.xz
"
diff --git a/community/oxygen/APKBUILD b/community/oxygen/APKBUILD
index 86136c1d2c..1b0866adf0 100644
--- a/community/oxygen/APKBUILD
+++ b/community/oxygen/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=oxygen
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Artwork, styles and assets for the Oxygen visual style for the Plasma Desktop"
# armhf blocked by extra-cmake-modules
@@ -55,5 +55,5 @@ sounds() {
amove usr/share/sounds
}
sha512sums="
-f8ab4989376eedecc5402dd788b15abf1a748863c510bebd306110423904790a9d3df3a246b8ed87273c4bd0425813a9b0a51181dca08b4c2ed27dcade6c950a oxygen-5.21.5.tar.xz
+b3a29245780c8cdbd8531d6ea91d15363ad9633ec89b3112a32c8d0909f9c6c33dd69a3f6eca5de5a74e583dbee4649cd034c85df5c1cfea32cb8e8e5e6d5731 oxygen-5.22.0.tar.xz
"
diff --git a/community/plasma-browser-integration/APKBUILD b/community/plasma-browser-integration/APKBUILD
index 85b99586f4..e1867a8e49 100644
--- a/community/plasma-browser-integration/APKBUILD
+++ b/community/plasma-browser-integration/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-browser-integration
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Components necessary to integrate browsers into the Plasma Desktop"
# armhf blocked by extra-cmake-modules
@@ -49,5 +49,5 @@ package() {
}
sha512sums="
-83165c59606d32b65359ac7a9f79beabdcd50ea35becab2d6cfec3c34e0981a0bd0452176cf213e6de0db293b4adc1615631be6477048f5e8033cc5e16f1b9a0 plasma-browser-integration-5.21.5.tar.xz
+0b8ad95830261fe823c98a12fa5aa2832b64d2754585d05db413314f3e2617426ade0a3c71df46143514b97448bf74b15c805a6e5512ab0f95cb734d56911811 plasma-browser-integration-5.22.0.tar.xz
"
diff --git a/community/plasma-desktop/APKBUILD b/community/plasma-desktop/APKBUILD
index ce31d30389..e111a1f909 100644
--- a/community/plasma-desktop/APKBUILD
+++ b/community/plasma-desktop/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-desktop
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE Plasma Desktop"
# s390x, mips, mips64 blocked by ibus
@@ -98,5 +98,5 @@ knetattach() {
DESTDIR="$subpkgdir" make install
}
sha512sums="
-40814910676a05d3d5a308a432c06ce9664b90724fe8c56c4f68f6eb939f35401f19dddb8c46006f44fa418e0f18b4a42b0ff78b5b07031f54e6b3c10c919e57 plasma-desktop-5.21.5.tar.xz
+b2799e196079519e2e3da27d6d41f2b9f606a1e703d3edb1a6c16fb74fc5d2a7d9452872cb70a3f3e8f699fb7951a9f178370744fd9989e1624294401d83d3ba plasma-desktop-5.22.0.tar.xz
"
diff --git a/community/plasma-disks/APKBUILD b/community/plasma-disks/APKBUILD
index 498055f28a..1619737965 100644
--- a/community/plasma-disks/APKBUILD
+++ b/community/plasma-disks/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-disks
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Monitors S.M.A.R.T. capable devices for imminent failure"
# armhf blocked by qt5-qtdeclarative
@@ -52,5 +52,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-b7057416b9ea692b70865122b4f4febb727c6a6dd72ffcd1cc23c9280ecba650b30c2e0002130ecceeb78b556c3e922c3ed83ec0e0d03163e01789cfb10d9426 plasma-disks-5.21.5.tar.xz
+c8e4df1d87199936736f31b5e10985d7319fccefe24434d669746ca0f17fb7cc1c015f51bd5fbd115aab5ad275773020936e78c46e895ef15fdeec88d38e8012 plasma-disks-5.22.0.tar.xz
"
diff --git a/community/plasma-firewall/APKBUILD b/community/plasma-firewall/APKBUILD
index 60323a104f..d6821d6402 100644
--- a/community/plasma-firewall/APKBUILD
+++ b/community/plasma-firewall/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-firewall
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Control Panel for your system firewall"
# armhf blocked by qt5-qtdeclarative
@@ -49,5 +49,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-93d36b12025f1918892ad02e6b92dc8c44c27d2c6426a19fb77d8b258940a25295acd923b3957d45eb1715dae097097368b0456bf27273c217b79b441dec55fd plasma-firewall-5.21.5.tar.xz
+500324331383c9aff899dbf9c78ec76c5ca468b1a06603b30d72763b88ed8f538b1c9d4cbae1e8b5777d0b1073710806db81601637d80f0fbead1471724cc5f5 plasma-firewall-5.22.0.tar.xz
"
diff --git a/community/plasma-integration/APKBUILD b/community/plasma-integration/APKBUILD
index 881162b1d6..800042ae64 100644
--- a/community/plasma-integration/APKBUILD
+++ b/community/plasma-integration/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-integration
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Qt Platform Theme integration plugins for the Plasma workspaces"
# armhf blocked by qt5-qtdeclarative
@@ -60,5 +60,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-655fee4335569325431e9fb7d368bdf82b43f1e0e0f727f95f11f481a81e8dbdcdc43470c1768b9ab5701159ffaf571b551edc57510a653e1c7fedc1c4216d92 plasma-integration-5.21.5.tar.xz
+0383d3c014c294c31e2dac3ef299288a6c67aeaec3e3367784add413c38edb16c96552fe315a4b77ded516c9e8efe246ae49dd98ab42efa13476dcafffe91315 plasma-integration-5.22.0.tar.xz
"
diff --git a/community/plasma-nano/APKBUILD b/community/plasma-nano/APKBUILD
index 25fb8c19ff..09bd89aa68 100644
--- a/community/plasma-nano/APKBUILD
+++ b/community/plasma-nano/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-nano
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="A minimal Plasma shell package intended for embedded devices"
# armhf blocked by extra-cmake-modules
@@ -43,5 +43,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-bb4c13b854f390f20bdc085db03622eef5666537ba4a8854e2eae9cdb1c53180055af2744c968eb212fe9fc699718260a032981bc16eab1f2ed8ab2cca0590d3 plasma-nano-5.21.5.tar.xz
+6f594ba2ba26b0a8d62c3144172dfbcf1e48a3bded45b3bd340d0ca93474f37ee69bfc74d943b04c3df929592fc4e8b39ed940841d556b56d827f78252f892ac plasma-nano-5.22.0.tar.xz
"
diff --git a/community/plasma-nm/APKBUILD b/community/plasma-nm/APKBUILD
index db1c97521d..e6fab5c28e 100644
--- a/community/plasma-nm/APKBUILD
+++ b/community/plasma-nm/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-nm
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plasma applet written in QML for managing network connections"
# armhf blocked by qt5-qtdeclarative
@@ -75,5 +75,5 @@ mobile() {
"$subpkgdir"/usr/share/kservices5/
}
sha512sums="
-ac074e50d5ee3be99bab515b02522e391b14936c1c8152ca067c7513af7a8e8b81cb55ab9b412ca703554f3e35fc9392368159c9872d8c372017089a4a65e3fa plasma-nm-5.21.5.tar.xz
+5fea3c71bba5481b363abba0ced6a546a3384225f57166c4fcba9f370f89f3a3b0bb98320239e61733c496ed17230fa38b0bcaeae927206f113976467f116a43 plasma-nm-5.22.0.tar.xz
"
diff --git a/community/plasma-pa/APKBUILD b/community/plasma-pa/APKBUILD
index 11941de1fb..347517e133 100644
--- a/community/plasma-pa/APKBUILD
+++ b/community/plasma-pa/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-pa
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plasma applet for audio volume management using PulseAudio"
# armhf blocked by qt5-qtdeclarative
@@ -50,5 +50,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-96a170b0ae90ff5a8015a532cde50e7a786452f9d04a30f7ee2402c9e7065ed1891e229f589bc79337ad68cd310c339840b10e7bb94551bab744ab7ae9a2ba40 plasma-pa-5.21.5.tar.xz
+4791943ea7d44c0798d6d87553a70f9a9d3f5cc9cca729ef07af15ba3dc0e908ce1de370dd347bea602207eccc06b43700c84e22b1894009711b79e9675182e1 plasma-pa-5.22.0.tar.xz
"
diff --git a/community/plasma-phone-components/APKBUILD b/community/plasma-phone-components/APKBUILD
index e79dabd346..106a2990f6 100644
--- a/community/plasma-phone-components/APKBUILD
+++ b/community/plasma-phone-components/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-phone-components
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Modules providing phone functionality for Plasma"
# armhf blocked by extra-cmake-modules
@@ -45,6 +45,7 @@ makedepends="
kservice-dev
kwayland-dev
kwidgetsaddons-dev
+ kwin-dev
kwindowsystem-dev
kxmlgui-dev
libphonenumber-dev
@@ -80,5 +81,5 @@ package() {
}
sha512sums="
-2fe1d7fa7406554eb279974681e73d57be4ae8857febde09e0023d021bf368ce2bfbe31dd6860d4b5e295601a780743def335b9162c02ccb847ba7c1496fe016 plasma-phone-components-5.21.5.tar.xz
+eba189d461286b5d64992386d8d2c98bd717aec6bb54a6c1b1b215e74166100239dcbf1c5b82261284770237536b2586f4c29d851209e2a39b07e3630de76d1f plasma-phone-components-5.22.0.tar.xz
"
diff --git a/community/plasma-sdk/APKBUILD b/community/plasma-sdk/APKBUILD
index 481aec14ab..5ebc0206a2 100644
--- a/community/plasma-sdk/APKBUILD
+++ b/community/plasma-sdk/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-sdk
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Applications useful for Plasma Development"
# armhf blocked by qt5-qtdeclarative
@@ -64,5 +64,5 @@ package() {
}
sha512sums="
-83d27d9ffca15baba292bdf285f19a743589f5a4433c6fad8fd581a93a910be0ce98f3163c7d6872d6a95b71342e301de5ebf6ce2677fcdfb61b8e4644a0d80f plasma-sdk-5.21.5.tar.xz
+84be9a4bab8ef0eb8a6e9445b1bc74c6d8d460b657b9b2f1260b2444e3f9f95de6044f097aa2fac4f31308168f1d0130bcb65acaf896fd849f4a6e248c6c7c3d plasma-sdk-5.22.0.tar.xz
"
diff --git a/community/plasma-systemmonitor/APKBUILD b/community/plasma-systemmonitor/APKBUILD
index cb56b19545..d863d827bf 100644
--- a/community/plasma-systemmonitor/APKBUILD
+++ b/community/plasma-systemmonitor/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-systemmonitor
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="An application for monitoring system resources"
# armhf blocked by extra-cmake-modules
@@ -51,5 +51,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-f21db1dc9428b9f97b4b012ae481738f7aa96c9080f35870c8a8f2dfed582b937507fd626723dbd60e66656d78984bda24279c77548d65efd847e15374b024f0 plasma-systemmonitor-5.21.5.tar.xz
+e1a8995660b440fb9af4b18737e45df7cb91995b9cbd75e947649b93c9654b9d28d16341ee2e2ed9fc6fa8e9645dfcf1a4e0d54500fed13a6fc013ad992e3970 plasma-systemmonitor-5.22.0.tar.xz
"
diff --git a/community/plasma-thunderbolt/APKBUILD b/community/plasma-thunderbolt/APKBUILD
index b9bd65fe3d..e47696ca74 100644
--- a/community/plasma-thunderbolt/APKBUILD
+++ b/community/plasma-thunderbolt/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-thunderbolt
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
# armhf blocked by qt5-qtdeclarative
# s390x blocked by bolt
@@ -49,5 +49,5 @@ package() {
}
sha512sums="
-b627ee07e0fe308b70b11c2a887443b8eb3997cb9895a67f8d7760a4294bf36eb612ff888c981ba63a6485180ae7d1cee3d9c7c0bb741a3db3599a2650ba6843 plasma-thunderbolt-5.21.5.tar.xz
+33cb92c07a212eb4c20faf8d0caf6a39f73326173c6765820a28562ce76480f1dc876a7f9411357bdd9bad6c4f3259e6a56416ccd0690ea9bcb81aa84088fe51 plasma-thunderbolt-5.22.0.tar.xz
"
diff --git a/community/plasma-vault/APKBUILD b/community/plasma-vault/APKBUILD
index b662f8e718..3bc003170a 100644
--- a/community/plasma-vault/APKBUILD
+++ b/community/plasma-vault/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-vault
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plasma applet and services for creating encrypted vaults"
# armhf blocked by extra-cmake-modules
@@ -46,5 +46,5 @@ package() {
}
sha512sums="
-bb3a4471e6a7cfc98eb4867142467cb009456c7643e87f274372e6d9d8cff1b97c6e70c1a3a51e21b72cc0d7a63a801f4681e3977339eac6a9ab108b13dc6d5c plasma-vault-5.21.5.tar.xz
+176090b62310683fd6b44edf9205858bf55cb6eef43f6c351db85dfed5601c15ef20fd760632c1217100a1c5cb7473ba90ebaf7de6b25ed884212ff0257e8cbe plasma-vault-5.22.0.tar.xz
"
diff --git a/community/plasma-workspace-wallpapers/APKBUILD b/community/plasma-workspace-wallpapers/APKBUILD
index f2e4374cd5..b58063142c 100644
--- a/community/plasma-workspace-wallpapers/APKBUILD
+++ b/community/plasma-workspace-wallpapers/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-workspace-wallpapers
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Wallpapers for the Plasma Workspace"
arch="noarch !armhf" # armhf blocked by extra-cmake-modules
@@ -29,5 +29,5 @@ package() {
}
sha512sums="
-d0a7862b55238bafc106b654bd584cd6e2fbeead8d6694cc71df23e74a2da02c0b25b2f412514b45c1d2b04567e7758ef7d77b6fc5bd814b5f5bdee0c26926ca plasma-workspace-wallpapers-5.21.5.tar.xz
+494fe7220b961ea6223f1fe66a3c8305418b12da07b08b94de96be2528f08a67bc5e5114899ce86f8a4ef0ca22a4450852bf3e92850a4b1fac85c3986dd2f1f4 plasma-workspace-wallpapers-5.22.0.tar.xz
"
diff --git a/community/plasma-workspace/APKBUILD b/community/plasma-workspace/APKBUILD
index 3ff9208e04..dc4659872c 100644
--- a/community/plasma-workspace/APKBUILD
+++ b/community/plasma-workspace/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma-workspace
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="KDE Plasma Workspace"
# armhf blocked by kirigami2
@@ -56,6 +56,7 @@ depends_dev="
kwallet-dev
kwayland-dev
kwin-dev
+ layer-shell-qt-dev
libkscreen-dev
libksysguard-dev
networkmanager-qt-dev
@@ -77,7 +78,7 @@ case "$pkgver" in
esac
source="https://download.kde.org/$_rel/plasma/$pkgver/plasma-workspace-$pkgver.tar.xz"
subpackages="$pkgname-dev $pkgname-libs $pkgname-doc $pkgname-lang"
-replaces="plasma-desktop<5.20"
+replaces="plasma-desktop<5.22"
build() {
cmake -B build \
@@ -91,7 +92,9 @@ check() {
cd build
# nightcolortest requires running dbus
# testdesktop, lookandfeel-kcmTest, test_kio_fonts, servicerunnertest systemtraymodeltest are broken
- CTEST_OUTPUT_ON_FAILURE=TRUE xvfb-run ctest -E "(nightcolortest|testdesktop|lookandfeel-kcmTest|test_kio_fonts|servicerunnertest|systemtraymodeltest)"
+ # tst_triangleFilter requires plasma-workspace to be installed
+ # locationsrunnertest requires a running Wayland environment
+ CTEST_OUTPUT_ON_FAILURE=TRUE xvfb-run ctest -E "(nightcolortest|testdesktop|lookandfeel-kcmTest|test_kio_fonts|servicerunnertest|systemtraymodeltest|tst_triangleFilter|locationsrunnertest)"
}
package() {
@@ -101,5 +104,5 @@ package() {
rm -r "$pkgdir"/usr/lib/systemd
}
sha512sums="
-6918c1a29e977ac7f3ebf6ac7308f20f20712db96bf10599f3372987509630aa2ca8bc6adf0b1af4e543ccd2a2001e38ce02d759d25f09588c7c1aaa358af1b4 plasma-workspace-5.21.5.tar.xz
+fb57dde0ae5f774cb782ab305a526ce22404972197d531a2c5e6972eb6400d57b99a7ce397fd417c0c42c8de58f4c06d9e9771b64eb94f501987da910054f4af plasma-workspace-5.22.0.tar.xz
"
diff --git a/community/plasma/APKBUILD b/community/plasma/APKBUILD
index 97079bc1e5..8a98fe4a43 100644
--- a/community/plasma/APKBUILD
+++ b/community/plasma/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plasma
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plasma (Base) meta package"
url="https://kde.org/plasma-desktop"
@@ -26,7 +26,6 @@ depends="
kmenuedit
kscreen
ksshaskpass
- ksysguard
kwallet-pam
kwayland-integration
pinentry-qt
@@ -35,6 +34,7 @@ depends="
plasma-disks
plasma-nm
plasma-pa
+ plasma-systemmonitor
plasma-vault
plasma-workspace-wallpapers
polkit-kde-agent-1
diff --git a/community/plymouth-kcm/APKBUILD b/community/plymouth-kcm/APKBUILD
index 2f22c64645..99f8696ee3 100644
--- a/community/plymouth-kcm/APKBUILD
+++ b/community/plymouth-kcm/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=plymouth-kcm
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
# armhf blocked by extra-cmake-modules
# s390x blocked by knewstuff
@@ -50,5 +50,5 @@ package() {
}
sha512sums="
-d8b8187ecce7bfdd2a3c22dec9b4ea2b085614a7e3b495671226bb9c5fe0a674d75ea03c74bfc3cc58f0ae3a7fc82ec2261d1ace5483658fd65333f61f95ba07 plymouth-kcm-5.21.5.tar.xz
+608ae68550cf7514a16d47c8a6564badca2b620182ec2a08490a596396899e25d2bc38c0181b0359c5281d35ea8f7e498c45d1d895e20dc7504b6c933a27f850 plymouth-kcm-5.22.0.tar.xz
"
diff --git a/community/polkit-kde-agent-1/APKBUILD b/community/polkit-kde-agent-1/APKBUILD
index c231d5f062..becb1b7e2b 100644
--- a/community/polkit-kde-agent-1/APKBUILD
+++ b/community/polkit-kde-agent-1/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=polkit-kde-agent-1
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Daemon providing a polkit authentication UI for KDE"
# armhf blocked by extra-cmake-modules
@@ -47,5 +47,5 @@ package() {
}
sha512sums="
-840e63d962054e6be2204f0ca5142249338050a1d3df3db66667275ae3fb1930bd3d9f4ea37b85feee73b33822c8b34015028b981d7a100afeaab793f1525ea9 polkit-kde-agent-1-5.21.5.tar.xz
+4c93eef0451d7e55c7eaee92c653f75f245426945e622227e78b22b550cf5524f0ac316ad914b4fc66a2e4e6db47d7f788f353ec1b1b8aa29e7c2e1bbe2b609e polkit-kde-agent-1-5.22.0.tar.xz
"
diff --git a/community/powerdevil/APKBUILD b/community/powerdevil/APKBUILD
index 0383f466a5..4b7d4c458c 100644
--- a/community/powerdevil/APKBUILD
+++ b/community/powerdevil/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=powerdevil
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Manages the power consumption settings of a Plasma Shell"
# armhf blocked by extra-cmake-modules
@@ -69,5 +69,5 @@ package() {
}
sha512sums="
-0d6b9cbd5cbf5cf3d34e3053f352d86586ed3672ddfae068531b899827d65b9e42c5ec0013f684deca501528590ec5074371fa7699aae4d6a20b120e319b625c powerdevil-5.21.5.tar.xz
+02e2bbbb763b130a22dca307a580f7f68f0cef2fb0f9d57c8a877897b22d368165f28e9182ff9dc71259595f9c74cf5d030e1e701ec0431d3c567cac0c967803 powerdevil-5.22.0.tar.xz
"
diff --git a/community/qqc2-breeze-style/APKBUILD b/community/qqc2-breeze-style/APKBUILD
index ce161771e1..eb5c4b19ce 100644
--- a/community/qqc2-breeze-style/APKBUILD
+++ b/community/qqc2-breeze-style/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=qqc2-breeze-style
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Breeze inspired QQC2 style"
# armhf blocked by extra-cmake-modules
@@ -49,5 +49,5 @@ package() {
}
sha512sums="
-aea577a7a2049915564558b0add589b1314c0cbf20b5a1506d9dbc5580aba20a82d9b531e9b603a1983e142694d3437ba7ca337199e7319bc321ee2935f693de qqc2-breeze-style-5.21.5.tar.xz
+7e16a6d6b90fb69623c26151468b3a70331b756c39a839a2da3804b0fc5ee3eb62ac4d3606beba2714680506dabbf93737ae5df70557e86a038c6a0a240d53ca qqc2-breeze-style-5.22.0.tar.xz
"
diff --git a/community/sddm-kcm/APKBUILD b/community/sddm-kcm/APKBUILD
index 5220577790..b8eff88b8e 100644
--- a/community/sddm-kcm/APKBUILD
+++ b/community/sddm-kcm/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=sddm-kcm
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Config module for SDDM"
# armhf blocked by qt5-qtdeclarative
@@ -52,5 +52,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-fc2fdaf67c144cccc8d2f68cc20e50027c1a083d6b33d37e8c8e7fa703b2904c11b63fc9acca63422688013346f565c77a1f02721c7f8dfcaf52bc97c7b07dbb sddm-kcm-5.21.5.tar.xz
+240e6182c9fd15fedad73542c86dd9d910c79042163de800ea4e63b923c8fa5607f1a632b75ca2624463060877e8147b92350789b8236a58a781b18566fed7b1 sddm-kcm-5.22.0.tar.xz
"
diff --git a/community/systemsettings/APKBUILD b/community/systemsettings/APKBUILD
index 7853e9e0f2..b56925b335 100644
--- a/community/systemsettings/APKBUILD
+++ b/community/systemsettings/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Bart Ribbers <bribbers@disroot.org>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=systemsettings
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="Plasma system manager for hardware, software, and workspaces"
# armhf blocked by qt5-qtdeclarative
@@ -60,5 +60,5 @@ package() {
DESTDIR="$pkgdir" cmake --install build
}
sha512sums="
-fac4f4081583079b20c4935e694ef05908d0ef62938e4560cdf9a3119234fa3d0c13f973854b83535fde3594e713b0bfdbfbc0db36994a9a507c4063b714a410 systemsettings-5.21.5.tar.xz
+30b9e0150508d6c15dda61814436cec0bd941f7a70614c890c1b275f3290e5f696db088a7d7715e42a97c9b7c98d9d23cde49e1aff14caede48d0e96186c4912 systemsettings-5.22.0.tar.xz
"
diff --git a/community/xdg-desktop-portal-kde/APKBUILD b/community/xdg-desktop-portal-kde/APKBUILD
index aa1f0191d5..6a8525d36d 100644
--- a/community/xdg-desktop-portal-kde/APKBUILD
+++ b/community/xdg-desktop-portal-kde/APKBUILD
@@ -1,7 +1,7 @@
# Contributor: Rasmus Thomsen <oss@cogitri.dev>
# Maintainer: Bart Ribbers <bribbers@disroot.org>
pkgname=xdg-desktop-portal-kde
-pkgver=5.21.5
+pkgver=5.22.0
pkgrel=0
pkgdesc="A backend implementation for xdg-desktop-portal that is using Qt/KDE"
# armhf blocked by extra-cmake-modules
@@ -47,5 +47,5 @@ package() {
}
sha512sums="
-98fe44d4a00cb16f3e3ebd30b184e836dd77efafbca7625b555852b0dcd45c4ecf71ab5d93659e3d508f2b3b42696422516a08e2ae759bbe916fa5f9fef6010e xdg-desktop-portal-kde-5.21.5.tar.xz
+9016790fc08d1e57e60dea0d0cbfe9e1226170d6b22b61f74904ca8123f43b7475512fd0a6feacf1705bfb3d5aab50c220e4e64120568ec898d7c7c0d2bf54ad xdg-desktop-portal-kde-5.22.0.tar.xz
"