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 connect(installation(), &KNSCore::Installation::signalEntryChanged, this, slotEntryChanged);
124 connect(cache().data(), &KNSCore::Cache::entryChanged, this, slotEntryChanged);
125 }
126 return valid;
127}
128void Engine::updateStatus()
129{
130 QString busyMessage;
131 BusyState state;
132 if (d->numPictureJobs > 0) {
133 // If it is loading previews or data is irrelevant for the user
134 busyMessage = i18n("Loading data");
135 state |= BusyOperation::LoadingPreview;
136 }
137 if (d->numInstallJobs > 0) {
138 busyMessage = i18n("Installing");
139 state |= BusyOperation::InstallingEntry;
140 }
141 if (d->numDataJobs > 0) {
142 busyMessage = i18n("Loading data");
143 state |= BusyOperation::LoadingData;
144 }
145 d->busyMessage = busyMessage;
146 setBusyState(state);
147}
148
149bool Engine::needsLazyLoadSpinner()
150{
151 return d->numDataJobs > 0 || d->numPictureJobs;
152}
153
154Engine::~Engine() = default;
155
156void Engine::setBusyState(BusyState state)
157{
158 d->busyState = state;
160}
162{
163 return d->busyState;
164}
165QString Engine::busyMessage() const
166{
167 return d->busyMessage;
168}
169
170QString Engine::configFile() const
171{
172 return d->configFile;
173}
174
175void Engine::setConfigFile(const QString &newFile)
176{
177 if (d->configFile != newFile) {
178 d->configFile = newFile;
179 Q_EMIT configFileChanged();
180
181 if (KNewStuffQuick::Settings::instance()->allowedByKiosk()) {
182 d->isValid = init(newFile);
183 Q_EMIT categoriesFilterChanged();
184 Q_EMIT filterChanged();
185 Q_EMIT sortOrderChanged();
186 Q_EMIT searchTermChanged();
187 } else {
188 // 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
189 // nothing the user can do to fix it, and we just tell them so they're not wondering what's wrong)
191 KNSCore::ErrorCode::ConfigFileError,
192 i18nc("An informational message which is shown to inform the user they are not authorized to use GetHotNewStuff functionality",
193 "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."),
194 QVariant());
195 }
196 }
197}
198
199QObject *Engine::categories() const
200{
201 return d->categoriesModel;
202}
203
204QStringList Engine::categoriesFilter() const
205{
206 return d->currentRequest.categories;
207}
208
209void Engine::setCategoriesFilter(const QStringList &newCategoriesFilter)
210{
211 if (d->currentRequest.categories != newCategoriesFilter) {
212 d->currentRequest.categories = newCategoriesFilter;
213 reloadEntries();
214 Q_EMIT categoriesFilterChanged();
215 }
216}
217
218KNSCore::Provider::Filter Engine::filter() const
219{
220 return d->currentRequest.filter;
221}
222
223void Engine::setFilter(KNSCore::Provider::Filter newFilter)
224{
225 if (d->currentRequest.filter != newFilter) {
226 d->currentRequest.filter = newFilter;
227 reloadEntries();
228 Q_EMIT filterChanged();
229 }
230}
231
232KNSCore::Provider::SortMode Engine::sortOrder() const
233{
234 return d->currentRequest.sortMode;
235}
236
237void Engine::setSortOrder(KNSCore::Provider::SortMode mode)
238{
239 if (d->currentRequest.sortMode != mode) {
240 d->currentRequest.sortMode = mode;
241 reloadEntries();
242 Q_EMIT sortOrderChanged();
243 }
244}
245
246QString Engine::searchTerm() const
247{
248 return d->currentRequest.searchTerm;
249}
250
251void Engine::setSearchTerm(const QString &searchTerm)
252{
253 if (d->isValid && d->currentRequest.searchTerm != searchTerm) {
254 d->currentRequest.searchTerm = searchTerm;
255 Q_EMIT searchTermChanged();
256 }
257 KNSCore::Entry::List cacheEntries = cache()->requestFromCache(d->currentRequest);
258 if (!cacheEntries.isEmpty()) {
259 reloadEntries();
260 } else {
261 d->searchTimer.start();
262 }
263}
264
265QObject *Engine::searchPresetModel() const
266{
267 return d->searchPresetModel;
268}
269
270bool Engine::isValid()
271{
272 return d->isValid;
273}
274
275void Engine::updateEntryContents(const KNSCore::Entry &entry)
276{
277 const auto provider = EngineBase::provider(entry.providerId());
278 if (provider.isNull() || !provider->isInitialized()) {
279 qCWarning(KNEWSTUFFQUICK) << "Provider was not found or is not initialized" << provider << entry.providerId();
280 return;
281 }
282 provider->loadEntryDetails(entry);
283}
284
285void Engine::reloadEntries()
286{
287 Q_EMIT signalResetView();
288 d->currentPage = -1;
289 d->currentRequest.page = 0;
290 d->numDataJobs = 0;
291
292 const auto providersList = EngineBase::providers();
294 if (p->isInitialized()) {
295 if (d->currentRequest.filter == KNSCore::Provider::Installed || d->currentRequest.filter == KNSCore::Provider::Updates) {
296 // when asking for installed entries, never use the cache
297 p->loadEntries(d->currentRequest);
298 } else {
299 // take entries from cache until there are no more
301 KNSCore::Entry::List lastCache = cache()->requestFromCache(d->currentRequest);
302 while (!lastCache.isEmpty()) {
303 qCDebug(KNEWSTUFFQUICK) << "From cache";
305
306 d->currentPage = d->currentRequest.page;
307 ++d->currentRequest.page;
308 lastCache = cache()->requestFromCache(d->currentRequest);
309 }
310
311 // Since the cache has no more pages, reset the request's page
312 if (d->currentPage >= 0) {
313 d->currentRequest.page = d->currentPage;
314 }
315
316 if (!cacheEntries.isEmpty()) {
317 Q_EMIT signalEntriesLoaded(cacheEntries);
318 } else {
319 qCDebug(KNEWSTUFFQUICK) << "From provider";
320 p->loadEntries(d->currentRequest);
321
322 ++d->numDataJobs;
323 updateStatus();
324 }
325 }
326 }
327 }
328}
330{
331 EngineBase::addProvider(provider);
332 connect(provider.data(), &KNSCore::Provider::loadingFinished, this, [this](const auto &request, const auto &entries) {
333 d->currentPage = qMax<int>(request.page, d->currentPage);
334 qCDebug(KNEWSTUFFQUICK) << "loaded page " << request.page << "current page" << d->currentPage << "count:" << entries.count();
335
336 if (request.filter != KNSCore::Provider::Updates) {
337 cache()->insertRequest(request, entries);
338 }
339 Q_EMIT signalEntriesLoaded(entries);
340
341 --d->numDataJobs;
342 updateStatus();
343 });
344 connect(provider.data(), &KNSCore::Provider::entryDetailsLoaded, this, [this](const auto &entry) {
345 --d->numDataJobs;
346 updateStatus();
347 Q_EMIT signalEntryEvent(entry, KNSCore::Entry::DetailsLoadedEvent);
348 });
349}
350
351void Engine::loadPreview(const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type)
352{
353 qCDebug(KNEWSTUFFQUICK) << "START preview: " << entry.name() << type;
354 auto l = new KNSCore::ImageLoader(entry, type, this);
355 connect(l, &KNSCore::ImageLoader::signalPreviewLoaded, this, [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type) {
356 qCDebug(KNEWSTUFFQUICK) << "FINISH preview: " << entry.name() << type;
357 Q_EMIT signalEntryPreviewLoaded(entry, type);
358 --d->numPictureJobs;
359 updateStatus();
360 });
361 connect(l, &KNSCore::ImageLoader::signalError, this, [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type, const QString &errorText) {
362 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ImageError, errorText, QVariantList() << entry.name() << type);
363 qCDebug(KNEWSTUFFQUICK) << "ERROR preview: " << errorText << entry.name() << type;
364 --d->numPictureJobs;
365 updateStatus();
366 });
367 l->start();
368 ++d->numPictureJobs;
369 updateStatus();
370}
371
373{
374 registerTransaction(KNSCore::Transaction::adopt(this, entry));
375}
376void Engine::install(const KNSCore::Entry &entry, int linkId)
377{
378 auto transaction = KNSCore::Transaction::install(this, entry, linkId);
379 registerTransaction(transaction);
380 if (!transaction->isFinished()) {
381 ++d->numInstallJobs;
382 }
383}
385{
386 registerTransaction(KNSCore::Transaction::uninstall(this, entry));
387}
388void Engine::registerTransaction(KNSCore::Transaction *transaction)
389{
390 connect(transaction, &KNSCore::Transaction::signalErrorCode, this, &EngineBase::signalErrorCode);
391 connect(transaction, &KNSCore::Transaction::signalMessage, this, &EngineBase::signalMessage);
393}
394
395void Engine::requestMoreData()
396{
397 qCDebug(KNEWSTUFFQUICK) << "Get more data! current page: " << d->currentPage << " requested: " << d->currentRequest.page;
398
399 if (d->currentPage < d->currentRequest.page) {
400 return;
401 }
402
403 d->currentRequest.page++;
404 doRequest();
405}
406void Engine::doRequest()
407{
408 const auto providersList = providers();
410 if (p->isInitialized()) {
411 p->loadEntries(d->currentRequest);
412 ++d->numDataJobs;
413 updateStatus();
414 }
415 }
416}
417
418void Engine::revalidateCacheEntries()
419{
420 // This gets called from QML, because in QtQuick we reuse the engine, BUG: 417985
421 // We can't handle this in the cache, because it can't access the configuration of the engine
422 if (cache()) {
423 const auto providersList = providers();
424 for (const auto &provider : providersList) {
425 if (provider && provider->isInitialized()) {
426 const KNSCore::Entry::List cacheBefore = cache()->registryForProvider(provider->id());
427 cache()->removeDeletedEntries();
428 const KNSCore::Entry::List cacheAfter = cache()->registryForProvider(provider->id());
429 // If the user has deleted them in the background we have to update the state to deleted
430 for (const auto &oldCachedEntry : cacheBefore) {
431 if (!cacheAfter.contains(oldCachedEntry)) {
433 removedEntry.setEntryDeleted();
435 }
436 }
437 }
438 }
439 }
440}
441
442void Engine::restoreSearch()
443{
444 d->searchTimer.stop();
445 d->currentRequest = d->storedRequest;
446 if (cache()) {
447 KNSCore::Entry::List cacheEntries = cache()->requestFromCache(d->currentRequest);
448 if (!cacheEntries.isEmpty()) {
449 reloadEntries();
450 } else {
451 d->searchTimer.start();
452 }
453 } else {
454 qCWarning(KNEWSTUFFQUICK) << "Attempted to call restoreSearch() without a correctly initialized engine. You will likely get unexpected behaviour.";
455 }
456}
457
458void Engine::storeSearch()
459{
460 d->storedRequest = d->currentRequest;
461}
462
463#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:48
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
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...)
Type type(const QSqlDatabase &db)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
T * data() const const
bool isNull() const const
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 Tue Mar 26 2024 11:21:35 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.