File indexing completed on 2025-02-09 04:25:27
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 <QDomDocument> 0017 #include <knewstuffcore_debug.h> 0018 0019 #include <attica/accountbalance.h> 0020 #include <attica/config.h> 0021 #include <attica/content.h> 0022 #include <attica/downloaditem.h> 0023 #include <attica/listjob.h> 0024 #include <attica/person.h> 0025 #include <attica/provider.h> 0026 #include <attica/providermanager.h> 0027 0028 using namespace Attica; 0029 0030 namespace KNSCore 0031 { 0032 AtticaProvider::AtticaProvider(const QStringList &categories, const QString &additionalAgentInformation) 0033 : mEntryJob(nullptr) 0034 , mInitialized(false) 0035 { 0036 // init categories map with invalid categories 0037 for (const QString &category : categories) { 0038 mCategoryMap.insert(category, Attica::Category()); 0039 } 0040 0041 connect(&m_providerManager, &ProviderManager::providerAdded, this, [this, additionalAgentInformation](const Attica::Provider &provider) { 0042 providerLoaded(provider); 0043 m_provider.setAdditionalAgentInformation(additionalAgentInformation); 0044 }); 0045 connect(&m_providerManager, &ProviderManager::authenticationCredentialsMissing, this, &AtticaProvider::onAuthenticationCredentialsMissing); 0046 } 0047 0048 AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString &additionalAgentInformation) 0049 : mEntryJob(nullptr) 0050 , mInitialized(false) 0051 { 0052 // init categories map with invalid categories 0053 for (const QString &category : categories) { 0054 mCategoryMap.insert(category, Attica::Category()); 0055 } 0056 providerLoaded(provider); 0057 m_provider.setAdditionalAgentInformation(additionalAgentInformation); 0058 } 0059 0060 QString AtticaProvider::id() const 0061 { 0062 return m_providerId; 0063 } 0064 0065 void AtticaProvider::onAuthenticationCredentialsMissing(const Attica::Provider &) 0066 { 0067 qCDebug(KNEWSTUFFCORE) << "Authentication missing!"; 0068 // FIXME Show authentication dialog 0069 } 0070 0071 bool AtticaProvider::setProviderXML(const QDomElement &xmldata) 0072 { 0073 if (xmldata.tagName() != QLatin1String("provider")) { 0074 return false; 0075 } 0076 0077 // FIXME this is quite ugly, repackaging the xml into a string 0078 QDomDocument doc(QStringLiteral("temp")); 0079 qCDebug(KNEWSTUFFCORE) << "setting provider xml" << doc.toString(); 0080 0081 doc.appendChild(xmldata.cloneNode(true)); 0082 m_providerManager.addProviderFromXml(doc.toString()); 0083 0084 if (!m_providerManager.providers().isEmpty()) { 0085 qCDebug(KNEWSTUFFCORE) << "base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString(); 0086 } else { 0087 qCCritical(KNEWSTUFFCORE) << "Could not load provider."; 0088 return false; 0089 } 0090 return true; 0091 } 0092 0093 void AtticaProvider::setCachedEntries(const KNSCore::Entry::List &cachedEntries) 0094 { 0095 mCachedEntries = cachedEntries; 0096 } 0097 0098 void AtticaProvider::providerLoaded(const Attica::Provider &provider) 0099 { 0100 setName(provider.name()); 0101 setIcon(provider.icon()); 0102 qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name(); 0103 0104 m_provider = provider; 0105 m_provider.setAdditionalAgentInformation(name()); 0106 m_providerId = provider.baseUrl().host(); 0107 0108 Attica::ListJob<Attica::Category> *job = m_provider.requestCategories(); 0109 connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded); 0110 job->start(); 0111 } 0112 0113 void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob) 0114 { 0115 if (!jobSuccess(listJob)) { 0116 return; 0117 } 0118 0119 qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys(); 0120 0121 auto *job = static_cast<Attica::ListJob<Attica::Category> *>(listJob); 0122 const Category::List categoryList = job->itemList(); 0123 0124 QList<CategoryMetadata> categoryMetadataList; 0125 for (const Category &category : categoryList) { 0126 if (mCategoryMap.contains(category.name())) { 0127 qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName(); 0128 // If there is only the placeholder category, replace it 0129 if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) { 0130 mCategoryMap.replace(category.name(), category); 0131 } else { 0132 mCategoryMap.insert(category.name(), category); 0133 } 0134 0135 CategoryMetadata categoryMetadata; 0136 categoryMetadata.id = category.id(); 0137 categoryMetadata.name = category.name(); 0138 categoryMetadata.displayName = category.displayName(); 0139 categoryMetadataList << categoryMetadata; 0140 } 0141 } 0142 std::sort(categoryMetadataList.begin(), 0143 categoryMetadataList.end(), 0144 [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool { 0145 const QString a(i.displayName.isEmpty() ? i.name : i.displayName); 0146 const QString b(j.displayName.isEmpty() ? j.name : j.displayName); 0147 0148 return (QCollator().compare(a, b) < 0); 0149 }); 0150 0151 bool correct = false; 0152 for (auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) { 0153 if (!it.value().isValid()) { 0154 qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key(); 0155 } else { 0156 correct = true; 0157 } 0158 } 0159 0160 if (correct) { 0161 mInitialized = true; 0162 Q_EMIT providerInitialized(this); 0163 Q_EMIT categoriesMetadataLoded(categoryMetadataList); 0164 } else { 0165 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError, i18n("All categories are missing"), QVariant()); 0166 } 0167 } 0168 0169 bool AtticaProvider::isInitialized() const 0170 { 0171 return mInitialized; 0172 } 0173 0174 void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request) 0175 { 0176 if (mEntryJob) { 0177 mEntryJob->abort(); 0178 mEntryJob = nullptr; 0179 } 0180 0181 mCurrentRequest = request; 0182 switch (request.filter) { 0183 case None: 0184 break; 0185 case ExactEntryId: { 0186 ItemJob<Content> *job = m_provider.requestContent(request.searchTerm); 0187 job->setProperty("providedEntryId", request.searchTerm); 0188 connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); 0189 job->start(); 0190 return; 0191 } 0192 case Installed: 0193 if (request.page == 0) { 0194 Q_EMIT loadingFinished(request, installedEntries()); 0195 } else { 0196 Q_EMIT loadingFinished(request, Entry::List()); 0197 } 0198 return; 0199 case Updates: 0200 checkForUpdates(); 0201 return; 0202 } 0203 0204 Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode); 0205 Attica::Category::List categoriesToSearch; 0206 0207 if (request.categories.isEmpty()) { 0208 // search in all categories 0209 categoriesToSearch = mCategoryMap.values(); 0210 } else { 0211 categoriesToSearch.reserve(request.categories.size()); 0212 for (const QString &categoryName : std::as_const(request.categories)) { 0213 categoriesToSearch.append(mCategoryMap.values(categoryName)); 0214 } 0215 } 0216 0217 ListJob<Content> *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize); 0218 job->setProperty("searchRequest", QVariant::fromValue(request)); 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 Entry &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::Entry &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 Entry entry = entryFromAtticaContent(content); 0253 entry.setEntryRequestedId(job->property("providedEntryId").toString()); // The ResultsStream should still known that this entry was for its query 0254 Q_EMIT entryDetailsLoaded(entry); 0255 qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name(); 0256 } 0257 0258 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) { 0259 qCDebug(KNEWSTUFFCORE) << "check update finished."; 0260 QList<Entry> updatable; 0261 for (const Entry &entry : std::as_const(mCachedEntries)) { 0262 if (entry.status() == KNSCore::Entry::Updateable) { 0263 updatable.append(entry); 0264 } 0265 } 0266 Q_EMIT loadingFinished(mCurrentRequest, updatable); 0267 } 0268 } 0269 0270 void AtticaProvider::categoryContentsLoaded(BaseJob *job) 0271 { 0272 if (!jobSuccess(job)) { 0273 return; 0274 } 0275 0276 auto *listJob = static_cast<ListJob<Content> *>(job); 0277 const Content::List contents = listJob->itemList(); 0278 0279 Entry::List entries; 0280 TagsFilterChecker checker(tagFilter()); 0281 TagsFilterChecker downloadschecker(downloadTagFilter()); 0282 for (const Content &content : contents) { 0283 if (!content.isValid()) { 0284 qCDebug(KNEWSTUFFCORE) 0285 << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of" 0286 << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories; 0287 continue; 0288 } 0289 if (checker.filterAccepts(content.tags())) { 0290 bool filterAcceptsDownloads = true; 0291 if (content.downloads() > 0) { 0292 filterAcceptsDownloads = false; 0293 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions(); 0294 for (const Attica::DownloadDescription &dli : descs) { 0295 if (downloadschecker.filterAccepts(dli.tags())) { 0296 filterAcceptsDownloads = true; 0297 break; 0298 } 0299 } 0300 } 0301 if (filterAcceptsDownloads) { 0302 mCachedContent.insert(content.id(), content); 0303 entries.append(entryFromAtticaContent(content)); 0304 } else { 0305 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter(); 0306 } 0307 } else { 0308 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter(); 0309 } 0310 } 0311 0312 qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size(); 0313 Q_EMIT loadingFinished(mCurrentRequest, entries); 0314 mEntryJob = nullptr; 0315 } 0316 0317 Attica::Provider::SortMode AtticaProvider::atticaSortMode(SortMode sortMode) 0318 { 0319 switch (sortMode) { 0320 case Newest: 0321 return Attica::Provider::Newest; 0322 case Alphabetical: 0323 return Attica::Provider::Alphabetical; 0324 case Downloads: 0325 return Attica::Provider::Downloads; 0326 default: 0327 return Attica::Provider::Rating; 0328 } 0329 } 0330 0331 void AtticaProvider::loadPayloadLink(const KNSCore::Entry &entry, int linkId) 0332 { 0333 Attica::Content content = mCachedContent.value(entry.uniqueId()); 0334 const DownloadDescription desc = content.downloadUrlDescription(linkId); 0335 0336 if (desc.hasPrice()) { 0337 // Ask for balance, then show information... 0338 ItemJob<AccountBalance> *job = m_provider.requestAccountBalance(); 0339 connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded); 0340 mDownloadLinkJobs[job] = qMakePair(entry, linkId); 0341 job->start(); 0342 0343 qCDebug(KNEWSTUFFCORE) << "get account balance"; 0344 } else { 0345 ItemJob<DownloadItem> *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId)); 0346 connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded); 0347 mDownloadLinkJobs[job] = qMakePair(entry, linkId); 0348 job->start(); 0349 0350 qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId(); 0351 } 0352 } 0353 0354 void AtticaProvider::loadComments(const Entry &entry, int commentsPerPage, int page) 0355 { 0356 ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral("0"), page, commentsPerPage); 0357 connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments); 0358 job->start(); 0359 } 0360 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<Entry, int> pair = mDownloadLinkJobs.take(job); 0472 Entry 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 Entry entry = mDownloadLinkJobs.take(job).first; 0510 entry.setPayload(QString(item.url().toString())); 0511 Q_EMIT payloadLinkLoaded(entry); 0512 } 0513 0514 Entry::List AtticaProvider::installedEntries() const 0515 { 0516 Entry::List entries; 0517 for (const Entry &entry : std::as_const(mCachedEntries)) { 0518 if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) { 0519 entries.append(entry); 0520 } 0521 } 0522 return entries; 0523 } 0524 0525 void AtticaProvider::vote(const Entry &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 Entry &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) 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::ErrorCode::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::ErrorCode::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::ErrorCode::OcsError, 0592 i18n("Too many requests to server. Please try again in a few minutes."), 0593 job->metadata().statusCode()); 0594 } else if (job->metadata().statusCode() == 405) { 0595 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError, 0596 i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()), 0597 job->metadata().statusCode()); 0598 } else { 0599 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError, 0600 i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()), 0601 job->metadata().statusCode()); 0602 } 0603 } 0604 0605 if (auto searchRequestVar = job->property("searchRequest"); searchRequestVar.isValid()) { 0606 SearchRequest req = searchRequestVar.value<SearchRequest>(); 0607 Q_EMIT loadingFailed(req); 0608 } 0609 return false; 0610 } 0611 0612 Entry AtticaProvider::entryFromAtticaContent(const Attica::Content &content) 0613 { 0614 Entry entry; 0615 0616 entry.setProviderId(id()); 0617 entry.setUniqueId(content.id()); 0618 entry.setStatus(KNSCore::Entry::Downloadable); 0619 entry.setVersion(content.version()); 0620 entry.setReleaseDate(content.updated().date()); 0621 entry.setCategory(content.attribute(QStringLiteral("typeid"))); 0622 0623 int index = mCachedEntries.indexOf(entry); 0624 if (index >= 0) { 0625 Entry &cacheEntry = mCachedEntries[index]; 0626 // check if updateable 0627 if (((cacheEntry.status() == KNSCore::Entry::Installed) || (cacheEntry.status() == KNSCore::Entry::Updateable)) 0628 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) { 0629 cacheEntry.setStatus(KNSCore::Entry::Updateable); 0630 cacheEntry.setUpdateVersion(entry.version()); 0631 cacheEntry.setUpdateReleaseDate(entry.releaseDate()); 0632 } 0633 entry = cacheEntry; 0634 } else { 0635 mCachedEntries.append(entry); 0636 } 0637 0638 entry.setName(content.name()); 0639 entry.setHomepage(content.detailpage()); 0640 entry.setRating(content.rating()); 0641 entry.setNumberOfComments(content.numberOfComments()); 0642 entry.setDownloadCount(content.downloads()); 0643 entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt()); 0644 entry.setDonationLink(content.attribute(QStringLiteral("donationpage"))); 0645 entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage"))); 0646 entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt()); 0647 entry.setHomepage(content.detailpage()); 0648 0649 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), Entry::PreviewSmall1); 0650 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), Entry::PreviewSmall2); 0651 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), Entry::PreviewSmall3); 0652 0653 entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), Entry::PreviewBig1); 0654 entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), Entry::PreviewBig2); 0655 entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), Entry::PreviewBig3); 0656 0657 entry.setLicense(content.license()); 0658 Author author; 0659 author.setId(content.author()); 0660 author.setName(content.author()); 0661 author.setHomepage(content.attribute(QStringLiteral("profilepage"))); 0662 entry.setAuthor(author); 0663 0664 entry.setSource(Entry::Online); 0665 entry.setSummary(content.description()); 0666 entry.setShortSummary(content.summary()); 0667 entry.setChangelog(content.changelog()); 0668 entry.setTags(content.tags()); 0669 0670 entry.clearDownloadLinkInformation(); 0671 const QList<Attica::DownloadDescription> descs = content.downloadUrlDescriptions(); 0672 for (const Attica::DownloadDescription &desc : descs) { 0673 Entry::DownloadLinkInformation info; 0674 info.name = desc.name(); 0675 info.priceAmount = desc.priceAmount(); 0676 info.distributionType = desc.distributionType(); 0677 info.descriptionLink = desc.link(); 0678 info.id = desc.id(); 0679 info.size = desc.size(); 0680 info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload; 0681 info.tags = desc.tags(); 0682 entry.appendDownloadLinkInformation(info); 0683 } 0684 0685 return entry; 0686 } 0687 0688 } // namespace 0689 0690 #include "moc_atticaprovider_p.cpp"