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
29using namespace Attica;
30
31namespace
32{
33Attica::Provider::SortMode atticaSortMode(KNSCore::Provider::SortMode sortMode)
34{
35 switch (sortMode) {
36 case KNSCore::Provider::SortMode::Newest:
37 return Attica::Provider::Newest;
38 case KNSCore::Provider::SortMode::Alphabetical:
39 return Attica::Provider::Alphabetical;
40 case KNSCore::Provider::SortMode::Downloads:
41 return Attica::Provider::Downloads;
42 case KNSCore::Provider::SortMode::Rating:
43 return Attica::Provider::Rating;
44 }
45 qWarning() << "Unmapped sortMode" << sortMode;
46 return Attica::Provider::Rating;
47}
48} // namespace
49
50namespace KNSCore
51{
52
53AtticaRequester::AtticaRequester(const KNSCore::Provider::SearchRequest &request, AtticaProvider *provider, QObject *parent)
54 : QObject(parent)
55 , m_request(request)
56 , m_provider(provider)
57{
58}
59
60void AtticaRequester::detailsLoaded(BaseJob *job)
61{
62 if (m_provider->jobSuccess(job)) {
63 auto *contentJob = dynamic_cast<ItemJob<Content> *>(job);
64 Content content = contentJob->result();
65 auto entry = entryFromAtticaContent(content);
66 entry.setEntryRequestedId(job->property("providedEntryId").toString()); // The ResultsStream should still known that this entry was for its query
67 Q_EMIT entryDetailsLoaded(entry);
68 qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name();
69 }
70
71 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
72 qCDebug(KNEWSTUFFCORE) << "check update finished.";
73 QList<Entry> updatable;
74 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
75 if (entry.status() == KNSCore::Entry::Updateable) {
76 updatable.append(entry);
77 }
78 }
79 qDebug() << "UPDATABLE" << updatable;
80 Q_EMIT loadingFinished(updatable);
81 }
82}
83
84void AtticaRequester::checkForUpdates()
85{
86 if (m_provider->mCachedEntries.isEmpty()) {
87 Q_EMIT loadingFinished({});
88 return;
89 }
90
91 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
92 ItemJob<Content> *job = m_provider->m_provider.requestContent(entry.uniqueId());
93 connect(job, &BaseJob::finished, this, &AtticaRequester::detailsLoaded);
94 m_updateJobs.insert(job);
95 job->start();
96 qCDebug(KNEWSTUFFCORE) << "Checking for update: " << entry.name();
97 }
98}
99
100Entry::List AtticaRequester::installedEntries() const
101{
102 Entry::List entries;
103 for (const Entry &entry : std::as_const(m_provider->mCachedEntries)) {
104 if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) {
105 entries.append(entry);
106 }
107 }
108 return entries;
109}
110
111void AtticaRequester::start()
112{
113 QMetaObject::invokeMethod(this, &AtticaRequester::startInternal, Qt::QueuedConnection);
114}
115
116void AtticaRequester::categoryContentsLoaded(BaseJob *job)
117{
118 if (!m_provider->jobSuccess(job)) {
119 return;
120 }
121
122 auto *listJob = dynamic_cast<ListJob<Content> *>(job);
123 const Content::List contents = listJob->itemList();
124
125 Entry::List entries;
126 TagsFilterChecker checker(m_provider->tagFilter());
127 TagsFilterChecker downloadschecker(m_provider->downloadTagFilter());
128 for (const Content &content : contents) {
129 if (!content.isValid()) {
130 qCDebug(KNEWSTUFFCORE)
131 << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
132 << m_provider->name() << "and inform them there is an issue with content in the category or categories" << m_request.categories;
133 continue;
134 }
135 if (checker.filterAccepts(content.tags())) {
136 bool filterAcceptsDownloads = true;
137 if (content.downloads() > 0) {
138 filterAcceptsDownloads = false;
139 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
140 for (const Attica::DownloadDescription &dli : descs) {
141 if (downloadschecker.filterAccepts(dli.tags())) {
142 filterAcceptsDownloads = true;
143 break;
144 }
145 }
146 }
147 if (filterAcceptsDownloads) {
148 m_provider->mCachedContent.insert(content.id(), content);
149 entries.append(entryFromAtticaContent(content));
150 } else {
151 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << m_provider->downloadTagFilter();
152 }
153 } else {
154 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << m_provider->tagFilter();
155 }
156 }
157
158 qCDebug(KNEWSTUFFCORE) << "loaded: " << m_request.hashForRequest() << " count: " << entries.size();
159 Q_EMIT loadingFinished(entries);
160}
161
162void AtticaRequester::startInternal()
163{
164 switch (m_request.filter) {
165 case KNSCore::Provider::None:
166 break;
167 case KNSCore::Provider::ExactEntryId: {
168 ItemJob<Content> *job = m_provider->m_provider.requestContent(m_request.searchTerm);
169 job->setProperty("providedEntryId", m_request.searchTerm);
170 connect(job, &BaseJob::finished, this, &AtticaRequester::detailsLoaded);
171 job->start();
172 return;
173 }
174 case KNSCore::Provider::Installed:
175 if (m_request.page == 0) {
176 Q_EMIT loadingFinished(installedEntries());
177 } else {
178 Q_EMIT loadingFinished({});
179 }
180 return;
181 case KNSCore::Provider::Updates:
182 checkForUpdates();
183 return;
184 }
185
186 Attica::Provider::SortMode sorting = atticaSortMode(m_request.sortMode);
187 Attica::Category::List categoriesToSearch;
188
189 if (m_request.categories.isEmpty()) {
190 // search in all categories
191 categoriesToSearch = m_provider->mCategoryMap.values();
192 } else {
193 categoriesToSearch.reserve(m_request.categories.size());
194 for (const QString &categoryName : std::as_const(m_request.categories)) {
195 categoriesToSearch.append(m_provider->mCategoryMap.values(categoryName));
196 }
197 }
198
199 ListJob<Content> *job = m_provider->m_provider.searchContents(categoriesToSearch, m_request.searchTerm, sorting, m_request.page, m_request.pageSize);
200 job->setProperty("searchRequest", QVariant::fromValue(m_request));
201 connect(job, &BaseJob::finished, this, &AtticaRequester::categoryContentsLoaded);
202 job->start();
203}
204
205Entry AtticaRequester::entryFromAtticaContent(const Attica::Content &content)
206{
207 Entry entry;
208
209 entry.setProviderId(m_provider->id());
210 entry.setUniqueId(content.id());
211 entry.setStatus(KNSCore::Entry::Downloadable);
212 entry.setVersion(content.version());
213 entry.setReleaseDate(content.updated().date());
214 entry.setCategory(content.attribute(QStringLiteral("typeid")));
215
216 qDebug() << "looking for cache entry";
217 auto index = m_provider->mCachedEntries.indexOf(entry);
218 qDebug() << "looking for cache entry" << index;
219 if (index >= 0) {
220 Entry &cacheEntry = m_provider->mCachedEntries[index];
221 qDebug() << "cache entry" << cacheEntry << cacheEntry.version() << entry.version();
222 // check if updateable
223 if (((cacheEntry.status() == KNSCore::Entry::Installed) || (cacheEntry.status() == KNSCore::Entry::Updateable))
224 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
225 cacheEntry.setStatus(KNSCore::Entry::Updateable);
226 cacheEntry.setUpdateVersion(entry.version());
227 cacheEntry.setUpdateReleaseDate(entry.releaseDate());
228 }
229 entry = cacheEntry;
230 } else {
231 m_provider->mCachedEntries.append(entry);
232 }
233
234 entry.setName(content.name());
235 entry.setHomepage(content.detailpage());
236 entry.setRating(content.rating());
237 entry.setNumberOfComments(content.numberOfComments());
238 entry.setDownloadCount(content.downloads());
239 entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt());
240 entry.setDonationLink(content.attribute(QStringLiteral("donationpage")));
241 entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage")));
242 entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt());
243 entry.setHomepage(content.detailpage());
244
245 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), Entry::PreviewSmall1);
246 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), Entry::PreviewSmall2);
247 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), Entry::PreviewSmall3);
248
249 entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), Entry::PreviewBig1);
250 entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), Entry::PreviewBig2);
251 entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), Entry::PreviewBig3);
252
253 entry.setLicense(content.license());
254 Author author;
255 author.setId(content.author());
256 author.setName(content.author());
257 author.setHomepage(content.attribute(QStringLiteral("profilepage")));
258 entry.setAuthor(author);
259
260 entry.setSource(Entry::Online);
261 entry.setSummary(content.description());
262 entry.setShortSummary(content.summary());
263 entry.setChangelog(content.changelog());
264 entry.setTags(content.tags());
265
267 entry.d->mDownloadLinkInformationList.clear();
268 entry.d->mDownloadLinkInformationList.reserve(descs.size());
269 for (const Attica::DownloadDescription &desc : descs) {
270 entry.d->mDownloadLinkInformationList.append({.name = desc.name(),
271 .priceAmount = desc.priceAmount(),
272 .distributionType = desc.distributionType(),
273 .descriptionLink = desc.link(),
274 .id = desc.id(),
275 .isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload,
276 .size = desc.size(),
277 .tags = desc.tags(),
278 .version = desc.version()});
279 }
280
281 return entry;
282}
283
284[[nodiscard]] KNSCore::Provider::SearchRequest AtticaRequester::request() const
285{
286 return m_request;
287}
288
289} // 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
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
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.