KNewStuff

atticarequester.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2// SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org>
3// SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
4
5#include "atticarequester_p.h"
6
7#include "commentsmodel.h"
8#include "entry_p.h"
9#include "question.h"
10#include "tagsfilterchecker.h"
11
12#include <KFormat>
13#include <KLocalizedString>
14#include <QCollator>
15#include <QDomDocument>
16#include <knewstuffcore_debug.h>
17
18#include <attica/accountbalance.h>
19#include <attica/config.h>
20#include <attica/content.h>
21#include <attica/downloaditem.h>
22#include <attica/listjob.h>
23#include <attica/person.h>
24#include <attica/provider.h>
25#include <attica/providermanager.h>
26
27#include "atticaprovider_p.h"
28#include "searchrequest_p.h"
29
30using namespace Attica;
31
32namespace
33{
34Attica::Provider::SortMode atticaSortMode(KNSCore::SortMode sortMode)
35{
36 switch (sortMode) {
37 case KNSCore::SortMode::Newest:
38 return Attica::Provider::Newest;
39 case KNSCore::SortMode::Alphabetical:
40 return Attica::Provider::Alphabetical;
41 case KNSCore::SortMode::Downloads:
42 return Attica::Provider::Downloads;
43 case KNSCore::SortMode::Rating:
44 return Attica::Provider::Rating;
45 }
46 qWarning() << "Unmapped sortMode" << sortMode;
47 return Attica::Provider::Rating;
48}
49} // namespace
50
51namespace KNSCore
52{
53
54AtticaRequester::AtticaRequester(const KNSCore::SearchRequest &request, AtticaProvider *provider, QObject *parent)
55 : QObject(parent)
56 , m_request(request)
57 , m_provider(provider)
58{
59}
60
61void AtticaRequester::detailsLoaded(BaseJob *job)
62{
63 if (m_provider->jobSuccess(job)) {
64 auto *contentJob = dynamic_cast<ItemJob<Content> *>(job);
65 Content content = contentJob->result();
66 auto entry = entryFromAtticaContent(content);
67 entry.setEntryRequestedId(job->property("providedEntryId").toString()); // The ResultsStream should still known that this entry was for its query
68 Q_EMIT entryDetailsLoaded(entry);
69 qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name();
70 }
71
72 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
73 qCDebug(KNEWSTUFFCORE) << "check update finished.";
74 QList<Entry> updatable;
75 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
76 if (entry.status() == KNSCore::Entry::Updateable) {
77 updatable.append(entry);
78 }
79 }
80 qDebug() << "UPDATABLE" << updatable;
81 Q_EMIT entriesLoaded(updatable);
82 Q_EMIT loadingDone();
83 }
84}
85
86void AtticaRequester::checkForUpdates()
87{
88 if (m_provider->mCachedEntries.isEmpty()) {
89 Q_EMIT loadingDone();
90 return;
91 }
92
93 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
94 ItemJob<Content> *job = m_provider->m_provider.requestContent(entry.uniqueId());
95 connect(job, &BaseJob::finished, this, &AtticaRequester::detailsLoaded);
96 m_updateJobs.insert(job);
97 job->start();
98 qCDebug(KNEWSTUFFCORE) << "Checking for update: " << entry.name();
99 }
100}
101
102Entry::List AtticaRequester::installedEntries() const
103{
104 Entry::List entries;
105 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
106 if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) {
107 entries.append(entry);
108 }
109 }
110 return entries;
111}
112
113void AtticaRequester::start()
114{
115 QMetaObject::invokeMethod(this, &AtticaRequester::startInternal, Qt::QueuedConnection);
116}
117
118void AtticaRequester::categoryContentsLoaded(BaseJob *job)
119{
120 if (!m_provider->jobSuccess(job)) {
121 return;
122 }
123
124 auto *listJob = dynamic_cast<ListJob<Content> *>(job);
125 const Content::List contents = listJob->itemList();
126
127 Entry::List entries;
128 TagsFilterChecker checker(m_provider->tagFilter());
129 TagsFilterChecker downloadschecker(m_provider->downloadTagFilter());
130 for (const Content &content : contents) {
131 if (!content.isValid()) {
132 qCDebug(KNEWSTUFFCORE)
133 << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
134 << m_provider->name() << "and inform them there is an issue with content in the category or categories" << m_request.d->categories;
135 continue;
136 }
137 if (checker.filterAccepts(content.tags())) {
138 bool filterAcceptsDownloads = true;
139 if (content.downloads() > 0) {
140 filterAcceptsDownloads = false;
141 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
142 for (const Attica::DownloadDescription &dli : descs) {
143 if (downloadschecker.filterAccepts(dli.tags())) {
144 filterAcceptsDownloads = true;
145 break;
146 }
147 }
148 }
149 if (filterAcceptsDownloads) {
150 m_provider->mCachedContent.insert(content.id(), content);
151 entries.append(entryFromAtticaContent(content));
152 } else {
153 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << m_provider->downloadTagFilter();
154 }
155 } else {
156 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << m_provider->tagFilter();
157 }
158 }
159
160 qCDebug(KNEWSTUFFCORE) << "loaded: " << m_request.d->hashForRequest() << " count: " << entries.size();
161 Q_EMIT entriesLoaded(entries);
162 Q_EMIT loadingDone();
163}
164
165void AtticaRequester::startInternal()
166{
167 switch (m_request.d->filter) {
168 case KNSCore::Filter::None:
169 break;
170 case KNSCore::Filter::ExactEntryId: {
171 ItemJob<Content> *job = m_provider->m_provider.requestContent(m_request.d->searchTerm);
172 job->setProperty("providedEntryId", m_request.d->searchTerm);
173 connect(job, &BaseJob::finished, this, &AtticaRequester::detailsLoaded);
174 job->start();
175 return;
176 }
177 case KNSCore::Filter::Installed:
178 if (m_request.d->page == 0) {
179 Q_EMIT entriesLoaded(installedEntries());
180 Q_EMIT loadingDone();
181 } else { // We always load everything on the first page. The caller may fetchMore and try to read further pages though.
182 Q_EMIT loadingDone();
183 }
184 return;
185 case KNSCore::Filter::Updates:
186 checkForUpdates();
187 return;
188 }
189
190 Attica::Provider::SortMode sorting = atticaSortMode(m_request.d->sortMode);
191 Attica::Category::List categoriesToSearch;
192
193 if (m_request.d->categories.isEmpty()) {
194 // search in all categories
195 categoriesToSearch = m_provider->mCategoryMap.values();
196 } else {
197 categoriesToSearch.reserve(m_request.d->categories.size());
198 for (const QString &categoryName : std::as_const(m_request.d->categories)) {
199 categoriesToSearch.append(m_provider->mCategoryMap.values(categoryName));
200 }
201 }
202
203 ListJob<Content> *job =
204 m_provider->m_provider.searchContents(categoriesToSearch, m_request.d->searchTerm, sorting, m_request.d->page, m_request.d->pageSize);
205 job->setProperty("searchRequest", QVariant::fromValue(m_request));
206 connect(job, &BaseJob::finished, this, &AtticaRequester::categoryContentsLoaded);
207 job->start();
208}
209
210Entry AtticaRequester::entryFromAtticaContent(const Attica::Content &content)
211{
212 Entry entry;
213
214 entry.setProviderId(m_provider->id());
215 entry.setUniqueId(content.id());
216 entry.setStatus(KNSCore::Entry::Downloadable);
217 entry.setVersion(content.version());
218 entry.setReleaseDate(content.updated().date());
219 entry.setCategory(content.attribute(QStringLiteral("typeid")));
220
221 qDebug() << "looking for cache entry";
222 auto index = m_provider->mCachedEntries.indexOf(entry);
223 qDebug() << "looking for cache entry" << index;
224 if (index >= 0) {
225 Entry &cacheEntry = m_provider->mCachedEntries[index];
226 qDebug() << "cache entry" << cacheEntry << cacheEntry.version() << entry.version();
227 // check if updateable
228 if (((cacheEntry.status() == KNSCore::Entry::Installed) || (cacheEntry.status() == KNSCore::Entry::Updateable))
229 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
230 cacheEntry.setStatus(KNSCore::Entry::Updateable);
231 cacheEntry.setUpdateVersion(entry.version());
232 cacheEntry.setUpdateReleaseDate(entry.releaseDate());
233 }
234 entry = cacheEntry;
235 } else {
236 m_provider->mCachedEntries.append(entry);
237 }
238
239 entry.setName(content.name());
240 entry.setHomepage(content.detailpage());
241 entry.setRating(content.rating());
242 entry.setNumberOfComments(content.numberOfComments());
243 entry.setDownloadCount(content.downloads());
244 entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt());
245 entry.setDonationLink(content.attribute(QStringLiteral("donationpage")));
246 entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage")));
247 entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt());
248 entry.setHomepage(content.detailpage());
249
250 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), Entry::PreviewSmall1);
251 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), Entry::PreviewSmall2);
252 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), Entry::PreviewSmall3);
253
254 entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), Entry::PreviewBig1);
255 entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), Entry::PreviewBig2);
256 entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), Entry::PreviewBig3);
257
258 entry.setLicense(content.license());
259 Author author;
260 author.setId(content.author());
261 author.setName(content.author());
262 author.setHomepage(content.attribute(QStringLiteral("profilepage")));
263 entry.setAuthor(author);
264
265 entry.setSource(Entry::Online);
266 entry.setSummary(content.description());
267 entry.setShortSummary(content.summary());
268 entry.setChangelog(content.changelog());
269 entry.setTags(content.tags());
270
272 entry.d->mDownloadLinkInformationList.clear();
273 entry.d->mDownloadLinkInformationList.reserve(descs.size());
274 for (const Attica::DownloadDescription &desc : descs) {
275 entry.d->mDownloadLinkInformationList.append({.name = desc.name(),
276 .priceAmount = desc.priceAmount(),
277 .distributionType = desc.distributionType(),
278 .descriptionLink = desc.link(),
279 .id = desc.id(),
280 .isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload,
281 .size = desc.size(),
282 .tags = desc.tags(),
283 .version = desc.version()});
284 }
285
286 return entry;
287}
288
289[[nodiscard]] KNSCore::SearchRequest AtticaRequester::request() const
290{
291 return m_request;
292}
293
294} // namespace KNSCore
QString description() const
int downloads() const
int rating() const
QList< DownloadDescription > downloadUrlDescriptions() const
QStringList tags() const
QString attribute(const QString &key) const
int numberOfComments() const
QUrl detailpage() const
QDateTime updated() const
QString summary() const
QString name() const
QString id() const
A search request.
QDate date() const const
void append(QList< T > &&value)
qsizetype size() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QVariant property(const char *name) const const
bool setProperty(const char *name, QVariant &&value)
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:20:03 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.