File indexing completed on 2024-05-12 05:29:03
0001 /* 0002 * SPDX-FileCopyrightText: 2012 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 "ResourcesModel.h" 0008 0009 #include "AbstractResource.h" 0010 #include "Category/CategoryModel.h" 0011 #include "Transaction/TransactionModel.h" 0012 #include "libdiscover_debug.h" 0013 #include "resources/AbstractBackendUpdater.h" 0014 #include "resources/AbstractResourcesBackend.h" 0015 #include "utils.h" 0016 #include <DiscoverBackendsFactory.h> 0017 #include <KConfigGroup> 0018 #include <KLocalizedString> 0019 #include <KOSRelease> 0020 #include <KSharedConfig> 0021 #include <QCoreApplication> 0022 #include <QIcon> 0023 #include <QMetaProperty> 0024 #include <QThread> 0025 #include <ReviewsBackend/AbstractReviewsBackend.h> 0026 #include <ReviewsBackend/Rating.h> 0027 #include <Transaction/Transaction.h> 0028 #include <functional> 0029 #include <resources/DiscoverAction.h> 0030 0031 using namespace Qt::StringLiterals; 0032 0033 ResourcesModel *ResourcesModel::s_self = nullptr; 0034 0035 ResourcesModel *ResourcesModel::global() 0036 { 0037 if (!s_self) { 0038 s_self = new ResourcesModel; 0039 s_self->init(true); 0040 } 0041 return s_self; 0042 } 0043 0044 ResourcesModel::ResourcesModel(QObject *parent) 0045 : QObject(parent) 0046 , m_isFetching(false) 0047 , m_initializingBackendsCount(0) 0048 , m_currentApplicationBackend(nullptr) 0049 , m_allInitializedEmitter(new QTimer(this)) 0050 , m_updatesCount( 0051 0, 0052 [this] { 0053 int ret = 0; 0054 for (AbstractResourcesBackend *backend : std::as_const(m_backends)) { 0055 ret += backend->updatesCount(); 0056 } 0057 return ret; 0058 }, 0059 [this](int count) { 0060 Q_EMIT updatesCountChanged(count); 0061 }) 0062 , m_fetchingUpdatesProgress( 0063 0, 0064 [this] { 0065 if (m_backends.isEmpty()) 0066 return 0; 0067 0068 int sum = 0; 0069 int weights = 0; 0070 for (auto backend : std::as_const(m_backends)) { 0071 sum += backend->fetchingUpdatesProgress() * backend->fetchingUpdatesProgressWeight(); 0072 weights += backend->fetchingUpdatesProgressWeight(); 0073 } 0074 return sum / weights; 0075 }, 0076 [this](int progress) { 0077 Q_EMIT fetchingUpdatesProgressChanged(progress); 0078 }) 0079 { 0080 connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching); 0081 connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend); 0082 } 0083 0084 void ResourcesModel::init(bool load) 0085 { 0086 Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread()); 0087 0088 m_allInitializedEmitter->setSingleShot(true); 0089 m_allInitializedEmitter->setInterval(0); 0090 connect(m_allInitializedEmitter, &QTimer::timeout, this, [this]() { 0091 if (m_initializingBackendsCount == 0) { 0092 m_isInitializing = false; 0093 Q_EMIT allInitialized(); 0094 } 0095 }); 0096 0097 if (load) { 0098 registerAllBackends(); 0099 } 0100 0101 m_updateAction = new DiscoverAction(this); 0102 m_updateAction->setIconName(QStringLiteral("system-software-update")); 0103 m_updateAction->setText(i18n("Refresh")); 0104 connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) { 0105 m_updateAction->setEnabled(!fetching); 0106 m_fetchingUpdatesProgress.reevaluate(); 0107 }); 0108 connect(m_updateAction, &DiscoverAction::triggered, this, &ResourcesModel::checkForUpdates); 0109 0110 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); 0111 } 0112 0113 ResourcesModel::ResourcesModel(const QString &backendName, QObject *parent) 0114 : ResourcesModel(parent) 0115 { 0116 s_self = this; 0117 registerBackendByName(backendName); 0118 init(false); 0119 } 0120 0121 ResourcesModel::~ResourcesModel() 0122 { 0123 s_self = nullptr; 0124 qDeleteAll(m_backends); 0125 } 0126 0127 void ResourcesModel::addResourcesBackend(AbstractResourcesBackend *backend) 0128 { 0129 Q_ASSERT(!m_backends.contains(backend)); 0130 if (!backend->isValid()) { 0131 qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); 0132 CategoryModel::global()->blacklistPlugin(backend->name()); 0133 backend->deleteLater(); 0134 return; 0135 } 0136 0137 m_backends += backend; 0138 if (!backend->isFetching()) { 0139 m_updatesCount.reevaluate(); 0140 } else { 0141 m_initializingBackendsCount++; 0142 } 0143 0144 connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged); 0145 connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller); 0146 connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged); 0147 connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, [this] { 0148 m_updatesCount.reevaluate(); 0149 }); 0150 connect(backend, &AbstractResourcesBackend::fetchingUpdatesProgressChanged, this, [this] { 0151 m_fetchingUpdatesProgress.reevaluate(); 0152 }); 0153 connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved); 0154 connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage); 0155 connect(backend, &AbstractResourcesBackend::inlineMessageChanged, this, &ResourcesModel::setInlineMessage); 0156 connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching); 0157 if (backend->reviewsBackend()) { 0158 connect(backend->reviewsBackend(), &AbstractReviewsBackend::error, this, &ResourcesModel::passiveMessage, Qt::UniqueConnection); 0159 } 0160 0161 // In case this is in fact the first backend to be added, and also happens to be 0162 // pre-filled, we still need for the rest of the backends to be added before trying 0163 // to send out the initialized signal. To ensure this happens, schedule it for the 0164 // start of the next run of the event loop. 0165 if (m_initializingBackendsCount == 0) { 0166 m_allInitializedEmitter->start(); 0167 } else { 0168 slotFetching(); 0169 } 0170 } 0171 0172 void ResourcesModel::callerFetchingChanged() 0173 { 0174 AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender()); 0175 0176 if (!backend->isValid()) { 0177 qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name(); 0178 int idx = m_backends.indexOf(backend); 0179 Q_ASSERT(idx >= 0); 0180 m_backends.removeAt(idx); 0181 Q_EMIT backendsChanged(); 0182 CategoryModel::global()->blacklistPlugin(backend->name()); 0183 backend->deleteLater(); 0184 slotFetching(); 0185 return; 0186 } 0187 0188 if (backend->isFetching()) { 0189 m_initializingBackendsCount++; 0190 slotFetching(); 0191 } else { 0192 m_initializingBackendsCount--; 0193 if (m_initializingBackendsCount == 0) 0194 m_allInitializedEmitter->start(); 0195 else 0196 slotFetching(); 0197 } 0198 } 0199 0200 void ResourcesModel::updateCaller(const QVector<QByteArray> &properties) 0201 { 0202 AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender()); 0203 0204 Q_EMIT backendDataChanged(backend, properties); 0205 } 0206 0207 QVector<AbstractResourcesBackend *> ResourcesModel::backends() const 0208 { 0209 return m_backends; 0210 } 0211 0212 bool ResourcesModel::hasSecurityUpdates() const 0213 { 0214 bool ret = false; 0215 0216 for (AbstractResourcesBackend *backend : std::as_const(m_backends)) { 0217 ret |= backend->hasSecurityUpdates(); 0218 } 0219 0220 return ret; 0221 } 0222 0223 void ResourcesModel::installApplication(AbstractResource *app) 0224 { 0225 TransactionModel::global()->addTransaction(app->backend()->installApplication(app)); 0226 } 0227 0228 void ResourcesModel::installApplication(AbstractResource *app, const AddonList &addons) 0229 { 0230 TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons)); 0231 } 0232 0233 void ResourcesModel::removeApplication(AbstractResource *app) 0234 { 0235 TransactionModel::global()->addTransaction(app->backend()->removeApplication(app)); 0236 } 0237 0238 void ResourcesModel::registerAllBackends() 0239 { 0240 DiscoverBackendsFactory f; 0241 const auto backends = f.allBackends(); 0242 if (m_initializingBackendsCount == 0 && backends.isEmpty()) { 0243 qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends"; 0244 m_allInitializedEmitter->start(); 0245 } else { 0246 for (AbstractResourcesBackend *b : backends) { 0247 addResourcesBackend(b); 0248 } 0249 Q_EMIT backendsChanged(); 0250 } 0251 } 0252 0253 void ResourcesModel::registerBackendByName(const QString &name) 0254 { 0255 DiscoverBackendsFactory f; 0256 const auto backends = f.backend(name); 0257 for (auto b : backends) 0258 addResourcesBackend(b); 0259 0260 Q_EMIT backendsChanged(); 0261 } 0262 0263 bool ResourcesModel::isFetching() const 0264 { 0265 return m_isFetching; 0266 } 0267 0268 bool ResourcesModel::isInitializing() const 0269 { 0270 return m_isInitializing; 0271 } 0272 0273 void ResourcesModel::slotFetching() 0274 { 0275 bool newFetching = false; 0276 for (AbstractResourcesBackend *b : std::as_const(m_backends)) { 0277 // isFetching should sort of be enough. However, sometimes the backend itself 0278 // will still be operating on things, which from a model point of view would 0279 // still mean something going on. So, interpret that as fetching as well, for 0280 // the purposes of this property. 0281 if (b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) { 0282 newFetching = true; 0283 break; 0284 } 0285 } 0286 if (newFetching != m_isFetching) { 0287 m_isFetching = newFetching; 0288 Q_EMIT fetchingChanged(m_isFetching); 0289 } 0290 } 0291 0292 bool ResourcesModel::isBusy() const 0293 { 0294 return TransactionModel::global()->rowCount() > 0; 0295 } 0296 0297 bool ResourcesModel::isExtended(const QString &id) 0298 { 0299 return std::any_of(m_backends.constBegin(), m_backends.constEnd(), [&](AbstractResourcesBackend *backend) { 0300 return backend->extends(id); 0301 }); 0302 } 0303 0304 AggregatedResultsStream::AggregatedResultsStream(const QSet<ResultsStream *> &streams) 0305 : ResultsStream(QStringLiteral("AggregatedResultsStream")) 0306 { 0307 Q_ASSERT(!streams.contains(nullptr)); 0308 if (streams.isEmpty()) { 0309 qCWarning(LIBDISCOVER_LOG) << "no streams to aggregate!!"; 0310 QTimer::singleShot(0, this, &AggregatedResultsStream::clear); 0311 } 0312 0313 for (auto stream : streams) { 0314 connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults); 0315 connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::streamDestruction); 0316 connect(this, &ResultsStream::fetchMore, stream, &ResultsStream::fetchMore); 0317 m_streams << stream; 0318 } 0319 0320 m_delayedEmission.setInterval(0); 0321 connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults); 0322 } 0323 0324 AggregatedResultsStream::~AggregatedResultsStream() = default; 0325 0326 void AggregatedResultsStream::addResults(const QVector<StreamResult> &res) 0327 { 0328 for (auto r : res) 0329 connect(r.resource, &QObject::destroyed, this, &AggregatedResultsStream::resourceDestruction); 0330 0331 m_results += res; 0332 0333 m_delayedEmission.start(); 0334 } 0335 0336 void AggregatedResultsStream::emitResults() 0337 { 0338 if (!m_results.isEmpty()) { 0339 Q_EMIT resourcesFound(m_results); 0340 m_results.clear(); 0341 } 0342 m_delayedEmission.setInterval(m_delayedEmission.interval() + 100); 0343 m_delayedEmission.stop(); 0344 } 0345 0346 void AggregatedResultsStream::resourceDestruction(QObject *obj) 0347 { 0348 for (auto it = m_results.begin(); it != m_results.end(); ++it) { 0349 if (obj == it->resource) 0350 it = m_results.erase(it); 0351 else 0352 ++it; 0353 } 0354 } 0355 0356 void AggregatedResultsStream::streamDestruction(QObject *obj) 0357 { 0358 m_streams.remove(obj); 0359 clear(); 0360 } 0361 0362 void AggregatedResultsStream::clear() 0363 { 0364 if (m_streams.isEmpty()) { 0365 emitResults(); 0366 Q_EMIT finished(); 0367 deleteLater(); 0368 } 0369 } 0370 0371 AggregatedResultsStream *ResourcesModel::search(const AbstractResourcesBackend::Filters &search) 0372 { 0373 if (search.isEmpty()) { 0374 return new AggregatedResultsStream({new ResultsStream(QStringLiteral("emptysearch"), {})}); 0375 } 0376 0377 auto streams = kTransform<QSet<ResultsStream *>>(m_backends, [search](AbstractResourcesBackend *backend) { 0378 return backend->search(search); 0379 }); 0380 return new AggregatedResultsStream(streams); 0381 } 0382 0383 void ResourcesModel::checkForUpdates() 0384 { 0385 for (auto backend : std::as_const(m_backends)) 0386 backend->checkForUpdates(); 0387 } 0388 0389 AbstractResourcesBackend *ResourcesModel::currentApplicationBackend() const 0390 { 0391 return m_currentApplicationBackend; 0392 } 0393 0394 void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend *backend, bool write) 0395 { 0396 if (backend != m_currentApplicationBackend) { 0397 if (write) { 0398 KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s); 0399 if (backend) 0400 settings.writeEntry("currentApplicationBackend", backend->name()); 0401 else 0402 settings.deleteEntry("currentApplicationBackend"); 0403 } 0404 0405 qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend" << backend; 0406 m_currentApplicationBackend = backend; 0407 Q_EMIT currentApplicationBackendChanged(backend); 0408 } 0409 } 0410 0411 void ResourcesModel::initApplicationsBackend() 0412 { 0413 const auto name = applicationSourceName(); 0414 0415 auto idx = kIndexOf(m_backends, [name](AbstractResourcesBackend *b) { 0416 return b->hasApplications() && b->name() == name; 0417 }); 0418 if (idx < 0) { 0419 idx = kIndexOf(m_backends, [](AbstractResourcesBackend *b) { 0420 return b->hasApplications(); 0421 }); 0422 qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx; 0423 } 0424 setCurrentApplicationBackend(m_backends.value(idx, nullptr), false); 0425 } 0426 0427 QString ResourcesModel::applicationSourceName() const 0428 { 0429 KConfigGroup settings(KSharedConfig::openConfig(), u"ResourcesModel"_s); 0430 return settings.readEntry<QString>("currentApplicationBackend", QStringLiteral("packagekit-backend")); 0431 } 0432 0433 QString ResourcesModel::distroName() const 0434 { 0435 return KOSRelease().name(); 0436 } 0437 0438 QUrl ResourcesModel::distroBugReportUrl() 0439 { 0440 return QUrl(KOSRelease().bugReportUrl()); 0441 } 0442 0443 void ResourcesModel::setInlineMessage(const QSharedPointer<InlineMessage> &inlineMessage) 0444 { 0445 if (inlineMessage == m_inlineMessage) { 0446 return; 0447 } 0448 0449 m_inlineMessage = inlineMessage; 0450 Q_EMIT inlineMessageChanged(inlineMessage); 0451 }