3332 lines
125 KiB
Diff
3332 lines
125 KiB
Diff
|
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..4bfe165b
|
||
|
--- /dev/null
|
||
|
+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkBackend.cpp
|
||
|
@@ -0,0 +1,503 @@
|
||
|
+/***************************************************************************
|
||
|
+ * 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 <QAction>
|
||
|
+#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 §ionName)
|
||
|
+{
|
||
|
+ 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 §ionName);
|
||
|
+ 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..a126483a
|
||
|
--- /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 <QAction>
|
||
|
+#include <QVector>
|
||
|
+
|
||
|
+// KF5
|
||
|
+#include <KAuthExecuteJob>
|
||
|
+#include <KLocalizedString>
|
||
|
+
|
||
|
+// libapk-qt
|
||
|
+#include <QtApk>
|
||
|
+
|
||
|
+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))
|
||
|
+ // ^^ unfortunately QML side ignores icons for custom actions
|
||
|
+{
|
||
|
+ loadSources();
|
||
|
+ QObject::connect(m_refreshAction, &QAction::triggered,
|
||
|
+ this, &AlpineApkSourcesBackend::loadSources);
|
||
|
+ 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()
|
||
|
+{
|
||
|
+ 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..eacda22d
|
||
|
--- /dev/null
|
||
|
+++ b/libdiscover/backends/AlpineApkBackend/AlpineApkSourcesBackend.h
|
||
|
@@ -0,0 +1,57 @@
|
||
|
+/***************************************************************************
|
||
|
+ * 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 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;
|
||
|
+ QAction *m_refreshAction = nullptr;
|
||
|
+ QAction *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()
|
||
|
--
|
||
|
2.30.0
|
||
|
|