KNewStuff

quickengine.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Dan Leinir Turthra Jensen <admin@leinir.dk>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "quickengine.h"
8#include "cache.h"
9#include "errorcode.h"
10#include "imageloader_p.h"
11#include "installation_p.h"
12#include "knewstuffquick_debug.h"
13#include "quicksettings.h"
14
15#include <KLocalizedString>
16#include <QTimer>
17
18#include "categoriesmodel.h"
19#include "quickquestionlistener.h"
20#include "searchpresetmodel.h"
21
22class EnginePrivate
23{
24public:
25 bool isValid = false;
26 CategoriesModel *categoriesModel = nullptr;
27 SearchPresetModel *searchPresetModel = nullptr;
28 QString configFile;
29 QTimer searchTimer;
30 Engine::BusyState busyState;
31 QString busyMessage;
32 // the current request from providers
35 // the page that is currently displayed, so it is not requested repeatedly
36 int currentPage = -1;
37
38 // when requesting entries from a provider, how many to ask for
39 int pageSize = 20;
40
41 int numDataJobs = 0;
42 int numPictureJobs = 0;
43 int numInstallJobs = 0;
44};
45
46Engine::Engine(QObject *parent)
47 : KNSCore::EngineBase(parent)
48 , d(new EnginePrivate)
49{
50 const auto setBusy = [this](Engine::BusyState state, const QString &msg) {
51 setBusyState(state);
52 d->busyMessage = msg;
53 };
54 setBusy(BusyOperation::Initializing, i18n("Loading data")); // For the user this should be the same as initializing
55
56 KNewStuffQuick::QuickQuestionListener::instance();
57 d->categoriesModel = new CategoriesModel(this);
58 connect(d->categoriesModel, &QAbstractListModel::modelReset, this, &Engine::categoriesChanged);
59 d->searchPresetModel = new SearchPresetModel(this);
60 connect(d->searchPresetModel, &QAbstractListModel::modelReset, this, &Engine::searchPresetModelChanged);
61
62 d->searchTimer.setSingleShot(true);
63 d->searchTimer.setInterval(1000);
64 connect(&d->searchTimer, &QTimer::timeout, this, &Engine::reloadEntries);
65 connect(installation(), &KNSCore::Installation::signalInstallationFinished, this, [this]() {
66 --d->numInstallJobs;
67 updateStatus();
68 });
69 connect(installation(), &KNSCore::Installation::signalInstallationFailed, this, [this](const QString &message) {
70 --d->numInstallJobs;
71 Q_EMIT signalErrorCode(KNSCore::ErrorCode::InstallationError, message, QVariant());
72 });
73 connect(this, &EngineBase::signalProvidersLoaded, this, &Engine::updateStatus);
74 connect(this, &EngineBase::signalProvidersLoaded, this, [this]() {
75 d->currentRequest.categories = EngineBase::categories();
76 });
77
78 connect(this,
80 this,
81 [setBusy, this](const KNSCore::ErrorCode::ErrorCode &error, const QString &message, const QVariant &metadata) {
82 Q_EMIT errorCode(error, message, metadata);
83 if (error == KNSCore::ErrorCode::ProviderError || error == KNSCore::ErrorCode::ConfigFileError) {
84 // This means loading the config or providers file failed entirely and we cannot complete the
85 // initialisation. It also means the engine is done loading, but that nothing will
86 // work, and we need to inform the user of this.
87 setBusy({}, QString());
88 }
89
90 // Emit the signal later, currently QML is not connected to the slot
91 if (error == KNSCore::ErrorCode::ConfigFileError) {
92 QTimer::singleShot(0, [this, error, message, metadata]() {
93 Q_EMIT errorCode(error, message, metadata);
94 });
95 }
96 });
97
98 connect(this, &Engine::signalEntryEvent, this, [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) {
99 // Just forward the event but not do anything more
100 Q_EMIT entryEvent(entry, event);
101 });
102 //
103 // And finally, let's just make sure we don't miss out the various things here getting changed
104 // In other words, when we're asked to reset the view, actually do that
105 connect(this, &Engine::signalResetView, this, &Engine::categoriesFilterChanged);
106 connect(this, &Engine::signalResetView, this, &Engine::filterChanged);
107 connect(this, &Engine::signalResetView, this, &Engine::sortOrderChanged);
108 connect(this, &Engine::signalResetView, this, &Engine::searchTermChanged);
109}
110
111bool Engine::init(const QString &configfile)
112{
113 const bool valid = EngineBase::init(configfile);
114 if (valid) {
117 cache()->registerChangedEntry(entry);
118 }
119 });
120 const auto slotEntryChanged = [this](const KNSCore::Entry &entry) {
122 };
123 // Don't connect KNSCore::Installation::signalEntryChanged as is already forwarded to
124 // Transaction, which in turn is forwarded to our slotEntryChanged, so avoids a double emission
125 connect(cache().data(), &KNSCore::Cache::entryChanged, this, slotEntryChanged);
126 }
127 return valid;
128}
129void Engine::updateStatus()
130{
131 QString busyMessage;
132 BusyState state;
133 if (d->numPictureJobs > 0) {
134 // If it is loading previews or data is irrelevant for the user
135 busyMessage = i18n("Loading data");
136 state |= BusyOperation::LoadingPreview;
137 }
138 if (d->numInstallJobs > 0) {
139 busyMessage = i18n("Installing");
140 state |= BusyOperation::InstallingEntry;
141 }
142 if (d->numDataJobs > 0) {
143 busyMessage = i18n("Loading data");
144 state |= BusyOperation::LoadingData;
145 }
146 d->busyMessage = busyMessage;
147 setBusyState(state);
148}
149
150bool Engine::needsLazyLoadSpinner()
151{
152 return d->numDataJobs > 0 || d->numPictureJobs;
153}
154
155Engine::~Engine() = default;
156
157void Engine::setBusyState(BusyState state)
158{
159 d->busyState = state;
161}
163{
164 return d->busyState;
165}
166QString Engine::busyMessage() const
167{
168 return d->busyMessage;
169}
170
171QString Engine::configFile() const
172{
173 return d->configFile;
174}
175
176void Engine::setConfigFile(const QString &newFile)
177{
178 if (d->configFile != newFile) {
179 d->configFile = newFile;
180 Q_EMIT configFileChanged();
181
182 if (KNewStuffQuick::Settings::instance()->allowedByKiosk()) {
183 d->isValid = init(newFile);
184 Q_EMIT categoriesFilterChanged();
185 Q_EMIT filterChanged();
186 Q_EMIT sortOrderChanged();
187 Q_EMIT searchTermChanged();
188 } else {
189 // This is not an error message in the proper sense, and the message is not intended to look like an error (as there is really
190 // nothing the user can do to fix it, and we just tell them so they're not wondering what's wrong)
192 KNSCore::ErrorCode::ConfigFileError,
193 i18nc("An informational message which is shown to inform the user they are not authorized to use GetHotNewStuff functionality",
194 "You are not authorized to Get Hot New Stuff. If you think this is in error, please contact the person in charge of your permissions."),
195 QVariant());
196 }
197 }
198}
199
200CategoriesModel *Engine::categories() const
201{
202 return d->categoriesModel;
203}
204
205QStringList Engine::categoriesFilter() const
206{
207 return d->currentRequest.categories;
208}
209
210void Engine::setCategoriesFilter(const QStringList &newCategoriesFilter)
211{
212 if (d->currentRequest.categories != newCategoriesFilter) {
213 d->currentRequest.categories = newCategoriesFilter;
214 reloadEntries();
215 Q_EMIT categoriesFilterChanged();
216 }
217}
218
219KNSCore::Provider::Filter Engine::filter() const
220{
221 return d->currentRequest.filter;
222}
223
224void Engine::setFilter(KNSCore::Provider::Filter newFilter)
225{
226 if (d->currentRequest.filter != newFilter) {
227 d->currentRequest.filter = newFilter;
228 reloadEntries();
229 Q_EMIT filterChanged();
230 }
231}
232
233KNSCore::Provider::SortMode Engine::sortOrder() const
234{
235 return d->currentRequest.sortMode;
236}
237
238void Engine::setSortOrder(KNSCore::Provider::SortMode mode)
239{
240 if (d->currentRequest.sortMode != mode) {
241 d->currentRequest.sortMode = mode;
242 reloadEntries();
243 Q_EMIT sortOrderChanged();
244 }
245}
246
247QString Engine::searchTerm() const
248{
249 return d->currentRequest.searchTerm;
250}
251
252void Engine::setSearchTerm(const QString &searchTerm)
253{
254 if (d->isValid && d->currentRequest.searchTerm != searchTerm) {
255 d->currentRequest.searchTerm = searchTerm;
256 Q_EMIT searchTermChanged();
257 }
258 KNSCore::Entry::List cacheEntries = cache()->requestFromCache(d->currentRequest);
259 if (!cacheEntries.isEmpty()) {
260 reloadEntries();
261 } else {
262 d->searchTimer.start();
263 }
264}
265
266SearchPresetModel *Engine::searchPresetModel() const
267{
268 return d->searchPresetModel;
269}
270
271bool Engine::isValid()
272{
273 return d->isValid;
274}
275
276void Engine::updateEntryContents(const KNSCore::Entry &entry)
277{
278 const auto provider = EngineBase::provider(entry.providerId());
279 if (provider.isNull() || !provider->isInitialized()) {
280 qCWarning(KNEWSTUFFQUICK) << "Provider was not found or is not initialized" << provider << entry.providerId();
281 return;
282 }
283 provider->loadEntryDetails(entry);
284}
285
286void Engine::reloadEntries()
287{
288 Q_EMIT signalResetView();
289 d->currentPage = -1;
290 d->currentRequest.page = 0;
291 d->numDataJobs = 0;
292
293 const auto providersList = EngineBase::providers();
294 for (const QSharedPointer<KNSCore::Provider> &p : providersList) {
295 if (p->isInitialized()) {
296 if (d->currentRequest.filter == KNSCore::Provider::Installed || d->currentRequest.filter == KNSCore::Provider::Updates) {
297 // when asking for installed entries, never use the cache
298 p->loadEntries(d->currentRequest);
299 } else {
300 // take entries from cache until there are no more
301 KNSCore::Entry::List cacheEntries;
302 KNSCore::Entry::List lastCache = cache()->requestFromCache(d->currentRequest);
303 while (!lastCache.isEmpty()) {
304 qCDebug(KNEWSTUFFQUICK) << "From cache";
305 cacheEntries << lastCache;
306
307 d->currentPage = d->currentRequest.page;
308 ++d->currentRequest.page;
309 lastCache = cache()->requestFromCache(d->currentRequest);
310 }
311
312 // Since the cache has no more pages, reset the request's page
313 if (d->currentPage >= 0) {
314 d->currentRequest.page = d->currentPage;
315 }
316
317 if (!cacheEntries.isEmpty()) {
318 Q_EMIT signalEntriesLoaded(cacheEntries);
319 } else {
320 qCDebug(KNEWSTUFFQUICK) << "From provider";
321 p->loadEntries(d->currentRequest);
322
323 ++d->numDataJobs;
324 updateStatus();
325 }
326 }
327 }
328 }
329}
331{
332 EngineBase::addProvider(provider);
333 connect(provider.data(), &KNSCore::Provider::loadingFinished, this, [this](const auto &request, const auto &entries) {
334 d->currentPage = qMax<int>(request.page, d->currentPage);
335 qCDebug(KNEWSTUFFQUICK) << "loaded page " << request.page << "current page" << d->currentPage << "count:" << entries.count();
336
337 if (request.filter != KNSCore::Provider::Updates) {
338 cache()->insertRequest(request, entries);
339 }
340 Q_EMIT signalEntriesLoaded(entries);
341
342 --d->numDataJobs;
343 updateStatus();
344 });
345 connect(provider.data(), &KNSCore::Provider::entryDetailsLoaded, this, [this](const auto &entry) {
346 --d->numDataJobs;
347 updateStatus();
348 Q_EMIT signalEntryEvent(entry, KNSCore::Entry::DetailsLoadedEvent);
349 });
350}
351
352void Engine::loadPreview(const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type)
353{
354 qCDebug(KNEWSTUFFQUICK) << "START preview: " << entry.name() << type;
355 auto l = new KNSCore::ImageLoader(entry, type, this);
356 connect(l, &KNSCore::ImageLoader::signalPreviewLoaded, this, [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type) {
357 qCDebug(KNEWSTUFFQUICK) << "FINISH preview: " << entry.name() << type;
358 Q_EMIT signalEntryPreviewLoaded(entry, type);
359 --d->numPictureJobs;
360 updateStatus();
361 });
362 connect(l, &KNSCore::ImageLoader::signalError, this, [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type, const QString &errorText) {
363 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ImageError, errorText, QVariantList() << entry.name() << type);
364 qCDebug(KNEWSTUFFQUICK) << "ERROR preview: " << errorText << entry.name() << type;
365 --d->numPictureJobs;
366 updateStatus();
367 });
368 l->start();
369 ++d->numPictureJobs;
370 updateStatus();
371}
372
374{
375 registerTransaction(KNSCore::Transaction::adopt(this, entry));
376}
377void Engine::install(const KNSCore::Entry &entry, int linkId)
378{
379 auto transaction = KNSCore::Transaction::install(this, entry, linkId);
380 registerTransaction(transaction);
381 if (!transaction->isFinished()) {
382 ++d->numInstallJobs;
383 }
384}
386{
387 registerTransaction(KNSCore::Transaction::uninstall(this, entry));
388}
389void Engine::registerTransaction(KNSCore::Transaction *transaction)
390{
391 connect(transaction, &KNSCore::Transaction::signalErrorCode, this, &EngineBase::signalErrorCode);
392 connect(transaction, &KNSCore::Transaction::signalMessage, this, &EngineBase::signalMessage);
394}
395
396void Engine::requestMoreData()
397{
398 qCDebug(KNEWSTUFFQUICK) << "Get more data! current page: " << d->currentPage << " requested: " << d->currentRequest.page;
399
400 if (d->currentPage < d->currentRequest.page) {
401 return;
402 }
403
404 d->currentRequest.page++;
405 doRequest();
406}
407void Engine::doRequest()
408{
409 const auto providersList = providers();
410 for (const QSharedPointer<KNSCore::Provider> &p : providersList) {
411 if (p->isInitialized()) {
412 p->loadEntries(d->currentRequest);
413 ++d->numDataJobs;
414 updateStatus();
415 }
416 }
417}
418
419void Engine::revalidateCacheEntries()
420{
421 // This gets called from QML, because in QtQuick we reuse the engine, BUG: 417985
422 // We can't handle this in the cache, because it can't access the configuration of the engine
423 if (cache()) {
424 const auto providersList = providers();
425 for (const auto &provider : providersList) {
426 if (provider && provider->isInitialized()) {
427 const KNSCore::Entry::List cacheBefore = cache()->registryForProvider(provider->id());
428 cache()->removeDeletedEntries();
429 const KNSCore::Entry::List cacheAfter = cache()->registryForProvider(provider->id());
430 // If the user has deleted them in the background we have to update the state to deleted
431 for (const auto &oldCachedEntry : cacheBefore) {
432 if (!cacheAfter.contains(oldCachedEntry)) {
433 KNSCore::Entry removedEntry = oldCachedEntry;
434 removedEntry.setEntryDeleted();
436 }
437 }
438 }
439 }
440 }
441}
442
443void Engine::restoreSearch()
444{
445 d->searchTimer.stop();
446 d->currentRequest = d->storedRequest;
447 if (cache()) {
448 KNSCore::Entry::List cacheEntries = cache()->requestFromCache(d->currentRequest);
449 if (!cacheEntries.isEmpty()) {
450 reloadEntries();
451 } else {
452 d->searchTimer.start();
453 }
454 } else {
455 qCWarning(KNEWSTUFFQUICK) << "Attempted to call restoreSearch() without a correctly initialized engine. You will likely get unexpected behaviour.";
456 }
457}
458
459void Engine::storeSearch()
460{
461 d->storedRequest = d->currentRequest;
462}
463
464#include "moc_quickengine.cpp"
A model which shows the categories found in an Engine.
void signalEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event)
void errorCode(KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata)
Fires in the case of any critical or serious errors, such as network or API problems.
Q_INVOKABLE void install(const KNSCore::Entry &entry, int linkId=1)
Installs an entry's payload file.
Q_INVOKABLE void adoptEntry(const KNSCore::Entry &entry)
Adopt an entry using the adoption command.
Q_SIGNAL void busyStateChanged()
Signal gets emitted when the busy state changes.
BusyState busyState
Current state of the engine, the state con contain multiple operations an empty BusyState represents ...
Definition quickengine.h:50
void addProvider(QSharedPointer< KNSCore::Provider > provider) override
Add a provider and connect it to the right slots.
Q_INVOKABLE void uninstall(const KNSCore::Entry &entry)
Uninstalls an entry.
void signalErrorCode(KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata)
Fires in the case of any critical or serious errors, such as network or API problems.
QSharedPointer< Cache > cache() const
Get the entries cache for this engine (note that it may be null if the engine is not yet initialized)...
QSharedPointer< Provider > provider(const QString &providerId) const
The Provider instance with the passed ID.
KNewStuff data entry container.
Definition entry.h:48
@ StatusChangedEvent
Used when an event's status is set (use Entry::status() to get the new status)
Definition entry.h:122
void setEntryDeleted()
Definition entry.cpp:700
KNewStuff Transaction.
Definition transaction.h:38
static Transaction * install(EngineBase *engine, const Entry &entry, int linkId=1)
Performs an install on the given entry from the engine.
void signalEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event)
Informs about how the entry has changed.
void signalMessage(const QString &message)
Provides the message to update our users about how the Transaction progressed.
void signalErrorCode(KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata)
Fires in the case of any critical or serious errors, such as network or API problems.
static Transaction * adopt(EngineBase *engine, const Entry &entry)
Adopt the entry from engine using the adoption command.
static Transaction * uninstall(EngineBase *engine, const Entry &entry)
Uninstalls the given entry from the engine.
The SearchPresetModel class.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
bool contains(const AT &value) const const
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
used to keep track of a search
Definition provider.h:70
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:12:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.