File indexing completed on 2025-04-20 12:26:24
0001 /* 0002 SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "atticaprovider_p.h" 0008 0009 #include "commentsmodel.h" 0010 #include "question.h" 0011 #include "tagsfilterchecker.h" 0012 0013 #include <KFormat> 0014 #include <KLocalizedString> 0015 #include <QCollator> 0016 #include <knewstuffcore_debug.h> 0017 0018 #include <attica/accountbalance.h> 0019 #include <attica/config.h> 0020 #include <attica/content.h> 0021 #include <attica/downloaditem.h> 0022 #include <attica/listjob.h> 0023 #include <attica/person.h> 0024 #include <attica/provider.h> 0025 #include <attica/providermanager.h> 0026 0027 using namespace Attica; 0028 0029 namespace KNSCore 0030 { 0031 AtticaProvider::AtticaProvider(const QStringList &categories, const QString &additionalAgentInformation) 0032 : mEntryJob(nullptr) 0033 , mInitialized(false) 0034 { 0035 // init categories map with invalid categories 0036 for (const QString &category : categories) { 0037 mCategoryMap.insert(category, Attica::Category()); 0038 } 0039 0040 connect(&m_providerManager, &ProviderManager::providerAdded, this, [=](const Attica::Provider &provider) { 0041 providerLoaded(provider); 0042 m_provider.setAdditionalAgentInformation(additionalAgentInformation); 0043 }); 0044 connect(&m_providerManager, &ProviderManager::authenticationCredentialsMissing, this, &AtticaProvider::onAuthenticationCredentialsMissing); 0045 connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments); 0046 connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson); 0047 connect(this, &Provider::loadBasics, this, &AtticaProvider::loadBasics); 0048 } 0049 0050 AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString &additionalAgentInformation) 0051 : mEntryJob(nullptr) 0052 , mInitialized(false) 0053 { 0054 // init categories map with invalid categories 0055 for (const QString &category : categories) { 0056 mCategoryMap.insert(category, Attica::Category()); 0057 } 0058 providerLoaded(provider); 0059 m_provider.setAdditionalAgentInformation(additionalAgentInformation); 0060 } 0061 0062 QString AtticaProvider::id() const 0063 { 0064 return m_providerId; 0065 } 0066 0067 void AtticaProvider::onAuthenticationCredentialsMissing(const Attica::Provider &) 0068 { 0069 qCDebug(KNEWSTUFFCORE) << "Authentication missing!"; 0070 // FIXME Show authentication dialog 0071 } 0072 0073 bool AtticaProvider::setProviderXML(const QDomElement &xmldata) 0074 { 0075 if (xmldata.tagName() != QLatin1String("provider")) { 0076 return false; 0077 } 0078 0079 // FIXME this is quite ugly, repackaging the xml into a string 0080 QDomDocument doc(QStringLiteral("temp")); 0081 qCDebug(KNEWSTUFFCORE) << "setting provider xml" << doc.toString(); 0082 0083 doc.appendChild(xmldata.cloneNode(true)); 0084 m_providerManager.addProviderFromXml(doc.toString()); 0085 0086 if (!m_providerManager.providers().isEmpty()) { 0087 qCDebug(KNEWSTUFFCORE) << "base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString(); 0088 } else { 0089 qCCritical(KNEWSTUFFCORE) << "Could not load provider."; 0090 return false; 0091 } 0092 return true; 0093 } 0094 0095 void AtticaProvider::setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries) 0096 { 0097 mCachedEntries = cachedEntries; 0098 } 0099 0100 void AtticaProvider::providerLoaded(const Attica::Provider &provider) 0101 { 0102 mName = provider.name(); 0103 mIcon = provider.icon(); 0104 qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name(); 0105 0106 m_provider = provider; 0107 m_provider.setAdditionalAgentInformation(mName); 0108 m_providerId = provider.baseUrl().toString(); 0109 0110 Attica::ListJob<Attica::Category> *job = m_provider.requestCategories(); 0111 connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded); 0112 job->start(); 0113 } 0114 0115 void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob) 0116 { 0117 if (!jobSuccess(listJob)) { 0118 return; 0119 } 0120 0121 qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys(); 0122 0123 auto *job = static_cast<Attica::ListJob<Attica::Category> *>(listJob); 0124 const Category::List categoryList = job->itemList(); 0125 0126 QList<CategoryMetadata> categoryMetadataList; 0127 for (const Category &category : categoryList) { 0128 if (mCategoryMap.contains(category.name())) { 0129 qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName(); 0130 // If there is only the placeholder category, replace it 0131 if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) { 0132 mCategoryMap.replace(category.name(), category); 0133 } else { 0134 mCategoryMap.insert(category.name(), category); 0135 } 0136 0137 CategoryMetadata categoryMetadata; 0138 categoryMetadata.id = category.id(); 0139 categoryMetadata.name = category.name(); 0140 categoryMetadata.displayName = category.displayName(); 0141 categoryMetadataList << categoryMetadata; 0142 } 0143 } 0144 std::sort(categoryMetadataList.begin(), 0145 categoryMetadataList.end(), 0146 [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool { 0147 const QString a(i.displayName.isEmpty() ? i.name : i.displayName); 0148 const QString b(j.displayName.isEmpty() ? j.name : j.displayName); 0149 0150 return (QCollator().compare(a, b) < 0); 0151 }); 0152 0153 bool correct = false; 0154 for (auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) { 0155 if (!it.value().isValid()) { 0156 qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key(); 0157 } else { 0158 correct = true; 0159 } 0160 } 0161 0162 if (correct) { 0163 mInitialized = true; 0164 Q_EMIT providerInitialized(this); 0165 Q_EMIT categoriesMetadataLoded(categoryMetadataList); 0166 } else { 0167 Q_EMIT signalErrorCode(KNSCore::ConfigFileError, i18n("All categories are missing"), QVariant()); 0168 } 0169 } 0170 0171 bool AtticaProvider::isInitialized() const 0172 { 0173 return mInitialized; 0174 } 0175 0176 void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request) 0177 { 0178 if (mEntryJob) { 0179 mEntryJob->abort(); 0180 mEntryJob = nullptr; 0181 } 0182 0183 mCurrentRequest = request; 0184 switch (request.filter) { 0185 case None: 0186 break; 0187 case ExactEntryId: { 0188 ItemJob<Content> *job = m_provider.requestContent(request.searchTerm); 0189 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); 0190 job->start(); 0191 return; 0192 } 0193 case Installed: 0194 if (request.page == 0) { 0195 Q_EMIT loadingFinished(request, installedEntries()); 0196 } else { 0197 Q_EMIT loadingFinished(request, EntryInternal::List()); 0198 } 0199 return; 0200 case Updates: 0201 checkForUpdates(); 0202 return; 0203 } 0204 0205 Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode); 0206 Attica::Category::List categoriesToSearch; 0207 0208 if (request.categories.isEmpty()) { 0209 // search in all categories 0210 categoriesToSearch = mCategoryMap.values(); 0211 } else { 0212 categoriesToSearch.reserve(request.categories.size()); 0213 for (const QString &categoryName : std::as_const(request.categories)) { 0214 categoriesToSearch.append(mCategoryMap.values(categoryName)); 0215 } 0216 } 0217 0218 ListJob<Content> *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize); 0219 connect(job, &BaseJob::finished, this, &AtticaProvider::categoryContentsLoaded); 0220 0221 mEntryJob = job; 0222 job->start(); 0223 } 0224 0225 void AtticaProvider::checkForUpdates() 0226 { 0227 if (mCachedEntries.isEmpty()) { 0228 Q_EMIT loadingFinished(mCurrentRequest, {}); 0229 } 0230 0231 for (const EntryInternal &e : std::as_const(mCachedEntries)) { 0232 ItemJob<Content> *job = m_provider.requestContent(e.uniqueId()); 0233 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); 0234 m_updateJobs.insert(job); 0235 job->start(); 0236 qCDebug(KNEWSTUFFCORE) << "Checking for update: " << e.name(); 0237 } 0238 } 0239 0240 void AtticaProvider::loadEntryDetails(const KNSCore::EntryInternal &entry) 0241 { 0242 ItemJob<Content> *job = m_provider.requestContent(entry.uniqueId()); 0243 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); 0244 job->start(); 0245 } 0246 0247 void AtticaProvider::detailsLoaded(BaseJob *job) 0248 { 0249 if (jobSuccess(job)) { 0250 auto *contentJob = static_cast<ItemJob<Content> *>(job); 0251 Content content = contentJob->result(); 0252 EntryInternal entry = entryFromAtticaContent(content); 0253 Q_EMIT entryDetailsLoaded(entry); 0254 qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name(); 0255 } 0256 0257 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) { 0258 qCDebug(KNEWSTUFFCORE) << "check update finished."; 0259 QList<EntryInternal> updatable; 0260 for (const EntryInternal &entry : std::as_const(mCachedEntries)) { 0261 if (entry.status() == KNS3::Entry::Updateable) { 0262 updatable.append(entry); 0263 } 0264 } 0265 Q_EMIT loadingFinished(mCurrentRequest, updatable); 0266 } 0267 } 0268 0269 void AtticaProvider::categoryContentsLoaded(BaseJob *job) 0270 { 0271 if (!jobSuccess(job)) { 0272 return; 0273 } 0274 0275 auto *listJob = static_cast<ListJob<Content> *>(job); 0276 const Content::List contents = listJob->itemList(); 0277 0278 EntryInternal::List entries; 0279 TagsFilterChecker checker(tagFilter()); 0280 TagsFilterChecker downloadschecker(downloadTagFilter()); 0281 for (const Content &content : contents) { 0282 if (!content.isValid()) { 0283 qCDebug(KNEWSTUFFCORE) 0284 << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of" 0285 << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories; 0286 continue; 0287 } 0288 if (checker.filterAccepts(content.tags())) { 0289 bool filterAcceptsDownloads = true; 0290 if (content.downloads() > 0) { 0291 filterAcceptsDownloads = false; 0292 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions(); 0293 for (const Attica::DownloadDescription &dli : descs) { 0294 if (downloadschecker.filterAccepts(dli.tags())) { 0295 filterAcceptsDownloads = true; 0296 break; 0297 } 0298 } 0299 } 0300 if (filterAcceptsDownloads) { 0301 mCachedContent.insert(content.id(), content); 0302 entries.append(entryFromAtticaContent(content)); 0303 } else { 0304 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter(); 0305 } 0306 } else { 0307 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter(); 0308 } 0309 } 0310 0311 qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size(); 0312 Q_EMIT loadingFinished(mCurrentRequest, entries); 0313 mEntryJob = nullptr; 0314 } 0315 0316 Attica::Provider::SortMode AtticaProvider::atticaSortMode(const SortMode &sortMode) 0317 { 0318 switch (sortMode) { 0319 case Newest: 0320 return Attica::Provider::Newest; 0321 case Alphabetical: 0322 return Attica::Provider::Alphabetical; 0323 case Downloads: 0324 return Attica::Provider::Downloads; 0325 default: 0326 return Attica::Provider::Rating; 0327 } 0328 } 0329 0330 void AtticaProvider::loadPayloadLink(const KNSCore::EntryInternal &entry, int linkId) 0331 { 0332 Attica::Content content = mCachedContent.value(entry.uniqueId()); 0333 const DownloadDescription desc = content.downloadUrlDescription(linkId); 0334 0335 if (desc.hasPrice()) { 0336 // Ask for balance, then show information... 0337 ItemJob<AccountBalance> *job = m_provider.requestAccountBalance(); 0338 connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded); 0339 mDownloadLinkJobs[job] = qMakePair(entry, linkId); 0340 job->start(); 0341 0342 qCDebug(KNEWSTUFFCORE) << "get account balance"; 0343 } else { 0344 ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId)); 0345 connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded); 0346 mDownloadLinkJobs[job] = qMakePair(entry, linkId); 0347 job->start(); 0348 0349 qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId(); 0350 } 0351 } 0352 0353 void AtticaProvider::loadComments(const EntryInternal &entry, int commentsPerPage, int page) 0354 { 0355 ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral("0"), page, commentsPerPage); 0356 connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments); 0357 job->start(); 0358 } 0359 0360 /// TODO KF6 QList is discouraged, and we'll probably want to switch this (and the rest of the KNS library) to QVector instead 0361 QList<std::shared_ptr<KNSCore::Comment>> getCommentsList(const Attica::Comment::List &comments, std::shared_ptr<KNSCore::Comment> parent) 0362 { 0363 QList<std::shared_ptr<KNSCore::Comment>> knsComments; 0364 for (const Attica::Comment &comment : comments) { 0365 qCDebug(KNEWSTUFFCORE) << "Appending comment with id" << comment.id() << ", which has" << comment.childCount() << "children"; 0366 auto knsComment = std::make_shared<KNSCore::Comment>(); 0367 knsComment->id = comment.id(); 0368 knsComment->subject = comment.subject(); 0369 knsComment->text = comment.text(); 0370 knsComment->childCount = comment.childCount(); 0371 knsComment->username = comment.user(); 0372 knsComment->date = comment.date(); 0373 knsComment->score = comment.score(); 0374 knsComment->parent = parent; 0375 knsComments << knsComment; 0376 if (comment.childCount() > 0) { 0377 qCDebug(KNEWSTUFFCORE) << "Getting more comments, as this one has children, and we currently have this number of comments:" << knsComments.count(); 0378 knsComments << getCommentsList(comment.children(), knsComment); 0379 qCDebug(KNEWSTUFFCORE) << "After getting the children, we now have the following number of comments:" << knsComments.count(); 0380 } 0381 } 0382 return knsComments; 0383 } 0384 0385 void AtticaProvider::loadedComments(Attica::BaseJob *baseJob) 0386 { 0387 if (!jobSuccess(baseJob)) { 0388 return; 0389 } 0390 0391 auto *job = static_cast<ListJob<Attica::Comment> *>(baseJob); 0392 Attica::Comment::List comments = job->itemList(); 0393 0394 QList<std::shared_ptr<KNSCore::Comment>> receivedComments = getCommentsList(comments, nullptr); 0395 Q_EMIT commentsLoaded(receivedComments); 0396 } 0397 0398 void AtticaProvider::loadPerson(const QString &username) 0399 { 0400 if (m_provider.hasPersonService()) { 0401 ItemJob<Attica::Person> *job = m_provider.requestPerson(username); 0402 job->setProperty("username", username); 0403 connect(job, &BaseJob::finished, this, &AtticaProvider::loadedPerson); 0404 job->start(); 0405 } 0406 } 0407 0408 void AtticaProvider::loadedPerson(Attica::BaseJob *baseJob) 0409 { 0410 if (!jobSuccess(baseJob)) { 0411 return; 0412 } 0413 0414 auto *job = static_cast<ItemJob<Attica::Person> *>(baseJob); 0415 Attica::Person person = job->result(); 0416 0417 auto author = std::make_shared<KNSCore::Author>(); 0418 // This is a touch hack-like, but it ensures we actually have the data in case it is not returned by the server 0419 author->setId(job->property("username").toString()); 0420 author->setName(QStringLiteral("%1 %2").arg(person.firstName(), person.lastName()).trimmed()); 0421 author->setHomepage(person.homepage()); 0422 author->setProfilepage(person.extendedAttribute(QStringLiteral("profilepage"))); 0423 author->setAvatarUrl(person.avatarUrl()); 0424 author->setDescription(person.extendedAttribute(QStringLiteral("description"))); 0425 Q_EMIT personLoaded(author); 0426 } 0427 0428 void AtticaProvider::loadBasics() 0429 { 0430 Attica::ItemJob<Attica::Config> *configJob = m_provider.requestConfig(); 0431 connect(configJob, &BaseJob::finished, this, &AtticaProvider::loadedConfig); 0432 configJob->start(); 0433 } 0434 0435 void AtticaProvider::loadedConfig(Attica::BaseJob *baseJob) 0436 { 0437 if (jobSuccess(baseJob)) { 0438 auto *job = static_cast<ItemJob<Attica::Config> *>(baseJob); 0439 Attica::Config config = job->result(); 0440 setVersion(config.version()); 0441 setSupportsSsl(config.ssl()); 0442 setContactEmail(config.contact()); 0443 QString protocol{QStringLiteral("http")}; 0444 if (config.ssl()) { 0445 protocol = QStringLiteral("https"); 0446 } 0447 // There is usually no protocol in the website and host, but in case 0448 // there is, trust what's there 0449 if (config.website().contains(QLatin1String("://"))) { 0450 setWebsite(QUrl(config.website())); 0451 } else { 0452 setWebsite(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.website()))); 0453 } 0454 if (config.host().contains(QLatin1String("://"))) { 0455 setHost(QUrl(config.host())); 0456 } else { 0457 setHost(QUrl(QLatin1String("%1://%2").arg(protocol).arg(config.host()))); 0458 } 0459 } 0460 } 0461 0462 void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob) 0463 { 0464 if (!jobSuccess(baseJob)) { 0465 return; 0466 } 0467 0468 auto *job = static_cast<ItemJob<AccountBalance> *>(baseJob); 0469 AccountBalance item = job->result(); 0470 0471 QPair<EntryInternal, int> pair = mDownloadLinkJobs.take(job); 0472 EntryInternal entry(pair.first); 0473 Content content = mCachedContent.value(entry.uniqueId()); 0474 if (content.downloadUrlDescription(pair.second).priceAmount() < item.balance()) { 0475 qCDebug(KNEWSTUFFCORE) << "Your balance is greater than the price." << content.downloadUrlDescription(pair.second).priceAmount() 0476 << " balance: " << item.balance(); 0477 Question question; 0478 question.setEntry(entry); 0479 question.setQuestion(i18nc("the price of a download item, parameter 1 is the currency, 2 is the price", 0480 "This item costs %1 %2.\nDo you want to buy it?", 0481 item.currency(), 0482 content.downloadUrlDescription(pair.second).priceAmount())); 0483 if (question.ask() == Question::YesResponse) { 0484 ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second)); 0485 connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded); 0486 mDownloadLinkJobs[job] = qMakePair(entry, pair.second); 0487 job->start(); 0488 } else { 0489 return; 0490 } 0491 } else { 0492 qCDebug(KNEWSTUFFCORE) << "You don't have enough money on your account!" << content.downloadUrlDescription(0).priceAmount() 0493 << " balance: " << item.balance(); 0494 Q_EMIT signalInformation(i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2", // 0495 item.balance(), 0496 content.downloadUrlDescription(0).priceAmount())); 0497 } 0498 } 0499 0500 void AtticaProvider::downloadItemLoaded(BaseJob *baseJob) 0501 { 0502 if (!jobSuccess(baseJob)) { 0503 return; 0504 } 0505 0506 auto *job = static_cast<ItemJob<DownloadItem> *>(baseJob); 0507 DownloadItem item = job->result(); 0508 0509 EntryInternal entry = mDownloadLinkJobs.take(job).first; 0510 entry.setPayload(QString(item.url().toString())); 0511 Q_EMIT payloadLinkLoaded(entry); 0512 } 0513 0514 EntryInternal::List AtticaProvider::installedEntries() const 0515 { 0516 EntryInternal::List entries; 0517 for (const EntryInternal &entry : std::as_const(mCachedEntries)) { 0518 if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) { 0519 entries.append(entry); 0520 } 0521 } 0522 return entries; 0523 } 0524 0525 void AtticaProvider::vote(const EntryInternal &entry, uint rating) 0526 { 0527 PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating); 0528 connect(job, &BaseJob::finished, this, &AtticaProvider::votingFinished); 0529 job->start(); 0530 } 0531 0532 void AtticaProvider::votingFinished(Attica::BaseJob *job) 0533 { 0534 if (!jobSuccess(job)) { 0535 return; 0536 } 0537 Q_EMIT signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded.")); 0538 } 0539 0540 void AtticaProvider::becomeFan(const EntryInternal &entry) 0541 { 0542 PostJob *job = m_provider.becomeFan(entry.uniqueId()); 0543 connect(job, &BaseJob::finished, this, &AtticaProvider::becomeFanFinished); 0544 job->start(); 0545 } 0546 0547 void AtticaProvider::becomeFanFinished(Attica::BaseJob *job) 0548 { 0549 if (!jobSuccess(job)) { 0550 return; 0551 } 0552 Q_EMIT signalInformation(i18n("You are now a fan.")); 0553 } 0554 0555 bool AtticaProvider::jobSuccess(Attica::BaseJob *job) const 0556 { 0557 if (job->metadata().error() == Attica::Metadata::NoError) { 0558 return true; 0559 } 0560 qCDebug(KNEWSTUFFCORE) << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message(); 0561 0562 if (job->metadata().error() == Attica::Metadata::NetworkError) { 0563 if (job->metadata().statusCode() == 503) { 0564 QDateTime retryAfter; 0565 static const QByteArray retryAfterKey{"Retry-After"}; 0566 for (const QNetworkReply::RawHeaderPair &headerPair : job->metadata().headers()) { 0567 if (headerPair.first == retryAfterKey) { 0568 // Retry-After is not a known header, so we need to do a bit of running around to make that work 0569 // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that 0570 // So, simple workaround, just pass it through a dummy request and get a formatted date out (the 0571 // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...) 0572 QNetworkRequest dummyRequest; 0573 dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second); 0574 retryAfter = dummyRequest.header(QNetworkRequest::LastModifiedHeader).toDateTime(); 0575 break; 0576 } 0577 } 0578 static const KFormat formatter; 0579 Q_EMIT signalErrorCode(KNSCore::TryAgainLaterError, 0580 i18n("The service is currently undergoing maintenance and is expected to be back in %1.", 0581 formatter.formatSpelloutDuration(retryAfter.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch())), 0582 {retryAfter}); 0583 } else { 0584 Q_EMIT signalErrorCode(KNSCore::NetworkError, 0585 i18n("Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()), 0586 job->metadata().statusCode()); 0587 } 0588 } 0589 if (job->metadata().error() == Attica::Metadata::OcsError) { 0590 if (job->metadata().statusCode() == 200) { 0591 Q_EMIT signalErrorCode(KNSCore::OcsError, i18n("Too many requests to server. Please try again in a few minutes."), job->metadata().statusCode()); 0592 } else if (job->metadata().statusCode() == 405) { 0593 Q_EMIT signalErrorCode(KNSCore::OcsError, 0594 i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()), 0595 job->metadata().statusCode()); 0596 } else { 0597 Q_EMIT signalErrorCode(KNSCore::OcsError, 0598 i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()), 0599 job->metadata().statusCode()); 0600 } 0601 } 0602 return false; 0603 } 0604 0605 EntryInternal AtticaProvider::entryFromAtticaContent(const Attica::Content &content) 0606 { 0607 EntryInternal entry; 0608 0609 entry.setProviderId(id()); 0610 entry.setUniqueId(content.id()); 0611 entry.setStatus(KNS3::Entry::Downloadable); 0612 entry.setVersion(content.version()); 0613 entry.setReleaseDate(content.updated().date()); 0614 entry.setCategory(content.attribute(QStringLiteral("typeid"))); 0615 0616 int index = mCachedEntries.indexOf(entry); 0617 if (index >= 0) { 0618 EntryInternal &cacheEntry = mCachedEntries[index]; 0619 // check if updateable 0620 if (((cacheEntry.status() == KNS3::Entry::Installed) || (cacheEntry.status() == KNS3::Entry::Updateable)) 0621 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) { 0622 cacheEntry.setStatus(KNS3::Entry::Updateable); 0623 cacheEntry.setUpdateVersion(entry.version()); 0624 cacheEntry.setUpdateReleaseDate(entry.releaseDate()); 0625 } 0626 entry = cacheEntry; 0627 } else { 0628 mCachedEntries.append(entry); 0629 } 0630 0631 entry.setName(content.name()); 0632 entry.setHomepage(content.detailpage()); 0633 entry.setRating(content.rating()); 0634 entry.setNumberOfComments(content.numberOfComments()); 0635 entry.setDownloadCount(content.downloads()); 0636 entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt()); 0637 entry.setDonationLink(content.attribute(QStringLiteral("donationpage"))); 0638 entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage"))); 0639 entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt()); 0640 entry.setHomepage(content.detailpage()); 0641 0642 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), EntryInternal::PreviewSmall1); 0643 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), EntryInternal::PreviewSmall2); 0644 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), EntryInternal::PreviewSmall3); 0645 0646 entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), EntryInternal::PreviewBig1); 0647 entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), EntryInternal::PreviewBig2); 0648 entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), EntryInternal::PreviewBig3); 0649 0650 entry.setLicense(content.license()); 0651 Author author; 0652 author.setId(content.author()); 0653 author.setName(content.author()); 0654 author.setHomepage(content.attribute(QStringLiteral("profilepage"))); 0655 entry.setAuthor(author); 0656 0657 entry.setSource(EntryInternal::Online); 0658 entry.setSummary(content.description()); 0659 entry.setShortSummary(content.summary()); 0660 entry.setChangelog(content.changelog()); 0661 entry.setTags(content.tags()); 0662 0663 entry.clearDownloadLinkInformation(); 0664 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions(); 0665 for (const Attica::DownloadDescription &desc : descs) { 0666 EntryInternal::DownloadLinkInformation info; 0667 info.name = desc.name(); 0668 info.priceAmount = desc.priceAmount(); 0669 info.distributionType = desc.distributionType(); 0670 info.descriptionLink = desc.link(); 0671 info.id = desc.id(); 0672 info.size = desc.size(); 0673 info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload; 0674 info.tags = desc.tags(); 0675 entry.appendDownloadLinkInformation(info); 0676 } 0677 0678 return entry; 0679 } 0680 0681 } // namespace 0682 0683 #include "moc_atticaprovider_p.cpp"