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