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 <QQmlInfo> |
17 | #include <QTimer> |
18 | |
19 | #include "categoriesmodel.h" |
20 | #include "quickquestionlistener.h" |
21 | #include "searchpresetmodel.h" |
22 | |
23 | #include "../core/enginebase_p.h" |
24 | #include "../core/providerbase_p.h" |
25 | #include "../core/providercore.h" |
26 | #include "../core/providercore_p.h" |
27 | |
28 | // Could be made :public EngineBasePrivate so we don't have two distinct d pointers |
29 | class EnginePrivate |
30 | { |
31 | public: |
32 | bool isValid = false; |
33 | CategoriesModel *categoriesModel = nullptr; |
34 | SearchPresetModel *searchPresetModel = nullptr; |
35 | QString configFile; |
36 | QTimer searchTimer; |
37 | Engine::BusyState busyState; |
38 | QString busyMessage; |
39 | // the current request from providers |
40 | KNSCore::SearchRequest currentRequest; |
41 | KNSCore::SearchRequest storedRequest; |
42 | // the page that is currently displayed, so it is not requested repeatedly |
43 | int currentPage = -1; |
44 | |
45 | // when requesting entries from a provider, how many to ask for |
46 | int pageSize = 20; |
47 | |
48 | int numDataJobs = 0; |
49 | int numPictureJobs = 0; |
50 | int numInstallJobs = 0; |
51 | }; |
52 | |
53 | Engine::Engine(QObject *parent) |
54 | : KNSCore::EngineBase(parent) |
55 | , d(new EnginePrivate) |
56 | , dd(KNSCore::EngineBase::d.get()) |
57 | { |
58 | connect(sender: this, signal: &KNSCore::EngineBase::providerAdded, context: this, slot: [this](auto core) { |
59 | connect(core->d->base, &KNSCore::ProviderBase::entriesLoaded, this, [this](const auto &request, const auto &entries) { |
60 | d->currentPage = qMax<int>(request.page(), d->currentPage); |
61 | qCDebug(KNEWSTUFFQUICK) << "loaded page " << request.page() << "current page" << d->currentPage << "count:" << entries.count(); |
62 | |
63 | if (request.filter() != KNSCore::Filter::Updates) { |
64 | dd->cache->insertRequest(request, entries); |
65 | } |
66 | Q_EMIT signalEntriesLoaded(entries); |
67 | |
68 | --d->numDataJobs; |
69 | updateStatus(); |
70 | }); |
71 | connect(core->d->base, &KNSCore::ProviderBase::entryDetailsLoaded, this, [this](const auto &entry) { |
72 | --d->numDataJobs; |
73 | updateStatus(); |
74 | Q_EMIT signalEntryEvent(entry, event: KNSCore::Entry::DetailsLoadedEvent); |
75 | }); |
76 | }); |
77 | |
78 | const auto setBusy = [this](Engine::BusyState state, const QString &msg) { |
79 | setBusyState(state); |
80 | d->busyMessage = msg; |
81 | }; |
82 | setBusy(BusyOperation::Initializing, i18n("Loading data" )); // For the user this should be the same as initializing |
83 | |
84 | KNewStuffQuick::QuickQuestionListener::instance(); |
85 | d->categoriesModel = new CategoriesModel(this); |
86 | connect(sender: d->categoriesModel, signal: &QAbstractListModel::modelReset, context: this, slot: &Engine::categoriesChanged); |
87 | d->searchPresetModel = new SearchPresetModel(this); |
88 | connect(sender: d->searchPresetModel, signal: &QAbstractListModel::modelReset, context: this, slot: &Engine::searchPresetModelChanged); |
89 | |
90 | d->searchTimer.setSingleShot(true); |
91 | d->searchTimer.setInterval(1000); |
92 | connect(sender: &d->searchTimer, signal: &QTimer::timeout, context: this, slot: &Engine::reloadEntries); |
93 | connect(sender: installation(), signal: &KNSCore::Installation::signalInstallationFinished, context: this, slot: [this]() { |
94 | --d->numInstallJobs; |
95 | updateStatus(); |
96 | }); |
97 | connect(sender: installation(), signal: &KNSCore::Installation::signalInstallationFailed, context: this, slot: [this](const QString &message) { |
98 | --d->numInstallJobs; |
99 | Q_EMIT signalErrorCode(errorCode: KNSCore::ErrorCode::InstallationError, message, metadata: QVariant()); |
100 | }); |
101 | connect(sender: this, signal: &EngineBase::signalProvidersLoaded, context: this, slot: &Engine::updateStatus); |
102 | connect(sender: this, signal: &EngineBase::signalProvidersLoaded, context: this, slot: [this]() { |
103 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
104 | d->currentRequest.filter(), |
105 | d->currentRequest.searchTerm(), |
106 | EngineBase::categories(), |
107 | d->currentRequest.page(), |
108 | d->currentRequest.pageSize()); |
109 | }); |
110 | |
111 | connect(sender: this, |
112 | signal: &KNSCore::EngineBase::signalErrorCode, |
113 | context: this, |
114 | slot: [setBusy, this](const KNSCore::ErrorCode::ErrorCode &error, const QString &message, const QVariant &metadata) { |
115 | Q_EMIT errorCode(errorCode: error, message, metadata); |
116 | if (error == KNSCore::ErrorCode::ProviderError || error == KNSCore::ErrorCode::ConfigFileError) { |
117 | // This means loading the config or providers file failed entirely and we cannot complete the |
118 | // initialisation. It also means the engine is done loading, but that nothing will |
119 | // work, and we need to inform the user of this. |
120 | setBusy({}, QString()); |
121 | } |
122 | |
123 | // Emit the signal later, currently QML is not connected to the slot |
124 | if (error == KNSCore::ErrorCode::ConfigFileError) { |
125 | QTimer::singleShot(interval: 0, slot: [this, error, message, metadata]() { |
126 | Q_EMIT errorCode(errorCode: error, message, metadata); |
127 | }); |
128 | } |
129 | }); |
130 | |
131 | connect(sender: this, signal: &Engine::signalEntryEvent, context: this, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) { |
132 | // Just forward the event but not do anything more |
133 | Q_EMIT entryEvent(entry, event); |
134 | }); |
135 | // |
136 | // And finally, let's just make sure we don't miss out the various things here getting changed |
137 | // In other words, when we're asked to reset the view, actually do that |
138 | connect(sender: this, signal: &Engine::signalResetView, context: this, slot: &Engine::categoriesFilterChanged); |
139 | connect(sender: this, signal: &Engine::signalResetView, context: this, slot: &Engine::filterChanged); |
140 | connect(sender: this, signal: &Engine::signalResetView, context: this, slot: &Engine::sortOrderChanged); |
141 | connect(sender: this, signal: &Engine::signalResetView, context: this, slot: &Engine::searchTermChanged); |
142 | } |
143 | |
144 | bool Engine::init(const QString &configfile) |
145 | { |
146 | const bool valid = EngineBase::init(configfile); |
147 | if (valid) { |
148 | connect(sender: this, signal: &Engine::signalEntryEvent, context: dd->cache.get(), slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) { |
149 | if (event == KNSCore::Entry::StatusChangedEvent) { |
150 | dd->cache->registerChangedEntry(entry); |
151 | } |
152 | }); |
153 | const auto slotEntryChanged = [this](const KNSCore::Entry &entry) { |
154 | Q_EMIT signalEntryEvent(entry, event: KNSCore::Entry::StatusChangedEvent); |
155 | }; |
156 | // Don't connect KNSCore::Installation::signalEntryChanged as is already forwarded to |
157 | // Transaction, which in turn is forwarded to our slotEntryChanged, so avoids a double emission |
158 | connect(sender: dd->cache.get(), signal: &KNSCore::Cache2::entryChanged, context: this, slot: slotEntryChanged); |
159 | } |
160 | return valid; |
161 | } |
162 | void Engine::updateStatus() |
163 | { |
164 | QString busyMessage; |
165 | BusyState state; |
166 | if (d->numPictureJobs > 0) { |
167 | // If it is loading previews or data is irrelevant for the user |
168 | busyMessage = i18n("Loading data" ); |
169 | state |= BusyOperation::LoadingPreview; |
170 | } |
171 | if (d->numInstallJobs > 0) { |
172 | busyMessage = i18n("Installing" ); |
173 | state |= BusyOperation::InstallingEntry; |
174 | } |
175 | if (d->numDataJobs > 0) { |
176 | busyMessage = i18n("Loading data" ); |
177 | state |= BusyOperation::LoadingData; |
178 | } |
179 | d->busyMessage = busyMessage; |
180 | setBusyState(state); |
181 | } |
182 | |
183 | bool Engine::needsLazyLoadSpinner() |
184 | { |
185 | return d->numDataJobs > 0 || d->numPictureJobs; |
186 | } |
187 | |
188 | Engine::~Engine() = default; |
189 | |
190 | void Engine::setBusyState(BusyState state) |
191 | { |
192 | d->busyState = state; |
193 | Q_EMIT busyStateChanged(); |
194 | } |
195 | Engine::BusyState Engine::busyState() const |
196 | { |
197 | return d->busyState; |
198 | } |
199 | QString Engine::busyMessage() const |
200 | { |
201 | return d->busyMessage; |
202 | } |
203 | |
204 | QString Engine::configFile() const |
205 | { |
206 | return d->configFile; |
207 | } |
208 | |
209 | void Engine::setConfigFile(const QString &newFile) |
210 | { |
211 | if (d->configFile != newFile) { |
212 | d->configFile = newFile; |
213 | Q_EMIT configFileChanged(); |
214 | |
215 | if (KNewStuffQuick::Settings::instance()->allowedByKiosk()) { |
216 | d->isValid = init(configfile: newFile); |
217 | Q_EMIT categoriesFilterChanged(); |
218 | Q_EMIT filterChanged(); |
219 | Q_EMIT sortOrderChanged(); |
220 | Q_EMIT searchTermChanged(); |
221 | } else { |
222 | // 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 |
223 | // nothing the user can do to fix it, and we just tell them so they're not wondering what's wrong) |
224 | Q_EMIT errorCode( |
225 | errorCode: KNSCore::ErrorCode::ConfigFileError, |
226 | i18nc("An informational message which is shown to inform the user they are not authorized to use GetHotNewStuff functionality" , |
227 | "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." ), |
228 | metadata: QVariant()); |
229 | } |
230 | } |
231 | } |
232 | |
233 | CategoriesModel *Engine::categories() const |
234 | { |
235 | return d->categoriesModel; |
236 | } |
237 | |
238 | QStringList Engine::categoriesFilter() const |
239 | { |
240 | return d->currentRequest.categories(); |
241 | } |
242 | |
243 | void Engine::setCategoriesFilter(const QStringList &newCategoriesFilter) |
244 | { |
245 | if (d->currentRequest.categories() != newCategoriesFilter) { |
246 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
247 | d->currentRequest.filter(), |
248 | d->currentRequest.searchTerm(), |
249 | newCategoriesFilter, |
250 | d->currentRequest.page(), |
251 | d->currentRequest.pageSize()); |
252 | reloadEntries(); |
253 | Q_EMIT categoriesFilterChanged(); |
254 | } |
255 | } |
256 | |
257 | #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9) |
258 | KNSCore::Provider::Filter Engine::filter() const |
259 | { |
260 | return [filter = filter2()] { |
261 | switch (filter) { |
262 | case KNSCore::Filter::None: |
263 | return KNSCore::Provider::None; |
264 | case KNSCore::Filter::Installed: |
265 | return KNSCore::Provider::Installed; |
266 | case KNSCore::Filter::Updates: |
267 | return KNSCore::Provider::Updates; |
268 | case KNSCore::Filter::ExactEntryId: |
269 | return KNSCore::Provider::ExactEntryId; |
270 | } |
271 | return KNSCore::Provider::None; |
272 | }(); |
273 | } |
274 | #endif |
275 | |
276 | #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9) |
277 | void Engine::setFilter(KNSCore::Provider::Filter newFilter_) |
278 | { |
279 | setFilter2([newFilter_] { |
280 | switch (newFilter_) { |
281 | case KNSCore::Provider::None: |
282 | return KNSCore::Filter::None; |
283 | case KNSCore::Provider::Installed: |
284 | return KNSCore::Filter::Installed; |
285 | case KNSCore::Provider::Updates: |
286 | return KNSCore::Filter::Updates; |
287 | case KNSCore::Provider::ExactEntryId: |
288 | return KNSCore::Filter::ExactEntryId; |
289 | } |
290 | return KNSCore::Filter::None; |
291 | }()); |
292 | } |
293 | #endif |
294 | |
295 | KNSCore::Filter Engine::filter2() const |
296 | { |
297 | return d->currentRequest.filter(); |
298 | } |
299 | |
300 | void Engine::setFilter2(KNSCore::Filter newFilter) |
301 | { |
302 | if (d->currentRequest.filter() != newFilter) { |
303 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
304 | newFilter, |
305 | d->currentRequest.searchTerm(), |
306 | d->currentRequest.categories(), |
307 | d->currentRequest.page(), |
308 | d->currentRequest.pageSize()); |
309 | reloadEntries(); |
310 | Q_EMIT filterChanged(); |
311 | } |
312 | } |
313 | |
314 | #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9) |
315 | KNSCore::Provider::SortMode Engine::sortOrder() const |
316 | { |
317 | return [mode = sortOrder2()] { |
318 | switch (mode) { |
319 | case KNSCore::SortMode::Newest: |
320 | return KNSCore::Provider::Newest; |
321 | case KNSCore::SortMode::Alphabetical: |
322 | return KNSCore::Provider::Alphabetical; |
323 | case KNSCore::SortMode::Rating: |
324 | return KNSCore::Provider::Rating; |
325 | case KNSCore::SortMode::Downloads: |
326 | return KNSCore::Provider::Downloads; |
327 | } |
328 | return KNSCore::Provider::Rating; |
329 | }(); |
330 | } |
331 | #endif |
332 | |
333 | #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9) |
334 | void Engine::setSortOrder(KNSCore::Provider::SortMode mode_) |
335 | { |
336 | setSortOrder2([mode_] { |
337 | switch (mode_) { |
338 | case KNSCore::Provider::Newest: |
339 | return KNSCore::SortMode::Newest; |
340 | case KNSCore::Provider::Alphabetical: |
341 | return KNSCore::SortMode::Alphabetical; |
342 | case KNSCore::Provider::Rating: |
343 | return KNSCore::SortMode::Rating; |
344 | case KNSCore::Provider::Downloads: |
345 | return KNSCore::SortMode::Downloads; |
346 | } |
347 | return KNSCore::SortMode::Rating; |
348 | }()); |
349 | } |
350 | #endif |
351 | |
352 | KNSCore::SortMode Engine::sortOrder2() const |
353 | { |
354 | return d->currentRequest.sortMode(); |
355 | } |
356 | |
357 | void Engine::setSortOrder2(KNSCore::SortMode mode) |
358 | { |
359 | if (d->currentRequest.sortMode() != mode) { |
360 | d->currentRequest = KNSCore::SearchRequest(mode, |
361 | d->currentRequest.filter(), |
362 | d->currentRequest.searchTerm(), |
363 | d->currentRequest.categories(), |
364 | d->currentRequest.page(), |
365 | d->currentRequest.pageSize()); |
366 | reloadEntries(); |
367 | Q_EMIT sortOrderChanged(); |
368 | } |
369 | } |
370 | |
371 | QString Engine::searchTerm() const |
372 | { |
373 | return d->currentRequest.searchTerm(); |
374 | } |
375 | |
376 | void Engine::setSearchTerm(const QString &searchTerm) |
377 | { |
378 | if (d->isValid && d->currentRequest.searchTerm() != searchTerm) { |
379 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
380 | d->currentRequest.filter(), |
381 | searchTerm, |
382 | d->currentRequest.categories(), |
383 | d->currentRequest.page(), |
384 | d->currentRequest.pageSize()); |
385 | Q_EMIT searchTermChanged(); |
386 | } |
387 | KNSCore::Entry::List cacheEntries = dd->cache->requestFromCache(d->currentRequest); |
388 | if (!cacheEntries.isEmpty()) { |
389 | reloadEntries(); |
390 | } else { |
391 | d->searchTimer.start(); |
392 | } |
393 | } |
394 | |
395 | SearchPresetModel *Engine::searchPresetModel() const |
396 | { |
397 | return d->searchPresetModel; |
398 | } |
399 | |
400 | bool Engine::isValid() |
401 | { |
402 | return d->isValid; |
403 | } |
404 | |
405 | void Engine::updateEntryContents(const KNSCore::Entry &entry) |
406 | { |
407 | const auto core = dd->providerCores.value(key: entry.providerId()); |
408 | if (!core) { |
409 | qCWarning(KNEWSTUFFQUICK) << "Provider was not found" << entry.providerId(); |
410 | return; |
411 | } |
412 | |
413 | const auto base = core->d->base; |
414 | if (!base->isInitialized()) { |
415 | qCWarning(KNEWSTUFFQUICK) << "Provider was not initialized" << base << entry.providerId(); |
416 | return; |
417 | } |
418 | |
419 | base->loadEntryDetails(entry); |
420 | } |
421 | |
422 | void Engine::reloadEntries() |
423 | { |
424 | Q_EMIT signalResetView(); |
425 | d->currentPage = -1; |
426 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
427 | d->currentRequest.filter(), |
428 | d->currentRequest.searchTerm(), |
429 | d->currentRequest.categories(), |
430 | 0, |
431 | d->currentRequest.pageSize()); |
432 | d->numDataJobs = 0; |
433 | |
434 | const auto providersList = dd->providerCores; |
435 | for (const auto &core : providersList) { |
436 | const auto &base = core->d->base; |
437 | if (base->isInitialized()) { |
438 | if (d->currentRequest.filter() == KNSCore::Filter::Installed || d->currentRequest.filter() == KNSCore::Filter::Updates) { |
439 | // when asking for installed entries, never use the cache |
440 | base->loadEntries(request: d->currentRequest); |
441 | } else { |
442 | // take entries from cache until there are no more |
443 | KNSCore::Entry::List cacheEntries; |
444 | KNSCore::Entry::List lastCache = dd->cache->requestFromCache(d->currentRequest); |
445 | while (!lastCache.isEmpty()) { |
446 | qCDebug(KNEWSTUFFQUICK) << "From cache" ; |
447 | cacheEntries << lastCache; |
448 | |
449 | d->currentPage = d->currentRequest.page(); |
450 | d->currentRequest = d->currentRequest.nextPage(); |
451 | lastCache = dd->cache->requestFromCache(d->currentRequest); |
452 | } |
453 | |
454 | // Since the cache has no more pages, reset the request's page |
455 | if (d->currentPage >= 0) { |
456 | d->currentRequest = KNSCore::SearchRequest(d->currentRequest.sortMode(), |
457 | d->currentRequest.filter(), |
458 | d->currentRequest.searchTerm(), |
459 | d->currentRequest.categories(), |
460 | d->currentPage, |
461 | d->currentRequest.pageSize()); |
462 | } |
463 | |
464 | if (!cacheEntries.isEmpty()) { |
465 | Q_EMIT signalEntriesLoaded(entries: cacheEntries); |
466 | } else { |
467 | qCDebug(KNEWSTUFFQUICK) << "From provider" ; |
468 | base->loadEntries(request: d->currentRequest); |
469 | |
470 | ++d->numDataJobs; |
471 | updateStatus(); |
472 | } |
473 | } |
474 | } |
475 | } |
476 | } |
477 | |
478 | void Engine::loadPreview(const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type) |
479 | { |
480 | qCDebug(KNEWSTUFFQUICK) << "START preview: " << entry.name() << type; |
481 | auto l = new KNSCore::ImageLoader(entry, type, this); |
482 | connect(sender: l, signal: &KNSCore::ImageLoader::signalPreviewLoaded, context: this, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type) { |
483 | qCDebug(KNEWSTUFFQUICK) << "FINISH preview: " << entry.name() << type; |
484 | Q_EMIT signalEntryPreviewLoaded(entry, type); |
485 | --d->numPictureJobs; |
486 | updateStatus(); |
487 | }); |
488 | connect(sender: l, signal: &KNSCore::ImageLoader::signalError, context: this, slot: [this](const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type, const QString &errorText) { |
489 | Q_EMIT signalErrorCode(errorCode: KNSCore::ErrorCode::ImageError, message: errorText, metadata: QVariantList() << entry.name() << type); |
490 | qCDebug(KNEWSTUFFQUICK) << "ERROR preview: " << errorText << entry.name() << type; |
491 | --d->numPictureJobs; |
492 | updateStatus(); |
493 | }); |
494 | l->start(); |
495 | ++d->numPictureJobs; |
496 | updateStatus(); |
497 | } |
498 | |
499 | void Engine::adoptEntry(const KNSCore::Entry &entry) |
500 | { |
501 | registerTransaction(transactions: KNSCore::Transaction::adopt(engine: this, entry)); |
502 | } |
503 | |
504 | #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9) |
505 | void Engine::install(const KNSCore::Entry &entry, int linkId) |
506 | { |
507 | qmlWarning(me: this) << "org.kde.newstuff.core.Engine.install is deprecated. Use installLinkId or installLatest" ; |
508 | auto transaction = KNSCore::Transaction::install(engine: this, entry, linkId); |
509 | registerTransaction(transactions: transaction); |
510 | if (!transaction->isFinished()) { |
511 | ++d->numInstallJobs; |
512 | } |
513 | } |
514 | #endif |
515 | |
516 | void Engine::installLinkId(const KNSCore::Entry &entry, quint8 linkId) |
517 | { |
518 | auto transaction = KNSCore::Transaction::installLinkId(engine: this, entry, linkId); |
519 | registerTransaction(transactions: transaction); |
520 | if (!transaction->isFinished()) { |
521 | ++d->numInstallJobs; |
522 | } |
523 | } |
524 | |
525 | void Engine::installLatest(const KNSCore::Entry &entry) |
526 | { |
527 | auto transaction = KNSCore::Transaction::installLatest(engine: this, entry); |
528 | registerTransaction(transactions: transaction); |
529 | if (!transaction->isFinished()) { |
530 | ++d->numInstallJobs; |
531 | } |
532 | } |
533 | |
534 | void Engine::uninstall(const KNSCore::Entry &entry) |
535 | { |
536 | registerTransaction(transactions: KNSCore::Transaction::uninstall(engine: this, entry)); |
537 | } |
538 | void Engine::registerTransaction(KNSCore::Transaction *transaction) |
539 | { |
540 | connect(sender: transaction, signal: &KNSCore::Transaction::signalErrorCode, context: this, slot: &EngineBase::signalErrorCode); |
541 | connect(sender: transaction, signal: &KNSCore::Transaction::signalMessage, context: this, slot: &EngineBase::signalMessage); |
542 | connect(sender: transaction, signal: &KNSCore::Transaction::signalEntryEvent, context: this, slot: &Engine::signalEntryEvent); |
543 | } |
544 | |
545 | void Engine::requestMoreData() |
546 | { |
547 | qCDebug(KNEWSTUFFQUICK) << "Get more data! current page: " << d->currentPage << " requested: " << d->currentRequest.page(); |
548 | |
549 | if (d->currentPage < d->currentRequest.page()) { |
550 | return; |
551 | } |
552 | |
553 | d->currentRequest = d->currentRequest.nextPage(); |
554 | doRequest(); |
555 | } |
556 | |
557 | void Engine::doRequest() |
558 | { |
559 | const auto cores = dd->providerCores; |
560 | for (const auto &core : cores) { |
561 | const auto &base = core->d->base; |
562 | if (base->isInitialized()) { |
563 | base->loadEntries(request: d->currentRequest); |
564 | ++d->numDataJobs; |
565 | updateStatus(); |
566 | } |
567 | } |
568 | } |
569 | |
570 | void Engine::revalidateCacheEntries() |
571 | { |
572 | // This gets called from QML, because in QtQuick we reuse the engine, BUG: 417985 |
573 | // We can't handle this in the cache, because it can't access the configuration of the engine |
574 | if (dd->cache) { |
575 | const auto cores = dd->providerCores; |
576 | for (const auto &core : cores) { |
577 | const auto &base = core->d->base; |
578 | if (base && base->isInitialized()) { |
579 | const KNSCore::Entry::List cacheBefore = dd->cache->registryForProvider(providerId: base->id()); |
580 | dd->cache->removeDeletedEntries(); |
581 | const KNSCore::Entry::List cacheAfter = dd->cache->registryForProvider(providerId: base->id()); |
582 | // If the user has deleted them in the background we have to update the state to deleted |
583 | for (const auto &oldCachedEntry : cacheBefore) { |
584 | if (!cacheAfter.contains(t: oldCachedEntry)) { |
585 | KNSCore::Entry removedEntry = oldCachedEntry; |
586 | removedEntry.setEntryDeleted(); |
587 | Q_EMIT signalEntryEvent(entry: removedEntry, event: KNSCore::Entry::StatusChangedEvent); |
588 | } |
589 | } |
590 | } |
591 | } |
592 | } |
593 | } |
594 | |
595 | void Engine::restoreSearch() |
596 | { |
597 | d->searchTimer.stop(); |
598 | d->currentRequest = d->storedRequest; |
599 | if (dd->cache) { |
600 | KNSCore::Entry::List cacheEntries = dd->cache->requestFromCache(d->currentRequest); |
601 | if (!cacheEntries.isEmpty()) { |
602 | reloadEntries(); |
603 | } else { |
604 | d->searchTimer.start(); |
605 | } |
606 | } else { |
607 | qCWarning(KNEWSTUFFQUICK) << "Attempted to call restoreSearch() without a correctly initialized engine. You will likely get unexpected behaviour." ; |
608 | } |
609 | } |
610 | |
611 | void Engine::storeSearch() |
612 | { |
613 | d->storedRequest = d->currentRequest; |
614 | } |
615 | |
616 | #include "moc_quickengine.cpp" |
617 | |