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 
662  entry.clearDownloadLinkInformation();
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
int rating() const
void append(const T &value)
used to keep track of a search
Definition: provider.h:72
QString toString(int indent) const const
QString uniqueId() const
Get the object's unique ID.
QList< DownloadDescription > downloadUrlDescriptions() const
KNewStuff data entry container.
Definition: entryinternal.h:52
QString number(int n, int base)
QString tagName() const const
QUrl baseUrl() const
QString description() const
int count(const T &value) const const
QString id() const
qint64 currentMSecsSinceEpoch()
bool filterAccepts(const QStringList &tags)
Check whether the filter list accepts the passed list of tags.
QVariant header(QNetworkRequest::KnownHeaders header) const const
@ NetworkError
A network error. In signalErrorCode, this will be accompanied by the QtNetwork error code in the meta...
Definition: errorcode.h:26
void setHost(ScriptableExtension *host)
QString summary() const
QDomNode cloneNode(bool deep) const const
QString name() const
@ ConfigFileError
The configuration file is missing or somehow incorrect. The configuration file filename will be held ...
Definition: errorcode.h:28
int size() const const
Apply simple filtering logic to a list of tags.
QString toString(QUrl::FormattingOptions options) const const
QString name() const
QString i18n(const char *text, const TYPE &arg...)
QUrl icon() const
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'll probably want to switch this (and the rest of the KNS librar...
QStringList tags() const
qint64 toMSecsSinceEpoch() const const
bool isEmpty() const const
Author
KSharedConfigPtr config()
void insert(int i, const T &value)
typedef RawHeaderPair
bool setProperty(const char *name, const QVariant &value)
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
QDateTime toDateTime() const const
QString formatSpelloutDuration(quint64 msecs) const
QDomNode appendChild(const QDomNode &newChild)
int numberOfComments() const
QString attribute(const QString &key) const
QString name(StandardShortcut id)
QDate date() const const
DownloadDescription downloadUrlDescription(int number) const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QList::iterator begin()
@ TryAgainLaterError
Specific error condition for failed network calls which explicitly request an amount of time to wait ...
Definition: errorcode.h:33
QUrl detailpage() const
int downloads() const
Contains the core functionality for handling interaction with NewStuff providers.
QList::iterator end()
void setPayload(const QString &url)
Sets the object's file.
QDateTime updated() const
QString name() const
Retrieve the name of the data object.
void setAdditionalAgentInformation(const QString &additionalInformation)
QString toString() const const
QVariant property(const char *name) const const
KNS3::Entry::Status status() const
Retrieves the entry's status.
@ OcsError
An error reported by the OCS API server. In signalErrorCode, this will be accompanied by the OCS erro...
Definition: errorcode.h:27
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Oct 6 2022 03:56:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.