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

KDE's Doxygen guidelines are available online.