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/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 using namespace Attica;
28 
29 namespace KNSCore
30 {
31 AtticaProvider::AtticaProvider(const QStringList &categories, const QString &additionalAgentInformation)
32  : mEntryJob(nullptr)
33  , mInitialized(false)
34 {
35  // init categories map with invalid categories
36  for (const QString &category : categories) {
37  mCategoryMap.insert(category, Attica::Category());
38  }
39 
40  connect(&m_providerManager, &ProviderManager::providerAdded, this, [=](const Attica::Provider &provider) {
41  providerLoaded(provider);
42  m_provider.setAdditionalAgentInformation(additionalAgentInformation);
43  });
44  connect(&m_providerManager, &ProviderManager::authenticationCredentialsMissing, this, &AtticaProvider::onAuthenticationCredentialsMissing);
45  connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments);
46  connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson);
47  connect(this, &Provider::loadBasics, this, &AtticaProvider::loadBasics);
48 }
49 
50 AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString &additionalAgentInformation)
51  : mEntryJob(nullptr)
52  , mInitialized(false)
53 {
54  // init categories map with invalid categories
55  for (const QString &category : categories) {
56  mCategoryMap.insert(category, Attica::Category());
57  }
58  providerLoaded(provider);
59  m_provider.setAdditionalAgentInformation(additionalAgentInformation);
60 }
61 
62 QString AtticaProvider::id() const
63 {
64  return m_providerId;
65 }
66 
67 void AtticaProvider::onAuthenticationCredentialsMissing(const Attica::Provider &)
68 {
69  qCDebug(KNEWSTUFFCORE) << "Authentication missing!";
70  // FIXME Show authentication dialog
71 }
72 
73 bool AtticaProvider::setProviderXML(const QDomElement &xmldata)
74 {
75  if (xmldata.tagName() != QLatin1String("provider")) {
76  return false;
77  }
78 
79  // FIXME this is quite ugly, repackaging the xml into a string
80  QDomDocument doc(QStringLiteral("temp"));
81  qCDebug(KNEWSTUFFCORE) << "setting provider xml" << doc.toString();
82 
83  doc.appendChild(xmldata.cloneNode(true));
84  m_providerManager.addProviderFromXml(doc.toString());
85 
86  if (!m_providerManager.providers().isEmpty()) {
87  qCDebug(KNEWSTUFFCORE) << "base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString();
88  } else {
89  qCCritical(KNEWSTUFFCORE) << "Could not load provider.";
90  return false;
91  }
92  return true;
93 }
94 
95 void AtticaProvider::setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries)
96 {
97  mCachedEntries = cachedEntries;
98 }
99 
100 void AtticaProvider::providerLoaded(const Attica::Provider &provider)
101 {
102  mName = provider.name();
103  mIcon = provider.icon();
104  qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name();
105 
106  m_provider = provider;
107  m_provider.setAdditionalAgentInformation(mName);
108  m_providerId = provider.baseUrl().toString();
109 
110  Attica::ListJob<Attica::Category> *job = m_provider.requestCategories();
111  connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded);
112  job->start();
113 }
114 
115 void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob)
116 {
117  if (!jobSuccess(listJob)) {
118  return;
119  }
120 
121  qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys();
122 
123  auto *job = static_cast<Attica::ListJob<Attica::Category> *>(listJob);
124  const Category::List categoryList = job->itemList();
125 
126  QList<CategoryMetadata> categoryMetadataList;
127  for (const Category &category : categoryList) {
128  if (mCategoryMap.contains(category.name())) {
129  qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName();
130  // If there is only the placeholder category, replace it
131  if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) {
132  mCategoryMap.replace(category.name(), category);
133  } else {
134  mCategoryMap.insert(category.name(), category);
135  }
136 
137  CategoryMetadata categoryMetadata;
138  categoryMetadata.id = category.id();
139  categoryMetadata.name = category.name();
140  categoryMetadata.displayName = category.displayName();
141  categoryMetadataList << categoryMetadata;
142  }
143  }
144  std::sort(categoryMetadataList.begin(),
145  categoryMetadataList.end(),
146  [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool {
147  const QString a(i.displayName.isEmpty() ? i.name : i.displayName);
148  const QString b(j.displayName.isEmpty() ? j.name : j.displayName);
149 
150  return (QCollator().compare(a, b) < 0);
151  });
152 
153  bool correct = false;
154  for (auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) {
155  if (!it.value().isValid()) {
156  qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key();
157  } else {
158  correct = true;
159  }
160  }
161 
162  if (correct) {
163  mInitialized = true;
164  Q_EMIT providerInitialized(this);
165  Q_EMIT categoriesMetadataLoded(categoryMetadataList);
166  } else {
167  Q_EMIT signalErrorCode(KNSCore::ConfigFileError, i18n("All categories are missing"), QVariant());
168  }
169 }
170 
171 bool AtticaProvider::isInitialized() const
172 {
173  return mInitialized;
174 }
175 
176 void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request)
177 {
178  if (mEntryJob) {
179  mEntryJob->abort();
180  mEntryJob = nullptr;
181  }
182 
183  mCurrentRequest = request;
184  switch (request.filter) {
185  case None:
186  break;
187  case ExactEntryId: {
188  ItemJob<Content> *job = m_provider.requestContent(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, EntryInternal::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  connect(job, &BaseJob::finished, this, &AtticaProvider::categoryContentsLoaded);
220 
221  mEntryJob = job;
222  job->start();
223 }
224 
225 void AtticaProvider::checkForUpdates()
226 {
227  if (mCachedEntries.isEmpty()) {
228  Q_EMIT loadingFinished(mCurrentRequest, {});
229  }
230 
231  for (const EntryInternal &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 
240 void AtticaProvider::loadEntryDetails(const KNSCore::EntryInternal &entry)
241 {
242  ItemJob<Content> *job = m_provider.requestContent(entry.uniqueId());
243  connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded);
244  job->start();
245 }
246 
247 void AtticaProvider::detailsLoaded(BaseJob *job)
248 {
249  if (jobSuccess(job)) {
250  auto *contentJob = static_cast<ItemJob<Content> *>(job);
251  Content content = contentJob->result();
252  EntryInternal entry = entryFromAtticaContent(content);
253  Q_EMIT entryDetailsLoaded(entry);
254  qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name();
255  }
256 
257  if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
258  qCDebug(KNEWSTUFFCORE) << "check update finished.";
259  QList<EntryInternal> updatable;
260  for (const EntryInternal &entry : std::as_const(mCachedEntries)) {
261  if (entry.status() == KNS3::Entry::Updateable) {
262  updatable.append(entry);
263  }
264  }
265  Q_EMIT loadingFinished(mCurrentRequest, updatable);
266  }
267 }
268 
269 void AtticaProvider::categoryContentsLoaded(BaseJob *job)
270 {
271  if (!jobSuccess(job)) {
272  return;
273  }
274 
275  auto *listJob = static_cast<ListJob<Content> *>(job);
276  const Content::List contents = listJob->itemList();
277 
278  EntryInternal::List entries;
279  TagsFilterChecker checker(tagFilter());
280  TagsFilterChecker downloadschecker(downloadTagFilter());
281  for (const Content &content : contents) {
282  if (!content.isValid()) {
283  qCDebug(KNEWSTUFFCORE)
284  << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
285  << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories;
286  continue;
287  }
288  if (checker.filterAccepts(content.tags())) {
289  bool filterAcceptsDownloads = true;
290  if (content.downloads() > 0) {
291  filterAcceptsDownloads = false;
292  const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions();
293  for (const Attica::DownloadDescription &dli : descs) {
294  if (downloadschecker.filterAccepts(dli.tags())) {
295  filterAcceptsDownloads = true;
296  break;
297  }
298  }
299  }
300  if (filterAcceptsDownloads) {
301  mCachedContent.insert(content.id(), content);
302  entries.append(entryFromAtticaContent(content));
303  } else {
304  qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter();
305  }
306  } else {
307  qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter();
308  }
309  }
310 
311  qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size();
312  Q_EMIT loadingFinished(mCurrentRequest, entries);
313  mEntryJob = nullptr;
314 }
315 
316 Attica::Provider::SortMode AtticaProvider::atticaSortMode(const SortMode &sortMode)
317 {
318  switch (sortMode) {
319  case Newest:
320  return Attica::Provider::Newest;
321  case Alphabetical:
322  return Attica::Provider::Alphabetical;
323  case Downloads:
324  return Attica::Provider::Downloads;
325  default:
326  return Attica::Provider::Rating;
327  }
328 }
329 
330 void AtticaProvider::loadPayloadLink(const KNSCore::EntryInternal &entry, int linkId)
331 {
332  Attica::Content content = mCachedContent.value(entry.uniqueId());
333  const DownloadDescription desc = content.downloadUrlDescription(linkId);
334 
335  if (desc.hasPrice()) {
336  // Ask for balance, then show information...
337  ItemJob<AccountBalance> *job = m_provider.requestAccountBalance();
338  connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded);
339  mDownloadLinkJobs[job] = qMakePair(entry, linkId);
340  job->start();
341 
342  qCDebug(KNEWSTUFFCORE) << "get account balance";
343  } else {
344  ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId));
345  connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
346  mDownloadLinkJobs[job] = qMakePair(entry, linkId);
347  job->start();
348 
349  qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId();
350  }
351 }
352 
353 void AtticaProvider::loadComments(const EntryInternal &entry, int commentsPerPage, int page)
354 {
355  ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral("0"), page, commentsPerPage);
356  connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments);
357  job->start();
358 }
359 
360 /// TODO KF6 QList is discouraged, and we'll probably want to switch this (and the rest of the KNS library) to QVector instead
361 QList<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 
385 void 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 
398 void 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 
408 void 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 
428 void 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 
435 void 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 
462 void 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<EntryInternal, int> pair = mDownloadLinkJobs.take(job);
472  EntryInternal 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.setQuestion(i18nc("the price of a download item, parameter 1 is the currency, 2 is the price",
479  "This item costs %1 %2.\nDo you want to buy it?",
480  item.currency(),
481  content.downloadUrlDescription(pair.second).priceAmount()));
482  if (question.ask() == Question::YesResponse) {
483  ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second));
484  connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded);
485  mDownloadLinkJobs[job] = qMakePair(entry, pair.second);
486  job->start();
487  } else {
488  return;
489  }
490  } else {
491  qCDebug(KNEWSTUFFCORE) << "You don't have enough money on your account!" << content.downloadUrlDescription(0).priceAmount()
492  << " balance: " << item.balance();
493  Q_EMIT signalInformation(i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2", //
494  item.balance(),
495  content.downloadUrlDescription(0).priceAmount()));
496  }
497 }
498 
499 void AtticaProvider::downloadItemLoaded(BaseJob *baseJob)
500 {
501  if (!jobSuccess(baseJob)) {
502  return;
503  }
504 
505  auto *job = static_cast<ItemJob<DownloadItem> *>(baseJob);
506  DownloadItem item = job->result();
507 
508  EntryInternal entry = mDownloadLinkJobs.take(job).first;
509  entry.setPayload(QString(item.url().toString()));
510  Q_EMIT payloadLinkLoaded(entry);
511 }
512 
513 EntryInternal::List AtticaProvider::installedEntries() const
514 {
515  EntryInternal::List entries;
516  for (const EntryInternal &entry : std::as_const(mCachedEntries)) {
517  if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) {
518  entries.append(entry);
519  }
520  }
521  return entries;
522 }
523 
524 void AtticaProvider::vote(const EntryInternal &entry, uint rating)
525 {
526  PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating);
527  connect(job, &BaseJob::finished, this, &AtticaProvider::votingFinished);
528  job->start();
529 }
530 
531 void AtticaProvider::votingFinished(Attica::BaseJob *job)
532 {
533  if (!jobSuccess(job)) {
534  return;
535  }
536  Q_EMIT signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded."));
537 }
538 
539 void AtticaProvider::becomeFan(const EntryInternal &entry)
540 {
541  PostJob *job = m_provider.becomeFan(entry.uniqueId());
542  connect(job, &BaseJob::finished, this, &AtticaProvider::becomeFanFinished);
543  job->start();
544 }
545 
546 void AtticaProvider::becomeFanFinished(Attica::BaseJob *job)
547 {
548  if (!jobSuccess(job)) {
549  return;
550  }
551  Q_EMIT signalInformation(i18n("You are now a fan."));
552 }
553 
554 bool AtticaProvider::jobSuccess(Attica::BaseJob *job) const
555 {
556  if (job->metadata().error() == Attica::Metadata::NoError) {
557  return true;
558  }
559  qCDebug(KNEWSTUFFCORE) << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message();
560 
561  if (job->metadata().error() == Attica::Metadata::NetworkError) {
562  if (job->metadata().statusCode() == 503) {
563  QDateTime retryAfter;
564  static const QByteArray retryAfterKey{"Retry-After"};
565  for (const QNetworkReply::RawHeaderPair &headerPair : job->metadata().headers()) {
566  if (headerPair.first == retryAfterKey) {
567  // Retry-After is not a known header, so we need to do a bit of running around to make that work
568  // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that
569  // So, simple workaround, just pass it through a dummy request and get a formatted date out (the
570  // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...)
571  QNetworkRequest dummyRequest;
572  dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second);
573  retryAfter = dummyRequest.header(QNetworkRequest::LastModifiedHeader).toDateTime();
574  break;
575  }
576  }
577  static const KFormat formatter;
578  Q_EMIT signalErrorCode(KNSCore::TryAgainLaterError,
579  i18n("The service is currently undergoing maintenance and is expected to be back in %1.",
581  {retryAfter});
582  } else {
583  Q_EMIT signalErrorCode(KNSCore::NetworkError,
584  i18n("Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()),
585  job->metadata().statusCode());
586  }
587  }
588  if (job->metadata().error() == Attica::Metadata::OcsError) {
589  if (job->metadata().statusCode() == 200) {
590  Q_EMIT signalErrorCode(KNSCore::OcsError, i18n("Too many requests to server. Please try again in a few minutes."), job->metadata().statusCode());
591  } else if (job->metadata().statusCode() == 405) {
592  Q_EMIT signalErrorCode(KNSCore::OcsError,
593  i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()),
594  job->metadata().statusCode());
595  } else {
596  Q_EMIT signalErrorCode(KNSCore::OcsError,
597  i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()),
598  job->metadata().statusCode());
599  }
600  }
601  return false;
602 }
603 
604 EntryInternal AtticaProvider::entryFromAtticaContent(const Attica::Content &content)
605 {
606  EntryInternal entry;
607 
608  entry.setProviderId(id());
609  entry.setUniqueId(content.id());
610  entry.setStatus(KNS3::Entry::Downloadable);
611  entry.setVersion(content.version());
612  entry.setReleaseDate(content.updated().date());
613  entry.setCategory(content.attribute(QStringLiteral("typeid")));
614 
615  int index = mCachedEntries.indexOf(entry);
616  if (index >= 0) {
617  EntryInternal &cacheEntry = mCachedEntries[index];
618  // check if updateable
619  if (((cacheEntry.status() == KNS3::Entry::Installed) || (cacheEntry.status() == KNS3::Entry::Updateable))
620  && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
621  cacheEntry.setStatus(KNS3::Entry::Updateable);
622  cacheEntry.setUpdateVersion(entry.version());
623  cacheEntry.setUpdateReleaseDate(entry.releaseDate());
624  }
625  entry = cacheEntry;
626  } else {
627  mCachedEntries.append(entry);
628  }
629 
630  entry.setName(content.name());
631  entry.setHomepage(content.detailpage());
632  entry.setRating(content.rating());
633  entry.setNumberOfComments(content.numberOfComments());
634  entry.setDownloadCount(content.downloads());
635  entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt());
636  entry.setDonationLink(content.attribute(QStringLiteral("donationpage")));
637  entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage")));
638  entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt());
639  entry.setHomepage(content.detailpage());
640 
641  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), EntryInternal::PreviewSmall1);
642  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), EntryInternal::PreviewSmall2);
643  entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), EntryInternal::PreviewSmall3);
644 
645  entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), EntryInternal::PreviewBig1);
646  entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), EntryInternal::PreviewBig2);
647  entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), EntryInternal::PreviewBig3);
648 
649  entry.setLicense(content.license());
650  Author author;
651  author.setId(content.author());
652  author.setName(content.author());
653  author.setHomepage(content.attribute(QStringLiteral("profilepage")));
654  entry.setAuthor(author);
655 
656  entry.setSource(EntryInternal::Online);
657  entry.setSummary(content.description());
658  entry.setShortSummary(content.summary());
659  entry.setChangelog(content.changelog());
660  entry.setTags(content.tags());
661 
664  for (const Attica::DownloadDescription &desc : descs) {
665  EntryInternal::DownloadLinkInformation info;
666  info.name = desc.name();
667  info.priceAmount = desc.priceAmount();
668  info.distributionType = desc.distributionType();
669  info.descriptionLink = desc.link();
670  info.id = desc.id();
671  info.size = desc.size();
672  info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload;
673  info.tags = desc.tags();
674  entry.appendDownloadLinkInformation(info);
675  }
676 
677  return entry;
678 }
679 
680 } // 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.
QUrl icon() const
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:72
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
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 Oct 22 2021 22:43:11 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.