1/*
2 SPDX-FileCopyrightText: 2023 Aleix Pol Gonzalez <aleixpol@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "resultsstream.h"
8#include "enginebase_p.h"
9#include "knewstuffcore_debug.h"
10
11#include <iostream>
12
13#include <QLoggingCategory>
14#include <QTimer>
15
16#include "providerbase_p.h"
17#include "providercore.h"
18#include "providercore_p.h"
19#include "searchrequest.h"
20#include "searchrequest_p.h"
21
22using namespace KNSCore;
23
24class KNSCore::ResultsStreamPrivate
25{
26public:
27 QList<QSharedPointer<KNSCore::ProviderCore>> providers;
28 EngineBase const *engine;
29 SearchRequest request;
30 bool finished = false;
31 int queuedFetch = 0;
32};
33
34#if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(6, 9)
35ResultsStream::ResultsStream([[maybe_unused]] const Provider::SearchRequest &request, EngineBase *base)
36 : KNSCore::ResultsStream(SearchRequest(), base)
37{
38 // This ctor should not be used. It is private and we don't use. Nobody else should either. Here for ABI stability.
39 Q_ASSERT(false);
40 qCFatal(KNEWSTUFFCORE, "Do not use private constructors!");
41}
42#endif
43
44ResultsStream::ResultsStream(const SearchRequest &request, EngineBase *base)
45 : d(new ResultsStreamPrivate{
46 .providers = base->d->providerCores.values(),
47 .engine = base,
48 .request = request,
49 })
50{
51 auto entriesLoaded = [this](const KNSCore::SearchRequest &request, const KNSCore::Entry::List &entries) {
52 if (request.d != d->request.d) {
53 return;
54 }
55 Q_EMIT entriesFound(entries);
56 };
57
58 auto done = [this](const KNSCore::SearchRequest &request) {
59 if (request.d != d->request.d) {
60 return;
61 }
62
63 qCDebug(KNEWSTUFFCORE) << this << "Finishing" << sender() << request.d->id;
64
65 auto base = qobject_cast<ProviderBase *>(object: sender());
66 Q_ASSERT_X(base, Q_FUNC_INFO, "Sender failed to cast to ProviderBase");
67 if (const auto coresRemoved = d->providers.removeIf(pred: [base](const auto &core) {
68 return core->d->base == base;
69 });
70 coresRemoved <= 0) {
71 qCWarning(KNEWSTUFFCORE) << "Request finished twice, check your provider" << sender() << d->engine;
72
73 Q_ASSERT(false);
74 return;
75 }
76
77 if (d->providers.isEmpty()) {
78 d->finished = true;
79 if (d->queuedFetch > 0) {
80 d->queuedFetch--;
81 fetchMore();
82 return;
83 }
84
85 d->request = {}; // prevent this stream from making more requests
86 d->finished = true;
87 finish();
88 }
89 };
90 auto failed = [this](const KNSCore::SearchRequest &request) {
91 if (request.d == d->request.d) {
92 finish();
93 }
94 };
95
96 auto seenProviders = d->providers;
97 seenProviders.clear();
98 for (const auto &provider : d->providers) {
99 Q_ASSERT(!seenProviders.contains(provider));
100 seenProviders.append(t: provider);
101
102 connect(sender: provider->d->base, signal: &ProviderBase::entriesLoaded, context: this, slot&: entriesLoaded);
103 connect(sender: provider->d->base, signal: &ProviderBase::loadingDone, context: this, slot&: done);
104 connect(sender: provider->d->base, signal: &ProviderBase::entryDetailsLoaded, context: this, slot: [this](const KNSCore::Entry &entry) {
105 if (d->request.d->filter == KNSCore::Filter::ExactEntryId && d->request.d->searchTerm == entry.uniqueId()) {
106 if (entry.isValid()) {
107 Q_EMIT entriesFound(entries: {entry});
108 }
109 finish();
110 }
111 });
112 connect(sender: provider->d->base, signal: &ProviderBase::loadingFailed, context: this, slot&: failed);
113 }
114}
115
116ResultsStream::~ResultsStream() = default;
117
118void ResultsStream::fetch()
119{
120 if (d->finished) {
121 Q_ASSERT_X(false, Q_FUNC_INFO, "Called fetch on an already finished stream. Call fetchMore.");
122 return;
123 }
124
125 qCDebug(KNEWSTUFFCORE) << this << "fetching" << d->request;
126 if (d->request.d->filter != Filter::Installed) {
127 // when asking for installed entries, never use the cache
128 Entry::List cacheEntries = d->engine->d->cache->requestFromCache(d->request);
129 if (!cacheEntries.isEmpty()) {
130 Q_EMIT entriesFound(entries: cacheEntries);
131 return;
132 }
133 }
134
135 for (const auto &providerCore : std::as_const(t&: d->providers)) {
136 auto provider = providerCore->d->base;
137 qCDebug(KNEWSTUFFCORE) << this << "loading entries from provider" << provider;
138 if (provider->isInitialized()) {
139 QTimer::singleShot(interval: 0, receiver: this, slot: [this, provider] {
140 provider->loadEntries(request: d->request);
141 });
142 } else {
143 connect(sender: provider, signal: &KNSCore::ProviderBase::providerInitialized, context: this, slot: [this, provider] {
144 disconnect(sender: provider, signal: &KNSCore::ProviderBase::providerInitialized, receiver: this, zero: nullptr);
145 provider->loadEntries(request: d->request);
146 });
147 }
148 }
149}
150
151void ResultsStream::fetchMore()
152{
153 // fetchMore requires some extra tinkering but this is worthwhile. By offering a fetchMore we can fully encapsulate
154 // a search state so the caller doesn't have to worry about persisting SearchRequests. Instead we'll do it for them.
155 if (!d->finished) {
156 d->queuedFetch++;
157 return;
158 }
159 d->finished = false;
160 const auto nextPage = d->request.d->page + 1;
161 d->request =
162 SearchRequest(d->request.d->sortMode, d->request.d->filter, d->request.d->searchTerm, d->request.d->categories, nextPage, d->request.d->pageSize);
163 d->providers = d->engine->d->providerCores.values();
164 fetch();
165}
166
167void ResultsStream::finish()
168{
169 Q_EMIT finished();
170 deleteLater();
171}
172
173#include "moc_resultsstream.cpp"
174

source code of knewstuff/src/core/resultsstream.cpp