KNewStuff

atticaprovider.cpp
1 /*
2  SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <[email protected]>
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 <knewstuffcore_debug.h>
17 
18 #include <attica/accountbalance.h>
19 #include <attica/content.h>
20 #include <attica/downloaditem.h>
21 #include <attica/listjob.h>
22 #include <attica/person.h>
23 #include <attica/provider.h>
24 #include <attica/providermanager.h>
25 
26 using namespace Attica;
27 
28 namespace KNSCore
29 {
30 AtticaProvider::AtticaProvider(const QStringList &categories, const QString &additionalAgentInformation)
31  : mEntryJob(nullptr)
32  , mInitialized(false)
33 {
34  // init categories map with invalid categories
35  for (const QString &category : categories) {
36  mCategoryMap.insert(category, Attica::Category());
37  }
38 
39  connect(&m_providerManager, &ProviderManager::providerAdded, this, [=](const Attica::Provider &provider) {
40  providerLoaded(provider);
41  m_provider.setAdditionalAgentInformation(additionalAgentInformation);
42  });
43  connect(&m_providerManager, SIGNAL(authenticationCredentialsMissing(Provider)), SLOT(authenticationCredentialsMissing(Provider)));
44  connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments);
45  connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson);
46 }
47 
48 AtticaProvider::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 
60 QString AtticaProvider::id() const
61 {
62  return m_providerId;
63 }
64 
65 void AtticaProvider::authenticationCredentialsMissing(const KNSCore::Provider &)
66 {
67  qCDebug(KNEWSTUFFCORE) << "Authentication missing!";
68  // FIXME Show autentication dialog
69 }
70 
71 bool 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 
93 void AtticaProvider::setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries)
94 {
95  mCachedEntries = cachedEntries;
96 }
97 
98 void AtticaProvider::providerLoaded(const Attica::Provider &provider)
99 {
100  mName = provider.name();
101  qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name();
102 
103  m_provider = provider;
104  m_provider.setAdditionalAgentInformation(mName);
105  m_providerId = provider.baseUrl().toString();
106 
107  Attica::ListJob<Attica::Category> *job = m_provider.requestCategories();
108  connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded);
109  job->start();
110 }
111 
112 void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob)
113 {
114  if (!jobSuccess(listJob)) {
115  return;
116  }
117 
118  qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys();
119 
120  auto *job = static_cast<Attica::ListJob<Attica::Category> *>(listJob);
121  const Category::List categoryList = job->itemList();
122 
123  QList<CategoryMetadata> categoryMetadataList;
124  for (const Category &category : categoryList) {
125  if (mCategoryMap.contains(category.name())) {
126  qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName();
127  // If there is only the placeholder category, replace it
128  if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) {
129  mCategoryMap.replace(category.name(), category);
130  } else {
131  mCategoryMap.insert(category.name(), category);
132  }
133 
134  CategoryMetadata categoryMetadata;
135  categoryMetadata.id = category.id();
136  categoryMetadata.name = category.name();
137  categoryMetadata.displayName = category.displayName();
138  categoryMetadataList << categoryMetadata;
139  }
140  }
141  std::sort(categoryMetadataList.begin(),
142  categoryMetadataList.end(),
143  [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool {
144  const QString a(i.displayName.isEmpty() ? i.name : i.displayName);
145  const QString b(j.displayName.isEmpty() ? j.name : j.displayName);
146 
147  return (QCollator().compare(a, b) < 0);
148  });
149 
150  bool correct = false;
151  for (auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) {
152  if (!it.value().isValid()) {
153  qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key();
154  } else {
155  correct = true;
156  }
157  }
158 
159  if (correct) {
160  mInitialized = true;
161  Q_EMIT providerInitialized(this);
162  Q_EMIT categoriesMetadataLoded(categoryMetadataList);
163  } else {
164  Q_EMIT signalErrorCode(KNSCore::ConfigFileError, i18n("All categories are missing"), QVariant());
165  }
166 }
167 
168 bool AtticaProvider::isInitialized() const
169 {
170  return mInitialized;
171 }
172 
173 void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request)
174 {
175  if (mEntryJob) {
176  mEntryJob->abort();
177  mEntryJob = nullptr;
178  }
179 
180  mCurrentRequest = request;
181  switch (request.filter) {
182  case None:
183  break;
184  case ExactEntryId: {
185  ItemJob<Content> *job = m_provider.requestContent(request.searchTerm);
186  connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
187  job->start();
188  return;
189  }
190  case Installed:
191  if (request.page == 0) {
192  Q_EMIT loadingFinished(request, installedEntries());
193  } else {
194  Q_EMIT loadingFinished(request, EntryInternal::List());
195  }
196  return;
197  case Updates:
198  checkForUpdates();
199  return;
200  }
201 
202  Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode);
203  Attica::Category::List categoriesToSearch;
204 
205  if (request.categories.isEmpty()) {
206  // search in all categories
207  categoriesToSearch = mCategoryMap.values();
208  } else {
209  categoriesToSearch.reserve(request.categories.size());
210  for (const QString &categoryName : qAsConst(request.categories)) {
211  categoriesToSearch.append(mCategoryMap.values(categoryName));
212  }
213  }
214 
215  ListJob<Content> *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize);
216  connect(job, &BaseJob::finished, this, &AtticaProvider::categoryContentsLoaded);
217 
218  mEntryJob = job;
219  job->start();
220 }
221 
222 void AtticaProvider::checkForUpdates()
223 {
224  if (mCachedEntries.isEmpty()) {
225  Q_EMIT loadingFinished(mCurrentRequest, {});
226  }
227 
228  for (const EntryInternal &e : qAsConst(mCachedEntries)) {
229  ItemJob<Content> *job = m_provider.requestContent(e.uniqueId());
230  connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
231  m_updateJobs.insert(job);
232  job->start();
233  qCDebug(KNEWSTUFFCORE) << "Checking for update: " << e.name();
234  }
235 }
236 
237 void AtticaProvider::loadEntryDetails(const KNSCore::EntryInternal &entry)
238 {
239  ItemJob<Content> *job = m_provider.requestContent(entry.uniqueId());
240  connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
241  job->start();
242 }
243 
244 void AtticaProvider::detailsLoaded(BaseJob *job)
245 {
246  if (jobSuccess(job)) {
247  auto *contentJob = static_cast<ItemJob<Content> *>(job);
248  Content content = contentJob->result();
249  EntryInternal entry = entryFromAtticaContent(content);
250  Q_EMIT entryDetailsLoaded(entry);
251  qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name();
252  }
253 
254  if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
255  qCDebug(KNEWSTUFFCORE) << "check update finished.";
256  QList<EntryInternal> updatable;
257  for (const EntryInternal &entry : qAsConst(mCachedEntries)) {
258  if (entry.status() == KNS3::Entry::Updateable) {
259  updatable.append(entry);
260  }
261  }
262  Q_EMIT loadingFinished(mCurrentRequest, updatable);
263  }
264 }
265 
266 void AtticaProvider::categoryContentsLoaded(BaseJob *job)
267 {
268  if (!jobSuccess(job)) {
269  return;
270  }
271 
272  auto *listJob = static_cast<ListJob<Content> *>(job);
273  const Content::List contents = listJob->itemList();
274 
275  EntryInternal::List entries;
276  TagsFilterChecker checker(tagFilter());
277  TagsFilterChecker downloadschecker(downloadTagFilter());
278  for (const Content &content : contents) {
279  if (!content.isValid()) {
280  qCDebug(KNEWSTUFFCORE)
281  << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
282  << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories;
283  continue;
284  }
285  if (checker.filterAccepts(content.tags())) {
286  bool filterAcceptsDownloads = true;
287  if (content.downloads() > 0) {
288  filterAcceptsDownloads = false;
289  const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
290  for (const Attica::DownloadDescription &dli : descs) {
291  if (downloadschecker.filterAccepts(dli.tags())) {
292  filterAcceptsDownloads = true;
293  break;
294  }
295  }
296  }
297  if (filterAcceptsDownloads) {
298  mCachedContent.insert(content.id(), content);
299  entries.append(entryFromAtticaContent(content));
300  } else {
301  qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter();
302  }
303  } else {
304  qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter();
305  }
306  }
307 
308  qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size();
309  Q_EMIT loadingFinished(mCurrentRequest, entries);
310  mEntryJob = nullptr;
311 }
312 
313 Attica::Provider::SortMode AtticaProvider::atticaSortMode(const SortMode &sortMode)
314 {
315  switch (sortMode) {
316  case Newest:
317  return Attica::Provider::Newest;
318  case Alphabetical:
319  return Attica::Provider::Alphabetical;
320  case Downloads:
321  return Attica::Provider::Downloads;
322  default:
323  return Attica::Provider::Rating;
324  }
325 }
326 
327 void AtticaProvider::loadPayloadLink(const KNSCore::EntryInternal &entry, int linkId)
328 {
329  Attica::Content content = mCachedContent.value(entry.uniqueId());
330  const DownloadDescription desc = content.downloadUrlDescription(linkId);
331 
332  if (desc.hasPrice()) {
333  // Ask for balance, then show information...
334  ItemJob<AccountBalance> *job = m_provider.requestAccountBalance();
335  connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded);
336  mDownloadLinkJobs[job] = qMakePair(entry, linkId);
337  job->start();
338 
339  qCDebug(KNEWSTUFFCORE) << "get account balance";
340  } else {
341  ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId));
342  connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
343  mDownloadLinkJobs[job] = qMakePair(entry, linkId);
344  job->start();
345 
346  qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId();
347  }
348 }
349 
350 void AtticaProvider::loadComments(const EntryInternal &entry, int commentsPerPage, int page)
351 {
352  ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral("0"), page, commentsPerPage);
353  connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments);
354  job->start();
355 }
356 
357 /// TODO KF6 QList is discouraged, and we'll probably want to switch this (and the rest of the KNS library) to QVector instead
358 QList<std::shared_ptr<KNSCore::Comment>> getCommentsList(const Attica::Comment::List &comments, std::shared_ptr<KNSCore::Comment> parent)
359 {
361  for (const Attica::Comment &comment : comments) {
362  qCDebug(KNEWSTUFFCORE) << "Appending comment with id" << comment.id() << ", which has" << comment.childCount() << "children";
363  auto knsComment = std::make_shared<KNSCore::Comment>();
364  knsComment->id = comment.id();
365  knsComment->subject = comment.subject();
366  knsComment->text = comment.text();
367  knsComment->childCount = comment.childCount();
368  knsComment->username = comment.user();
369  knsComment->date = comment.date();
370  knsComment->score = comment.score();
371  knsComment->parent = parent;
372  knsComments << knsComment;
373  if (comment.childCount() > 0) {
374  qCDebug(KNEWSTUFFCORE) << "Getting more comments, as this one has children, and we currently have this number of comments:" << knsComments.count();
375  knsComments << getCommentsList(comment.children(), knsComment);
376  qCDebug(KNEWSTUFFCORE) << "After getting the children, we now have the following number of comments:" << knsComments.count();
377  }
378  }
379  return knsComments;
380 }
381 
382 void AtticaProvider::loadedComments(Attica::BaseJob *baseJob)
383 {
384  if (!jobSuccess(baseJob)) {
385  return;
386  }
387 
388  auto *job = static_cast<ListJob<Attica::Comment> *>(baseJob);
389  Attica::Comment::List comments = job->itemList();
390 
391  QList<std::shared_ptr<KNSCore::Comment>> receivedComments = getCommentsList(comments, nullptr);
392  Q_EMIT commentsLoaded(receivedComments);
393 }
394 
395 void AtticaProvider::loadPerson(const QString &username)
396 {
397  if (m_provider.hasPersonService()) {
398  ItemJob<Attica::Person> *job = m_provider.requestPerson(username);
399  job->setProperty("username", username);
400  connect(job, &BaseJob::finished, this, &AtticaProvider::loadedPerson);
401  job->start();
402  }
403 }
404 
405 void AtticaProvider::loadedPerson(Attica::BaseJob *baseJob)
406 {
407  if (!jobSuccess(baseJob)) {
408  return;
409  }
410 
411  auto *job = static_cast<ItemJob<Attica::Person> *>(baseJob);
412  Attica::Person person = job->result();
413 
414  auto author = std::make_shared<KNSCore::Author>();
415  // This is a touch hack-like, but it ensures we actually have the data in case it is not returned by the server
416  author->setId(job->property("username").toString());
417  author->setName(QStringLiteral("%1 %2").arg(person.firstName(), person.lastName()).trimmed());
418  author->setHomepage(person.homepage());
419  author->setProfilepage(person.extendedAttribute(QStringLiteral("profilepage")));
420  author->setAvatarUrl(person.avatarUrl());
421  author->setDescription(person.extendedAttribute(QStringLiteral("description")));
422  Q_EMIT personLoaded(author);
423 }
424 
425 void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob)
426 {
427  if (!jobSuccess(baseJob)) {
428  return;
429  }
430 
431  auto *job = static_cast<ItemJob<AccountBalance> *>(baseJob);
432  AccountBalance item = job->result();
433 
434  QPair<EntryInternal, int> pair = mDownloadLinkJobs.take(job);
435  EntryInternal entry(pair.first);
436  Content content = mCachedContent.value(entry.uniqueId());
437  if (content.downloadUrlDescription(pair.second).priceAmount() < item.balance()) {
438  qCDebug(KNEWSTUFFCORE) << "Your balance is greater than the price." << content.downloadUrlDescription(pair.second).priceAmount()
439  << " balance: " << item.balance();
440  Question question;
441  question.setQuestion(i18nc("the price of a download item, parameter 1 is the currency, 2 is the price",
442  "This item costs %1 %2.\nDo you want to buy it?",
443  item.currency(),
444  content.downloadUrlDescription(pair.second).priceAmount()));
445  if (question.ask() == Question::YesResponse) {
446  ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second));
447  connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
448  mDownloadLinkJobs[job] = qMakePair(entry, pair.second);
449  job->start();
450  } else {
451  return;
452  }
453  } else {
454  qCDebug(KNEWSTUFFCORE) << "You don't have enough money on your account!" << content.downloadUrlDescription(0).priceAmount()
455  << " balance: " << item.balance();
456  Q_EMIT signalInformation(i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2", //
457  item.balance(),
458  content.downloadUrlDescription(0).priceAmount()));
459  }
460 }
461 
462 void AtticaProvider::downloadItemLoaded(BaseJob *baseJob)
463 {
464  if (!jobSuccess(baseJob)) {
465  return;
466  }
467 
468  auto *job = static_cast<ItemJob<DownloadItem> *>(baseJob);
469  DownloadItem item = job->result();
470 
471  EntryInternal entry = mDownloadLinkJobs.take(job).first;
472  entry.setPayload(QString(item.url().toString()));
473  Q_EMIT payloadLinkLoaded(entry);
474 }
475 
476 EntryInternal::List AtticaProvider::installedEntries() const
477 {
478  EntryInternal::List entries;
479  for (const EntryInternal &entry : qAsConst(mCachedEntries)) {
480  if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) {
481  entries.append(entry);
482  }
483  }
484  return entries;
485 }
486 
487 void AtticaProvider::vote(const EntryInternal &entry, uint rating)
488 {
489  PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating);
490  connect(job, &BaseJob::finished, this, &AtticaProvider::votingFinished);
491  job->start();
492 }
493 
494 void AtticaProvider::votingFinished(Attica::BaseJob *job)
495 {
496  if (!jobSuccess(job)) {
497  return;
498  }
499  Q_EMIT signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded."));
500 }
501 
502 void AtticaProvider::becomeFan(const EntryInternal &entry)
503 {
504  PostJob *job = m_provider.becomeFan(entry.uniqueId());
505  connect(job, &BaseJob::finished, this, &AtticaProvider::becomeFanFinished);
506  job->start();
507 }
508 
509 void AtticaProvider::becomeFanFinished(Attica::BaseJob *job)
510 {
511  if (!jobSuccess(job)) {
512  return;
513  }
514  Q_EMIT signalInformation(i18n("You are now a fan."));
515 }
516 
517 bool AtticaProvider::jobSuccess(Attica::BaseJob *job) const
518 {
519  if (job->metadata().error() == Attica::Metadata::NoError) {
520  return true;
521  }
522  qCDebug(KNEWSTUFFCORE) << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message();
523 
524  if (job->metadata().error() == Attica::Metadata::NetworkError) {
525  if (job->metadata().statusCode() == 503) {
526  QDateTime retryAfter;
527  static const QByteArray retryAfterKey{"Retry-After"};
528  for (const QNetworkReply::RawHeaderPair &headerPair : job->metadata().headers()) {
529  if (headerPair.first == retryAfterKey) {
530  // Retry-After is not a known header, so we need to do a bit of running around to make that work
531  // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that
532  // So, simple workaround, just pass it through a dummy request and get a formatted date out (the
533  // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...)
534  QNetworkRequest dummyRequest;
535  dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second);
536  retryAfter = dummyRequest.header(QNetworkRequest::LastModifiedHeader).toDateTime();
537  break;
538  }
539  }
540  static const KFormat formatter;
541  Q_EMIT signalErrorCode(KNSCore::TryAgainLaterError,
542  i18n("The service is currently undergoing maintenance and is expected to be back in %1.",
544  {retryAfter});
545  } else {
546  Q_EMIT signalErrorCode(KNSCore::NetworkError,
547  i18n("Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()),
548  job->metadata().statusCode());
549  }
550  }
551  if (job->metadata().error() == Attica::Metadata::OcsError) {
552  if (job->metadata().statusCode() == 200) {
553  Q_EMIT signalErrorCode(KNSCore::OcsError, i18n("Too many requests to server. Please try again in a few minutes."), job->metadata().statusCode());
554  } else if (job->metadata().statusCode() == 405) {
555  Q_EMIT signalErrorCode(KNSCore::OcsError,
556  i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()),
557  job->metadata().statusCode());
558  } else {
559  Q_EMIT signalErrorCode(KNSCore::OcsError,
560  i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()),
561  job->metadata().statusCode());
562  }
563  }
564  return false;
565 }
566 
567 EntryInternal AtticaProvider::entryFromAtticaContent(const Attica::Content &content)
568 {
569  EntryInternal entry;
570 
571  entry.setProviderId(id());
572  entry.setUniqueId(content.id());
573  entry.setStatus(KNS3::Entry::Downloadable);
574  entry.setVersion(content.version());
575  entry.setReleaseDate(content.updated().date());
576  entry.setCategory(content.attribute(QStringLiteral("typeid")));
577 
578  int index = mCachedEntries.indexOf(entry);
579  if (index >= 0) {
580  EntryInternal &cacheEntry = mCachedEntries[index];
581  // check if updateable
582  if (((cacheEntry.status() == KNS3::Entry::Installed) || (cacheEntry.status() == KNS3::Entry::Updateable))
583  && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
584  cacheEntry.setStatus(KNS3::Entry::Updateable);
585  cacheEntry.setUpdateVersion(entry.version());
586  cacheEntry.setUpdateReleaseDate(entry.releaseDate());
587  }
588  entry = cacheEntry;
589  } else {
590  mCachedEntries.append(entry);
591  }
592 
593  entry.setName(content.name());
594  entry.setHomepage(content.detailpage());
595  entry.setRating(content.rating());
596  entry.setNumberOfComments(content.numberOfComments());
597  entry.setDownloadCount(content.downloads());
598  entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt());
599  entry.setDonationLink(content.attribute(QStringLiteral("donationpage")));
600  entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage")));
601  entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt());
602  entry.setHomepage(content.detailpage());
603 
604  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), EntryInternal::PreviewSmall1);
605  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), EntryInternal::PreviewSmall2);
606  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), EntryInternal::PreviewSmall3);
607 
608  entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), EntryInternal::PreviewBig1);
609  entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), EntryInternal::PreviewBig2);
610  entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), EntryInternal::PreviewBig3);
611 
612  entry.setLicense(content.license());
613  Author author;
614  author.setId(content.author());
615  author.setName(content.author());
616  author.setHomepage(content.attribute(QStringLiteral("profilepage")));
617  entry.setAuthor(author);
618 
619  entry.setSource(EntryInternal::Online);
620  entry.setSummary(content.description());
621  entry.setShortSummary(content.summary());
622  entry.setChangelog(content.changelog());
623  entry.setTags(content.tags());
624 
627  for (const Attica::DownloadDescription &desc : descs) {
628  EntryInternal::DownloadLinkInformation info;
629  info.name = desc.name();
630  info.priceAmount = desc.priceAmount();
631  info.distributionType = desc.distributionType();
632  info.descriptionLink = desc.link();
633  info.id = desc.id();
634  info.size = desc.size();
635  info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload;
636  info.tags = desc.tags();
637  entry.appendDownloadLinkInformation(info);
638  }
639 
640  return entry;
641 }
642 
643 } // namespace
void setRating(int rating)
Sets the rating between 0 (worst) and 100 (best).
A way to ask a user a question from inside a GUI-less library (like KNewStuffCore) ...
Definition: question.h:41
The configuration file is missing or somehow incorrect. The configuration file filename will be held ...
Definition: errorcode.h:28
qint64 toMSecsSinceEpoch() const const
void setUniqueId(const QString &id)
Set the object&#39;s unique ID.
void setName(const QString &name)
Sets the full name of the author.
Definition: core/author.cpp:78
QString uniqueId() const
Get the object&#39;s unique ID.
QString name() const
QDomNode appendChild(const QDomNode &newChild)
void setId(const QString &id)
Sets the user ID of the author.
Definition: core/author.cpp:68
QVariant header(QNetworkRequest::KnownHeaders header) const const
void setHomepage(const QString &homepage)
Sets the homepage of the author.
QString toString(int indent) const const
QDateTime toDateTime() const const
void setCategory(const QString &category)
Sets the data category, e.g.
QString summary() const
Contains the core functionality for handling interaction with NewStuff providers. ...
QString attribute(const QString &key) const
void setKnowledgebaseLink(const QString &link)
Set the link for the knowledgebase.
used to keep track of a search
Definition: provider.h:66
QDate releaseDate() const
Retrieve the date of the object&#39;s publication.
KNewStuff author information.
Definition: core/author.h:30
QString toString(QUrl::FormattingOptions options) const const
void setShortSummary(const QString &summary)
Sets a short description of what the object is all about (should be very short)
QList< std::shared_ptr< KNSCore::Comment > > getCommentsList(const Attica::Comment::List &comments, std::shared_ptr< KNSCore::Comment > parent)
TODO KF6 QList is discouraged, and we&#39;ll probably want to switch this (and the rest of the KNS librar...
int size() const const
void setPayload(const QString &url)
Sets the object&#39;s file.
int downloads() const
qint64 currentMSecsSinceEpoch()
void setSummary(const QString &summary)
Sets a description (which can potentially be very long)
QString number(int n, int base)
int count(const T &value) const const
QString version() const
Retrieve the version string of the object.
void append(const T &value)
int numberOfComments() const
void setUpdateVersion(const QString &version)
Sets the version number that is available as update.
QStringList tags() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void setAdditionalAgentInformation(const QString &additionalInformation)
int toInt(bool *ok, int base) const const
bool isEmpty() const const
void setLicense(const QString &license)
Sets the license (abbreviation) applicable to the object.
typedef RawHeaderPair
void setAuthor(const Author &author)
Sets the author of the object.
void setSource(Source source)
The source of this entry can be Cache, Registry or Online -.
QString id() const
void setPreviewUrl(const QString &url, PreviewType type=PreviewSmall1)
Sets the object&#39;s preview file, if available.
void setNumberOfComments(int comments)
Sets the number of comments in the asset.
void setStatus(KNS3::Entry::Status status)
Sets the entry&#39;s status.
An error reported by the OCS API server. In signalErrorCode, this will be accompanied by the OCS erro...
Definition: errorcode.h:27
QString formatSpelloutDuration(quint64 msecs) const
KNewStuff Base Provider class.
Definition: provider.h:41
Apply simple filtering logic to a list of tags.
QDateTime updated() const
void setNumberFans(int fans)
Sets how many people are fans.
void setDownloadCount(int downloads)
Sets the number of downloads.
QList::iterator end()
QString description() const
void setVersion(const QString &version)
Sets the version number.
QString i18n(const char *text, const TYPE &arg...)
void setDonationLink(const QString &link)
Set a string representation of the URL for the donation website for this entry.
void setUpdateReleaseDate(const QDate &releasedate)
Sets the release date that is available as update.
Specific error condition for failed network calls which explicitly request an amount of time to wait ...
Definition: errorcode.h:33
QDate date() const const
QString name() const
KNewStuff data entry container.
Definition: entryinternal.h:49
void insert(int i, const T &value)
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
QDomNode cloneNode(bool deep) const const
void setTags(const QStringList &tags)
Set the tags for the content item.
bool filterAccepts(const QStringList &tags)
Check whether the filter list accepts the passed list of tags.
A network error. In signalErrorCode, this will be accompanied by the QtNetwork error code in the meta...
Definition: errorcode.h:26
QUrl detailpage() const
void setNumberKnowledgebaseEntries(int num)
Set the number of knowledgebase entries for this entry.
void setName(const QString &name)
Sets the name for this data object.
QString tagName() const const
void setHomepage(const QUrl &page)
Set a link to a website containing information about this entry.
KNS3::Entry::Status status() const
Retrieves the entry&#39;s status.
int compare(const QString &s1, const QString &s2) const const
void setChangelog(const QString &changelog)
The user written changelog.
int rating() const
QString name() const
Retrieve the name of the data object.
QUrl baseUrl() const
QList< DownloadDescription > downloadUrlDescriptions() const
void clearDownloadLinkInformation()
Remove all download options from this entry.
Definition: engine.h:28
QList::iterator begin()
void setReleaseDate(const QDate &releasedate)
Sets the release date.
void appendDownloadLinkInformation(const DownloadLinkInformation &info)
Add a new download option to this entry.
DownloadDescription downloadUrlDescription(int number) const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Jun 18 2021 22:41:48 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.