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"