File indexing completed on 2024-11-24 04:55:01

0001 /*
0002  *   SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "SnapBackend.h"
0008 #include "SnapResource.h"
0009 #include "SnapTransaction.h"
0010 #include <Category/Category.h>
0011 #include <Transaction/Transaction.h>
0012 #include <appstream/AppStreamUtils.h>
0013 #include <appstream/OdrsReviewsBackend.h>
0014 #include <resources/SourcesModel.h>
0015 #include <resources/StandardBackendUpdater.h>
0016 #include <resources/StoredResultsStream.h>
0017 
0018 #include <KAboutData>
0019 #include <KConfigGroup>
0020 #include <KLocalizedString>
0021 #include <KPluginFactory>
0022 #include <KSharedConfig>
0023 #include <QDebug>
0024 #include <QFuture>
0025 #include <QFutureWatcher>
0026 #include <QStandardItemModel>
0027 #include <QThread>
0028 #include <QTimer>
0029 #include <QtConcurrentMap>
0030 #include <QtConcurrentRun>
0031 
0032 #include "utils.h"
0033 
0034 DISCOVER_BACKEND_PLUGIN(SnapBackend)
0035 
0036 class SnapSourcesBackend : public AbstractSourcesBackend
0037 {
0038 public:
0039     explicit SnapSourcesBackend(AbstractResourcesBackend *parent)
0040         : AbstractSourcesBackend(parent)
0041         , m_model(new QStandardItemModel(this))
0042     {
0043         auto it = new QStandardItem(i18n("Snap"));
0044         it->setData(QStringLiteral("Snap"), IdRole);
0045         m_model->appendRow(it);
0046     }
0047 
0048     QAbstractItemModel *sources() override
0049     {
0050         return m_model;
0051     }
0052     bool addSource(const QString & /*id*/) override
0053     {
0054         return false;
0055     }
0056     bool removeSource(const QString & /*id*/) override
0057     {
0058         return false;
0059     }
0060     QString idDescription() override
0061     {
0062         return QStringLiteral("Snap");
0063     }
0064     QVariantList actions() const override
0065     {
0066         return {};
0067     }
0068 
0069     bool supportsAdding() const override
0070     {
0071         return false;
0072     }
0073     bool canMoveSources() const override
0074     {
0075         return false;
0076     }
0077 
0078 private:
0079     QStandardItemModel *const m_model;
0080 };
0081 
0082 SnapBackend::SnapBackend(QObject *parent)
0083     : AbstractResourcesBackend(parent)
0084     , m_updater(new StandardBackendUpdater(this))
0085     , m_reviews(OdrsReviewsBackend::global())
0086 {
0087     connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, [this] {
0088         m_reviews->emitRatingFetched(this, kTransform<QList<AbstractResource *>>(m_resources.values(), [](AbstractResource *r) {
0089                                          return r;
0090                                      }));
0091     });
0092 
0093     // make sure we populate the installed resources first
0094     refreshStates();
0095 
0096     SourcesModel::global()->addSourcesBackend(new SnapSourcesBackend(this));
0097 
0098     m_threadPool.setMaxThreadCount(1);
0099 }
0100 
0101 SnapBackend::~SnapBackend()
0102 {
0103     Q_EMIT shuttingDown();
0104     m_threadPool.waitForDone(80000);
0105     m_threadPool.clear();
0106 }
0107 
0108 int SnapBackend::updatesCount() const
0109 {
0110     return m_updater->updatesCount();
0111 }
0112 
0113 static ResultsStream *voidStream()
0114 {
0115     return new ResultsStream(QStringLiteral("Snap-void"), {});
0116 }
0117 
0118 ResultsStream *SnapBackend::search(const AbstractResourcesBackend::Filters &filters)
0119 {
0120     if (!filters.extends.isEmpty()) {
0121         return voidStream();
0122     } else if (!filters.resourceUrl.isEmpty()) {
0123         return findResourceByPackageName(filters.resourceUrl);
0124     } else if (filters.category && filters.category->isAddons()) {
0125         return voidStream();
0126     } else if (filters.state >= AbstractResource::Installed || filters.origin == QLatin1String("Snap")) {
0127         std::function<bool(const QSharedPointer<QSnapdSnap> &)> f = [filters](const QSharedPointer<QSnapdSnap> &s) {
0128             return filters.search.isEmpty() || s->name().contains(filters.search, Qt::CaseInsensitive)
0129                 || s->description().contains(filters.search, Qt::CaseInsensitive);
0130         };
0131         return populateWithFilter(m_client.getSnaps(), f);
0132     } else if (!filters.search.isEmpty()) {
0133         return populate(m_client.find(QSnapdClient::FindFlag::None, filters.search));
0134     }
0135     return voidStream();
0136 }
0137 
0138 ResultsStream *SnapBackend::findResourceByPackageName(const QUrl &search)
0139 {
0140     Q_ASSERT(!search.host().isEmpty() || !AppStreamUtils::appstreamIds(search).isEmpty());
0141     return search.scheme() == QLatin1String("snap") ? populate(m_client.find(QSnapdClient::MatchName, search.host())) :
0142 #ifdef SNAP_FIND_COMMON_ID
0143         search.scheme() == QLatin1String("appstream")
0144         ? populate(kTransform<QVector<QSnapdFindRequest *>>(AppStreamUtils::appstreamIds(search),
0145                                                             [this](const QString &id) {
0146                                                                 return m_client.find(QSnapdClient::MatchCommonId, id);
0147                                                             }))
0148         :
0149 #endif
0150         voidStream();
0151 }
0152 
0153 template<class T>
0154 ResultsStream *SnapBackend::populate(T *job)
0155 {
0156     return populate<T>(QVector<T *>{job});
0157 }
0158 
0159 template<class T>
0160 ResultsStream *SnapBackend::populate(const QVector<T *> &jobs)
0161 {
0162     std::function<bool(const QSharedPointer<QSnapdSnap> &)> acceptAll = [](const QSharedPointer<QSnapdSnap> &) {
0163         return true;
0164     };
0165     return populateJobsWithFilter(jobs, acceptAll);
0166 }
0167 
0168 template<class T>
0169 ResultsStream *SnapBackend::populateWithFilter(T *job, std::function<bool(const QSharedPointer<QSnapdSnap> &s)> &filter)
0170 {
0171     return populateJobsWithFilter<T>({job}, filter);
0172 }
0173 
0174 template<class T>
0175 ResultsStream *SnapBackend::populateJobsWithFilter(const QVector<T *> &jobs, std::function<bool(const QSharedPointer<QSnapdSnap> &s)> &filter)
0176 {
0177     auto stream = new ResultsStream(QStringLiteral("Snap-populate"));
0178     auto future = QtConcurrent::run(&m_threadPool, [this, jobs]() {
0179         for (auto job : jobs) {
0180             connect(this, &SnapBackend::shuttingDown, job, &T::cancel);
0181             job->runSync();
0182         }
0183     });
0184 
0185     auto watcher = new QFutureWatcher<void>(this);
0186     watcher->setFuture(future);
0187     connect(watcher, &QFutureWatcher<void>::finished, watcher, &QObject::deleteLater);
0188     connect(watcher, &QFutureWatcher<void>::finished, stream, [this, jobs, filter, stream] {
0189         QVector<StreamResult> ret;
0190         for (auto job : jobs) {
0191             job->deleteLater();
0192             if (job->error()) {
0193                 qDebug() << "error:" << job->error() << job->errorString();
0194                 continue;
0195             }
0196 
0197             for (int i = 0, c = job->snapCount(); i < c; ++i) {
0198                 QSharedPointer<QSnapdSnap> snap(job->snap(i));
0199 
0200                 if (!filter(snap))
0201                     continue;
0202 
0203                 const auto snapname = snap->name();
0204                 SnapResource *&res = m_resources[snapname];
0205                 if (!res) {
0206                     res = new SnapResource(snap, AbstractResource::None, this);
0207                     Q_ASSERT(res->packageName() == snapname);
0208                 } else {
0209                     res->setSnap(snap);
0210                 }
0211                 ret += res;
0212             }
0213         }
0214 
0215         if (!ret.isEmpty())
0216             Q_EMIT stream->resourcesFound(ret);
0217         stream->finish();
0218     });
0219     return stream;
0220 }
0221 
0222 void SnapBackend::setFetching(bool fetching)
0223 {
0224     if (m_fetching != fetching) {
0225         m_fetching = fetching;
0226         Q_EMIT fetchingChanged();
0227     } else {
0228         qWarning() << "fetching already on state" << fetching;
0229     }
0230 }
0231 
0232 AbstractBackendUpdater *SnapBackend::backendUpdater() const
0233 {
0234     return m_updater;
0235 }
0236 
0237 AbstractReviewsBackend *SnapBackend::reviewsBackend() const
0238 {
0239     return m_reviews.data();
0240 }
0241 
0242 Transaction *SnapBackend::installApplication(AbstractResource *app, const AddonList &addons)
0243 {
0244     Q_ASSERT(addons.isEmpty());
0245     return installApplication(app);
0246 }
0247 
0248 Transaction *SnapBackend::installApplication(AbstractResource *_app)
0249 {
0250     auto app = qobject_cast<SnapResource *>(_app);
0251     return new SnapTransaction(&m_client, app, Transaction::InstallRole, AbstractResource::Installed);
0252 }
0253 
0254 Transaction *SnapBackend::removeApplication(AbstractResource *_app)
0255 {
0256     auto app = qobject_cast<SnapResource *>(_app);
0257     return new SnapTransaction(&m_client, app, Transaction::RemoveRole, AbstractResource::None);
0258 }
0259 
0260 QString SnapBackend::displayName() const
0261 {
0262     return QStringLiteral("Snap");
0263 }
0264 
0265 void SnapBackend::refreshStates()
0266 {
0267     auto ret = new StoredResultsStream({populate(m_client.getSnaps())});
0268     connect(ret, &StoredResultsStream::finishedResources, this, [this](const QVector<StreamResult> &resources) {
0269         for (auto res : std::as_const(m_resources)) {
0270             bool contained = kContains(resources, [res](const StreamResult &in) {
0271                 return in.resource == res;
0272             });
0273             if (contained)
0274                 res->setState(AbstractResource::Installed);
0275             else
0276                 res->setState(AbstractResource::None);
0277         }
0278     });
0279 }
0280 
0281 #include "SnapBackend.moc"