KNewStuff

atticaprovider.cpp
1/*
2 SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "atticaprovider_p.h"
8
9#include "commentsmodel.h"
10#include "entry_p.h"
11#include "question.h"
12#include "tagsfilterchecker.h"
13
14#include <KFormat>
15#include <KLocalizedString>
16#include <QCollator>
17#include <QDomDocument>
18#include <knewstuffcore_debug.h>
19
20#include <attica/accountbalance.h>
21#include <attica/config.h>
22#include <attica/content.h>
23#include <attica/downloaditem.h>
24#include <attica/listjob.h>
25#include <attica/person.h>
26#include <attica/provider.h>
27#include <attica/providermanager.h>
28
29using namespace Attica;
30
31namespace KNSCore
32{
33AtticaProvider::AtticaProvider(const QStringList &categories, const QString &additionalAgentInformation)
34 : mEntryJob(nullptr)
35 , mInitialized(false)
36{
37 // init categories map with invalid categories
38 for (const QString &category : categories) {
39 mCategoryMap.insert(category, Attica::Category());
40 }
41
42 connect(&m_providerManager, &ProviderManager::providerAdded, this, [this, additionalAgentInformation](const Attica::Provider &provider) {
43 providerLoaded(provider);
44 m_provider.setAdditionalAgentInformation(additionalAgentInformation);
45 });
46 connect(&m_providerManager, &ProviderManager::authenticationCredentialsMissing, this, &AtticaProvider::onAuthenticationCredentialsMissing);
47}
48
49AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString &additionalAgentInformation)
50 : mEntryJob(nullptr)
51 , mInitialized(false)
52{
53 // init categories map with invalid categories
54 for (const QString &category : categories) {
55 mCategoryMap.insert(category, Attica::Category());
56 }
57 providerLoaded(provider);
58 m_provider.setAdditionalAgentInformation(additionalAgentInformation);
59}
60
61QString AtticaProvider::id() const
62{
63 return m_providerId;
64}
65
66void AtticaProvider::onAuthenticationCredentialsMissing(const Attica::Provider &)
67{
68 qCDebug(KNEWSTUFFCORE) << "Authentication missing!";
69 // FIXME Show authentication dialog
70}
71
72bool AtticaProvider::setProviderXML(const QDomElement &xmldata)
73{
74 if (xmldata.tagName() != QLatin1String("provider")) {
75 return false;
76 }
77
78 // FIXME this is quite ugly, repackaging the xml into a string
79 QDomDocument doc(QStringLiteral("temp"));
80 qCDebug(KNEWSTUFFCORE) << "setting provider xml" << doc.toString();
81
82 doc.appendChild(xmldata.cloneNode(true));
83 m_providerManager.addProviderFromXml(doc.toString());
84
85 if (!m_providerManager.providers().isEmpty()) {
86 qCDebug(KNEWSTUFFCORE) << "base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString();
87 } else {
88 qCCritical(KNEWSTUFFCORE) << "Could not load provider.";
89 return false;
90 }
91 return true;
92}
93
94void AtticaProvider::setCachedEntries(const KNSCore::Entry::List &cachedEntries)
95{
96 mCachedEntries = cachedEntries;
97}
98
99void AtticaProvider::providerLoaded(const Attica::Provider &provider)
100{
101 setName(provider.name());
102 setIcon(provider.icon());
103 qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name();
104
105 m_provider = provider;
107 m_providerId = provider.baseUrl().host();
108
109 Attica::ListJob<Attica::Category> *job = m_provider.requestCategories();
110 connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded);
111 job->start();
112}
113
114void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob)
115{
116 if (!jobSuccess(listJob)) {
117 return;
118 }
119
120 qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys();
121
122 auto *job = static_cast<Attica::ListJob<Attica::Category> *>(listJob);
123 const Category::List categoryList = job->itemList();
124
125 QList<CategoryMetadata> categoryMetadataList;
126 for (const Category &category : categoryList) {
127 if (mCategoryMap.contains(category.name())) {
128 qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName();
129 // If there is only the placeholder category, replace it
130 if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) {
131 mCategoryMap.replace(category.name(), category);
132 } else {
133 mCategoryMap.insert(category.name(), category);
134 }
135
136 CategoryMetadata categoryMetadata;
137 categoryMetadata.id = category.id();
138 categoryMetadata.name = category.name();
139 categoryMetadata.displayName = category.displayName();
140 categoryMetadataList << categoryMetadata;
141 }
142 }
143 std::sort(categoryMetadataList.begin(),
144 categoryMetadataList.end(),
145 [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool {
146 const QString a(i.displayName.isEmpty() ? i.name : i.displayName);
147 const QString b(j.displayName.isEmpty() ? j.name : j.displayName);
148
149 return (QCollator().compare(a, b) < 0);
150 });
151
152 bool correct = false;
153 for (auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) {
154 if (!it.value().isValid()) {
155 qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key();
156 } else {
157 correct = true;
158 }
159 }
160
161 if (correct) {
162 mInitialized = true;
163 Q_EMIT providerInitialized(this);
164 Q_EMIT categoriesMetadataLoded(categoryMetadataList);
165 } else {
166 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError, i18n("All categories are missing"), QVariant());
167 }
168}
169
170bool AtticaProvider::isInitialized() const
171{
172 return mInitialized;
173}
174
175void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request)
176{
177 if (mEntryJob) {
178 mEntryJob->abort();
179 mEntryJob = nullptr;
180 }
181
182 mCurrentRequest = request;
183 switch (request.filter) {
184 case None:
185 break;
186 case ExactEntryId: {
187 ItemJob<Content> *job = m_provider.requestContent(request.searchTerm);
188 job->setProperty("providedEntryId", request.searchTerm);
189 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
190 job->start();
191 return;
192 }
193 case Installed:
194 if (request.page == 0) {
195 Q_EMIT loadingFinished(request, installedEntries());
196 } else {
197 Q_EMIT loadingFinished(request, Entry::List());
198 }
199 return;
200 case Updates:
201 checkForUpdates();
202 return;
203 }
204
205 Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode);
206 Attica::Category::List categoriesToSearch;
207
208 if (request.categories.isEmpty()) {
209 // search in all categories
210 categoriesToSearch = mCategoryMap.values();
211 } else {
212 categoriesToSearch.reserve(request.categories.size());
213 for (const QString &categoryName : std::as_const(request.categories)) {
214 categoriesToSearch.append(mCategoryMap.values(categoryName));
215 }
216 }
217
218 ListJob<Content> *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize);
219 job->setProperty("searchRequest", QVariant::fromValue(request));
220 connect(job, &BaseJob::finished, this, &AtticaProvider::categoryContentsLoaded);
221
222 mEntryJob = job;
223 job->start();
224}
225
226void AtticaProvider::checkForUpdates()
227{
228 if (mCachedEntries.isEmpty()) {
229 Q_EMIT loadingFinished(mCurrentRequest, {});
230 }
231
232 for (const Entry &e : std::as_const(mCachedEntries)) {
233 ItemJob<Content> *job = m_provider.requestContent(e.uniqueId());
234 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
235 m_updateJobs.insert(job);
236 job->start();
237 qCDebug(KNEWSTUFFCORE) << "Checking for update: " << e.name();
238 }
239}
240
241void AtticaProvider::loadEntryDetails(const KNSCore::Entry &entry)
242{
243 ItemJob<Content> *job = m_provider.requestContent(entry.uniqueId());
244 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
245 job->start();
246}
247
248void AtticaProvider::detailsLoaded(BaseJob *job)
249{
250 if (jobSuccess(job)) {
251 auto *contentJob = static_cast<ItemJob<Content> *>(job);
252 Content content = contentJob->result();
253 Entry entry = entryFromAtticaContent(content);
254 entry.setEntryRequestedId(job->property("providedEntryId").toString()); // The ResultsStream should still known that this entry was for its query
255 Q_EMIT entryDetailsLoaded(entry);
256 qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name();
257 }
258
259 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
260 qCDebug(KNEWSTUFFCORE) << "check update finished.";
261 QList<Entry> updatable;
262 for (const Entry &entry : std::as_const(mCachedEntries)) {
263 if (entry.status() == KNSCore::Entry::Updateable) {
264 updatable.append(entry);
265 }
266 }
267 Q_EMIT loadingFinished(mCurrentRequest, updatable);
268 }
269}
270
271void AtticaProvider::categoryContentsLoaded(BaseJob *job)
272{
273 if (!jobSuccess(job)) {
274 return;
275 }
276
277 auto *listJob = static_cast<ListJob<Content> *>(job);
278 const Content::List contents = listJob->itemList();
279
280 Entry::List entries;
281 TagsFilterChecker checker(tagFilter());
282 TagsFilterChecker downloadschecker(downloadTagFilter());
283 for (const Content &content : contents) {
284 if (!content.isValid()) {
285 qCDebug(KNEWSTUFFCORE)
286 << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
287 << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories;
288 continue;
289 }
290 if (checker.filterAccepts(content.tags())) {
291 bool filterAcceptsDownloads = true;
292 if (content.downloads() > 0) {
293 filterAcceptsDownloads = false;
294 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
295 for (const Attica::DownloadDescription &dli : descs) {
296 if (downloadschecker.filterAccepts(dli.tags())) {
297 filterAcceptsDownloads = true;
298 break;
299 }
300 }
301 }
302 if (filterAcceptsDownloads) {
303 mCachedContent.insert(content.id(), content);
304 entries.append(entryFromAtticaContent(content));
305 } else {
306 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter();
307 }
308 } else {
309 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter();
310 }
311 }
312
313 qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size();
314 Q_EMIT loadingFinished(mCurrentRequest, entries);
315 mEntryJob = nullptr;
316}
317
318Attica::Provider::SortMode AtticaProvider::atticaSortMode(SortMode sortMode)
319{
320 switch (sortMode) {
321 case Newest:
322 return Attica::Provider::Newest;
323 case Alphabetical:
324 return Attica::Provider::Alphabetical;
325 case Downloads:
326 return Attica::Provider::Downloads;
327 default:
328 return Attica::Provider::Rating;
329 }
330}
331
332void AtticaProvider::loadPayloadLink(const KNSCore::Entry &entry, int linkId)
333{
334 Attica::Content content = mCachedContent.value(entry.uniqueId());
335 const DownloadDescription desc = content.downloadUrlDescription(linkId);
336
337 if (desc.hasPrice()) {
338 // Ask for balance, then show information...
339 ItemJob<AccountBalance> *job = m_provider.requestAccountBalance();
340 connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded);
341 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
342 job->start();
343
344 qCDebug(KNEWSTUFFCORE) << "get account balance";
345 } else {
346 ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId));
347 connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
348 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
349 job->start();
350
351 qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId();
352 }
353}
354
355void AtticaProvider::loadComments(const Entry &entry, int commentsPerPage, int page)
356{
357 ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral("0"), page, commentsPerPage);
358 connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments);
359 job->start();
360}
361
362QList<std::shared_ptr<KNSCore::Comment>> getCommentsList(const Attica::Comment::List &comments, std::shared_ptr<KNSCore::Comment> parent)
363{
365 for (const Attica::Comment &comment : comments) {
366 qCDebug(KNEWSTUFFCORE) << "Appending comment with id" << comment.id() << ", which has" << comment.childCount() << "children";
367 auto knsComment = std::make_shared<KNSCore::Comment>();
368 knsComment->id = comment.id();
369 knsComment->subject = comment.subject();
370 knsComment->text = comment.text();
371 knsComment->childCount = comment.childCount();
372 knsComment->username = comment.user();
373 knsComment->date = comment.date();
374 knsComment->score = comment.score();
375 knsComment->parent = parent;
376 knsComments << knsComment;
377 if (comment.childCount() > 0) {
378 qCDebug(KNEWSTUFFCORE) << "Getting more comments, as this one has children, and we currently have this number of comments:" << knsComments.count();
379 knsComments << getCommentsList(comment.children(), knsComment);
380 qCDebug(KNEWSTUFFCORE) << "After getting the children, we now have the following number of comments:" << knsComments.count();
381 }
382 }
383 return knsComments;
384}
385
386void AtticaProvider::loadedComments(Attica::BaseJob *baseJob)
387{
388 if (!jobSuccess(baseJob)) {
389 return;
390 }
391
392 auto *job = static_cast<ListJob<Attica::Comment> *>(baseJob);
393 Attica::Comment::List comments = job->itemList();
394
395 QList<std::shared_ptr<KNSCore::Comment>> receivedComments = getCommentsList(comments, nullptr);
396 Q_EMIT commentsLoaded(receivedComments);
397}
398
399void AtticaProvider::loadPerson(const QString &username)
400{
401 if (m_provider.hasPersonService()) {
402 ItemJob<Attica::Person> *job = m_provider.requestPerson(username);
403 job->setProperty("username", username);
404 connect(job, &BaseJob::finished, this, &AtticaProvider::loadedPerson);
405 job->start();
406 }
407}
408
409void AtticaProvider::loadedPerson(Attica::BaseJob *baseJob)
410{
411 if (!jobSuccess(baseJob)) {
412 return;
413 }
414
415 auto *job = static_cast<ItemJob<Attica::Person> *>(baseJob);
416 Attica::Person person = job->result();
417
418 auto author = std::make_shared<KNSCore::Author>();
419 // This is a touch hack-like, but it ensures we actually have the data in case it is not returned by the server
420 author->setId(job->property("username").toString());
421 author->setName(QStringLiteral("%1 %2").arg(person.firstName(), person.lastName()).trimmed());
422 author->setHomepage(person.homepage());
423 author->setProfilepage(person.extendedAttribute(QStringLiteral("profilepage")));
424 author->setAvatarUrl(person.avatarUrl());
425 author->setDescription(person.extendedAttribute(QStringLiteral("description")));
426 Q_EMIT personLoaded(author);
427}
428
429void AtticaProvider::loadBasics()
430{
431 Attica::ItemJob<Attica::Config> *configJob = m_provider.requestConfig();
432 connect(configJob, &BaseJob::finished, this, &AtticaProvider::loadedConfig);
433 configJob->start();
434}
435
436void AtticaProvider::loadedConfig(Attica::BaseJob *baseJob)
437{
438 if (jobSuccess(baseJob)) {
439 auto *job = static_cast<ItemJob<Attica::Config> *>(baseJob);
440 Attica::Config config = job->result();
441 setVersion(config.version());
442 setSupportsSsl(config.ssl());
443 setContactEmail(config.contact());
444 QString protocol{QStringLiteral("http")};
445 if (config.ssl()) {
446 protocol = QStringLiteral("https");
447 }
448 // There is usually no protocol in the website and host, but in case
449 // there is, trust what's there
450 if (config.website().contains(QLatin1String("://"))) {
451 setWebsite(QUrl(config.website()));
452 } else {
453 setWebsite(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.website())));
454 }
455 if (config.host().contains(QLatin1String("://"))) {
456 setHost(QUrl(config.host()));
457 } else {
458 setHost(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.host())));
459 }
460 }
461}
462
463void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob)
464{
465 if (!jobSuccess(baseJob)) {
466 return;
467 }
468
469 auto *job = static_cast<ItemJob<AccountBalance> *>(baseJob);
470 AccountBalance item = job->result();
471
472 QPair<Entry, int> pair = mDownloadLinkJobs.take(job);
473 Entry entry(pair.first);
474 Content content = mCachedContent.value(entry.uniqueId());
475 if (content.downloadUrlDescription(pair.second).priceAmount() < item.balance()) {
476 qCDebug(KNEWSTUFFCORE) << "Your balance is greater than the price." << content.downloadUrlDescription(pair.second).priceAmount()
477 << " balance: " << item.balance();
478 Question question;
479 question.setEntry(entry);
480 question.setQuestion(i18nc("the price of a download item, parameter 1 is the currency, 2 is the price",
481 "This item costs %1 %2.\nDo you want to buy it?",
482 item.currency(),
483 content.downloadUrlDescription(pair.second).priceAmount()));
484 if (question.ask() == Question::YesResponse) {
485 ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second));
486 connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
487 mDownloadLinkJobs[job] = qMakePair(entry, pair.second);
488 job->start();
489 } else {
490 return;
491 }
492 } else {
493 qCDebug(KNEWSTUFFCORE) << "You don't have enough money on your account!" << content.downloadUrlDescription(0).priceAmount()
494 << " balance: " << item.balance();
495 Q_EMIT signalInformation(i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2", //
496 item.balance(),
497 content.downloadUrlDescription(0).priceAmount()));
498 }
499}
500
501void AtticaProvider::downloadItemLoaded(BaseJob *baseJob)
502{
503 if (!jobSuccess(baseJob)) {
504 return;
505 }
506
507 auto *job = static_cast<ItemJob<DownloadItem> *>(baseJob);
508 DownloadItem item = job->result();
509
510 Entry entry = mDownloadLinkJobs.take(job).first;
511 entry.setPayload(QString(item.url().toString()));
512 Q_EMIT payloadLinkLoaded(entry);
513}
514
515Entry::List AtticaProvider::installedEntries() const
516{
517 Entry::List entries;
518 for (const Entry &entry : std::as_const(mCachedEntries)) {
519 if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) {
520 entries.append(entry);
521 }
522 }
523 return entries;
524}
525
526void AtticaProvider::vote(const Entry &entry, uint rating)
527{
528 PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating);
529 connect(job, &BaseJob::finished, this, &AtticaProvider::votingFinished);
530 job->start();
531}
532
533void AtticaProvider::votingFinished(Attica::BaseJob *job)
534{
535 if (!jobSuccess(job)) {
536 return;
537 }
538 Q_EMIT signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded."));
539}
540
541void AtticaProvider::becomeFan(const Entry &entry)
542{
543 PostJob *job = m_provider.becomeFan(entry.uniqueId());
544 connect(job, &BaseJob::finished, this, &AtticaProvider::becomeFanFinished);
545 job->start();
546}
547
548void AtticaProvider::becomeFanFinished(Attica::BaseJob *job)
549{
550 if (!jobSuccess(job)) {
551 return;
552 }
553 Q_EMIT signalInformation(i18n("You are now a fan."));
554}
555
556bool AtticaProvider::jobSuccess(Attica::BaseJob *job)
557{
558 if (job->metadata().error() == Attica::Metadata::NoError) {
559 return true;
560 }
561 qCDebug(KNEWSTUFFCORE) << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message();
562
563 if (job->metadata().error() == Attica::Metadata::NetworkError) {
564 if (job->metadata().statusCode() == 503) {
565 QDateTime retryAfter;
566 static const QByteArray retryAfterKey{"Retry-After"};
567 for (const QNetworkReply::RawHeaderPair &headerPair : job->metadata().headers()) {
568 if (headerPair.first == retryAfterKey) {
569 // Retry-After is not a known header, so we need to do a bit of running around to make that work
570 // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that
571 // So, simple workaround, just pass it through a dummy request and get a formatted date out (the
572 // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...)
573 QNetworkRequest dummyRequest;
574 dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second);
575 retryAfter = dummyRequest.header(QNetworkRequest::LastModifiedHeader).toDateTime();
576 break;
577 }
578 }
579 static const KFormat formatter;
580 Q_EMIT signalErrorCode(KNSCore::ErrorCode::TryAgainLaterError,
581 i18n("The service is currently undergoing maintenance and is expected to be back in %1.",
583 {retryAfter});
584 } else {
585 Q_EMIT signalErrorCode(KNSCore::ErrorCode::NetworkError,
586 i18n("Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()),
587 job->metadata().statusCode());
588 }
589 }
590 if (job->metadata().error() == Attica::Metadata::OcsError) {
591 if (job->metadata().statusCode() == 200) {
592 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
593 i18n("Too many requests to server. Please try again in a few minutes."),
594 job->metadata().statusCode());
595 } else if (job->metadata().statusCode() == 405) {
596 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
597 i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()),
598 job->metadata().statusCode());
599 } else {
600 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
601 i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()),
602 job->metadata().statusCode());
603 }
604 }
605
606 if (auto searchRequestVar = job->property("searchRequest"); searchRequestVar.isValid()) {
607 SearchRequest req = searchRequestVar.value<SearchRequest>();
608 Q_EMIT loadingFailed(req);
609 }
610 return false;
611}
612
613Entry AtticaProvider::entryFromAtticaContent(const Attica::Content &content)
614{
615 Entry entry;
616
617 entry.setProviderId(id());
618 entry.setUniqueId(content.id());
619 entry.setStatus(KNSCore::Entry::Downloadable);
620 entry.setVersion(content.version());
621 entry.setReleaseDate(content.updated().date());
622 entry.setCategory(content.attribute(QStringLiteral("typeid")));
623
624 int index = mCachedEntries.indexOf(entry);
625 if (index >= 0) {
626 Entry &cacheEntry = mCachedEntries[index];
627 // check if updateable
628 if (((cacheEntry.status() == KNSCore::Entry::Installed) || (cacheEntry.status() == KNSCore::Entry::Updateable))
629 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
630 cacheEntry.setStatus(KNSCore::Entry::Updateable);
631 cacheEntry.setUpdateVersion(entry.version());
632 cacheEntry.setUpdateReleaseDate(entry.releaseDate());
633 }
634 entry = cacheEntry;
635 } else {
636 mCachedEntries.append(entry);
637 }
638
639 entry.setName(content.name());
640 entry.setHomepage(content.detailpage());
641 entry.setRating(content.rating());
642 entry.setNumberOfComments(content.numberOfComments());
643 entry.setDownloadCount(content.downloads());
644 entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt());
645 entry.setDonationLink(content.attribute(QStringLiteral("donationpage")));
646 entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage")));
647 entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt());
648 entry.setHomepage(content.detailpage());
649
650 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), Entry::PreviewSmall1);
651 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), Entry::PreviewSmall2);
652 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), Entry::PreviewSmall3);
653
654 entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), Entry::PreviewBig1);
655 entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), Entry::PreviewBig2);
656 entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), Entry::PreviewBig3);
657
658 entry.setLicense(content.license());
659 Author author;
660 author.setId(content.author());
661 author.setName(content.author());
662 author.setHomepage(content.attribute(QStringLiteral("profilepage")));
663 entry.setAuthor(author);
664
665 entry.setSource(Entry::Online);
666 entry.setSummary(content.description());
667 entry.setShortSummary(content.summary());
668 entry.setChangelog(content.changelog());
669 entry.setTags(content.tags());
670
672 entry.d->mDownloadLinkInformationList.clear();
673 entry.d->mDownloadLinkInformationList.reserve(descs.size());
674 for (const Attica::DownloadDescription &desc : descs) {
675 entry.d->mDownloadLinkInformationList.append({.name = desc.name(),
676 .priceAmount = desc.priceAmount(),
677 .distributionType = desc.distributionType(),
678 .descriptionLink = desc.link(),
679 .id = desc.id(),
680 .isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload,
681 .size = desc.size(),
682 .tags = desc.tags(),
683 .version = desc.version()});
684 }
685
686 return entry;
687}
688
689} // namespace
690
691#include "moc_atticaprovider_p.cpp"
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
DownloadDescription downloadUrlDescription(int number) const
QString name() const
QString id() const
QStringList tags() const
QString name() const
QUrl baseUrl() const
void setAdditionalAgentInformation(const QString &additionalInformation)
QUrl icon() const
QString formatSpelloutDuration(quint64 msecs) const
KNewStuff data entry container.
Definition entry.h:48
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString name(GameStandardAction id)
Category category(StandardShortcut id)
KEDUVOCDOCUMENT_EXPORT QStringList comments(const QString &language=QString())
qint64 currentMSecsSinceEpoch()
QDate date() const const
qint64 toMSecsSinceEpoch() const const
QString tagName() const const
QDomNode cloneNode(bool deep) const const
void append(QList< T > &&value)
iterator begin()
qsizetype count() const const
iterator end()
bool isEmpty() const const
qsizetype size() const const
typedef RawHeaderPair
QVariant header(KnownHeaders header) const const
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
QVariant property(const char *name) const const
bool setProperty(const char *name, QVariant &&value)
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString host(ComponentFormattingOptions options) const const
QVariant fromValue(T &&value)
QDateTime toDateTime() const const
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 Jul 26 2024 11:56:35 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.