7#include "atticaprovider_p.h"
9#include "commentsmodel.h"
11#include "tagsfilterchecker.h"
14#include <KLocalizedString>
16#include <QDomDocument>
17#include <knewstuffcore_debug.h>
19#include <attica/accountbalance.h>
20#include <attica/config.h>
21#include <attica/content.h>
22#include <attica/downloaditem.h>
23#include <attica/listjob.h>
24#include <attica/person.h>
25#include <attica/provider.h>
26#include <attica/providermanager.h>
32AtticaProvider::AtticaProvider(
const QStringList &categories,
const QString &additionalAgentInformation)
37 for (
const QString &category : categories) {
41 connect(&m_providerManager, &ProviderManager::providerAdded,
this, [
this, additionalAgentInformation](
const Attica::Provider &provider) {
42 providerLoaded(provider);
43 m_provider.setAdditionalAgentInformation(additionalAgentInformation);
45 connect(&m_providerManager, &ProviderManager::authenticationCredentialsMissing,
this, &AtticaProvider::onAuthenticationCredentialsMissing);
53 for (
const QString &category : categories) {
56 providerLoaded(provider);
57 m_provider.setAdditionalAgentInformation(additionalAgentInformation);
60QString AtticaProvider::id()
const
65void AtticaProvider::onAuthenticationCredentialsMissing(
const Attica::Provider &)
67 qCDebug(KNEWSTUFFCORE) <<
"Authentication missing!";
71bool AtticaProvider::setProviderXML(
const QDomElement &xmldata)
79 qCDebug(KNEWSTUFFCORE) <<
"setting provider xml" << doc.toString();
82 m_providerManager.addProviderFromXml(doc.toString());
84 if (!m_providerManager.providers().isEmpty()) {
85 qCDebug(KNEWSTUFFCORE) <<
"base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString();
87 qCCritical(KNEWSTUFFCORE) <<
"Could not load provider.";
95 mCachedEntries = cachedEntries;
100 setName(provider.
name());
101 setIcon(provider.
icon());
102 qCDebug(KNEWSTUFFCORE) <<
"Added provider: " << provider.
name();
104 m_provider = provider;
109 connect(job, &BaseJob::finished,
this, &AtticaProvider::listOfCategoriesLoaded);
115 if (!jobSuccess(listJob)) {
119 qCDebug(KNEWSTUFFCORE) <<
"loading categories: " << mCategoryMap.keys();
122 const Category::List categoryList = job->itemList();
125 for (
const Category &category : categoryList) {
126 if (mCategoryMap.contains(
category.name())) {
127 qCDebug(KNEWSTUFFCORE) <<
"Adding category: " <<
category.name() <<
category.displayName();
129 if (mCategoryMap.contains(
category.name()) && !mCategoryMap.value(
category.name()).isValid()) {
130 mCategoryMap.replace(
category.name(), category);
132 mCategoryMap.insert(
category.name(), category);
135 CategoryMetadata categoryMetadata;
136 categoryMetadata.id =
category.id();
137 categoryMetadata.name =
category.name();
138 categoryMetadata.displayName =
category.displayName();
139 categoryMetadataList << categoryMetadata;
142 std::sort(categoryMetadataList.
begin(),
143 categoryMetadataList.
end(),
144 [](
const AtticaProvider::CategoryMetadata &i,
const AtticaProvider::CategoryMetadata &j) ->
bool {
145 const QString a(i.displayName.isEmpty() ? i.name : i.displayName);
146 const QString b(j.displayName.isEmpty() ? j.name : j.displayName);
148 return (QCollator().compare(a, b) < 0);
151 bool correct =
false;
152 for (
auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it != itEnd; ++it) {
153 if (!it.value().isValid()) {
154 qCWarning(KNEWSTUFFCORE) <<
"Could not find category" << it.key();
162 Q_EMIT providerInitialized(
this);
163 Q_EMIT categoriesMetadataLoded(categoryMetadataList);
165 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError,
i18n(
"All categories are missing"),
QVariant());
169bool AtticaProvider::isInitialized()
const
181 mCurrentRequest = request;
182 switch (request.filter) {
187 job->
setProperty(
"providedEntryId", request.searchTerm);
188 connect(job, &BaseJob::finished,
this, &AtticaProvider::detailsLoaded);
193 if (request.page == 0) {
194 Q_EMIT loadingFinished(request, installedEntries());
196 Q_EMIT loadingFinished(request, Entry::List());
204 Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode);
205 Attica::Category::List categoriesToSearch;
207 if (request.categories.
isEmpty()) {
209 categoriesToSearch = mCategoryMap.values();
211 categoriesToSearch.reserve(request.categories.
size());
212 for (
const QString &categoryName :
std::as_const(request.categories)) {
213 categoriesToSearch.append(mCategoryMap.values(categoryName));
217 ListJob<Content> *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize);
219 connect(job, &BaseJob::finished,
this, &AtticaProvider::categoryContentsLoaded);
225void AtticaProvider::checkForUpdates()
227 if (mCachedEntries.isEmpty()) {
228 Q_EMIT loadingFinished(mCurrentRequest, {});
231 for (
const Entry &e :
std::as_const(mCachedEntries)) {
233 connect(job, &BaseJob::finished,
this, &AtticaProvider::detailsLoaded);
234 m_updateJobs.insert(job);
236 qCDebug(KNEWSTUFFCORE) <<
"Checking for update: " << e.name();
243 connect(job, &BaseJob::finished,
this, &AtticaProvider::detailsLoaded);
247void AtticaProvider::detailsLoaded(
BaseJob *job)
249 if (jobSuccess(job)) {
251 Content content = contentJob->result();
252 Entry entry = entryFromAtticaContent(content);
254 Q_EMIT entryDetailsLoaded(entry);
255 qCDebug(KNEWSTUFFCORE) <<
"check update finished: " << entry.name();
258 if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) {
259 qCDebug(KNEWSTUFFCORE) <<
"check update finished.";
261 for (
const Entry &entry :
std::as_const(mCachedEntries)) {
262 if (entry.status() == KNSCore::Entry::Updateable) {
266 Q_EMIT loadingFinished(mCurrentRequest, updatable);
270void AtticaProvider::categoryContentsLoaded(
BaseJob *job)
272 if (!jobSuccess(job)) {
277 const Content::List contents = listJob->itemList();
280 TagsFilterChecker checker(tagFilter());
281 TagsFilterChecker downloadschecker(downloadTagFilter());
282 for (
const Content &content : contents) {
283 if (!content.isValid()) {
284 qCDebug(KNEWSTUFFCORE)
285 <<
"Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of"
286 <<
name() <<
"and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories;
289 if (checker.filterAccepts(content.tags())) {
290 bool filterAcceptsDownloads =
true;
291 if (content.downloads() > 0) {
292 filterAcceptsDownloads =
false;
295 if (downloadschecker.filterAccepts(dli.tags())) {
296 filterAcceptsDownloads =
true;
301 if (filterAcceptsDownloads) {
302 mCachedContent.insert(content.id(), content);
303 entries.append(entryFromAtticaContent(content));
305 qCDebug(KNEWSTUFFCORE) <<
"Filter has excluded" << content.name() <<
"on download filter" << downloadTagFilter();
308 qCDebug(KNEWSTUFFCORE) <<
"Filter has excluded" << content.name() <<
"on entry filter" << tagFilter();
312 qCDebug(KNEWSTUFFCORE) <<
"loaded: " << mCurrentRequest.hashForRequest() <<
" count: " << entries.size();
313 Q_EMIT loadingFinished(mCurrentRequest, entries);
317Attica::Provider::SortMode AtticaProvider::atticaSortMode(SortMode sortMode)
321 return Attica::Provider::Newest;
323 return Attica::Provider::Alphabetical;
325 return Attica::Provider::Downloads;
327 return Attica::Provider::Rating;
331void AtticaProvider::loadPayloadLink(
const KNSCore::Entry &entry,
int linkId)
336 if (desc.hasPrice()) {
339 connect(job, &BaseJob::finished,
this, &AtticaProvider::accountBalanceLoaded);
340 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
343 qCDebug(KNEWSTUFFCORE) <<
"get account balance";
346 connect(job, &BaseJob::finished,
this, &AtticaProvider::downloadItemLoaded);
347 mDownloadLinkJobs[job] = qMakePair(entry, linkId);
350 qCDebug(KNEWSTUFFCORE) <<
" link for " << entry.uniqueId();
354void AtticaProvider::loadComments(
const Entry &entry,
int commentsPerPage,
int page)
356 ListJob<Attica::Comment> *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral(
"0"), page, commentsPerPage);
357 connect(job, &BaseJob::finished,
this, &AtticaProvider::loadedComments);
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();
387 if (!jobSuccess(baseJob)) {
392 Attica::Comment::List
comments = job->itemList();
395 Q_EMIT commentsLoaded(receivedComments);
398void AtticaProvider::loadPerson(
const QString &username)
400 if (m_provider.hasPersonService()) {
403 connect(job, &BaseJob::finished,
this, &AtticaProvider::loadedPerson);
410 if (!jobSuccess(baseJob)) {
417 auto author = std::make_shared<KNSCore::Author>();
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);
428void AtticaProvider::loadBasics()
431 connect(configJob, &BaseJob::finished,
this, &AtticaProvider::loadedConfig);
437 if (jobSuccess(baseJob)) {
440 setVersion(config.version());
441 setSupportsSsl(config.ssl());
442 setContactEmail(config.contact());
443 QString protocol{QStringLiteral(
"http")};
445 protocol = QStringLiteral(
"https");
450 setWebsite(
QUrl(config.website()));
455 setHost(
QUrl(config.host()));
464 if (!jobSuccess(baseJob)) {
471 QPair<Entry, int> pair = mDownloadLinkJobs.take(job);
472 Entry entry(pair.first);
473 Content content = mCachedContent.value(entry.uniqueId());
475 qCDebug(KNEWSTUFFCORE) <<
"Your balance is greater than the price." << content.
downloadUrlDescription(pair.second).priceAmount()
476 <<
" balance: " << item.balance();
478 question.setEntry(entry);
479 question.setQuestion(
i18nc(
"the price of a download item, parameter 1 is the currency, 2 is the price",
480 "This item costs %1 %2.\nDo you want to buy it?",
483 if (question.ask() == Question::YesResponse) {
485 connect(job, &BaseJob::finished,
this, &AtticaProvider::downloadItemLoaded);
486 mDownloadLinkJobs[job] = qMakePair(entry, pair.second);
492 qCDebug(KNEWSTUFFCORE) <<
"You don't have enough money on your account!" << content.
downloadUrlDescription(0).priceAmount()
493 <<
" balance: " << item.balance();
494 Q_EMIT signalInformation(
i18n(
"Your account balance is too low:\nYour balance: %1\nPrice: %2",
500void AtticaProvider::downloadItemLoaded(
BaseJob *baseJob)
502 if (!jobSuccess(baseJob)) {
509 Entry entry = mDownloadLinkJobs.take(job).first;
510 entry.setPayload(
QString(item.url().toString()));
511 Q_EMIT payloadLinkLoaded(entry);
514Entry::List AtticaProvider::installedEntries()
const
517 for (
const Entry &entry :
std::as_const(mCachedEntries)) {
518 if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) {
519 entries.append(entry);
525void AtticaProvider::vote(
const Entry &entry, uint rating)
527 PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating);
528 connect(job, &BaseJob::finished,
this, &AtticaProvider::votingFinished);
534 if (!jobSuccess(job)) {
537 Q_EMIT signalInformation(
i18nc(
"voting for an item (good/bad)",
"Your vote was recorded."));
540void AtticaProvider::becomeFan(
const Entry &entry)
542 PostJob *job = m_provider.becomeFan(entry.uniqueId());
543 connect(job, &BaseJob::finished,
this, &AtticaProvider::becomeFanFinished);
549 if (!jobSuccess(job)) {
552 Q_EMIT signalInformation(
i18n(
"You are now a fan."));
557 if (job->metadata().error() == Attica::Metadata::NoError) {
560 qCDebug(KNEWSTUFFCORE) <<
"job error: " << job->metadata().error() <<
" status code: " << job->metadata().statusCode() << job->metadata().message();
562 if (job->metadata().error() == Attica::Metadata::NetworkError) {
563 if (job->metadata().statusCode() == 503) {
565 static const QByteArray retryAfterKey{
"Retry-After"};
567 if (headerPair.first == retryAfterKey) {
578 static const KFormat formatter;
579 Q_EMIT signalErrorCode(KNSCore::ErrorCode::TryAgainLaterError,
580 i18n(
"The service is currently undergoing maintenance and is expected to be back in %1.",
584 Q_EMIT signalErrorCode(KNSCore::ErrorCode::NetworkError,
585 i18n(
"Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()),
586 job->metadata().statusCode());
589 if (job->metadata().error() == Attica::Metadata::OcsError) {
590 if (job->metadata().statusCode() == 200) {
591 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
592 i18n(
"Too many requests to server. Please try again in a few minutes."),
593 job->metadata().statusCode());
594 }
else if (job->metadata().statusCode() == 405) {
595 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
596 i18n(
"The Open Collaboration Services instance %1 does not support the attempted function.",
name()),
597 job->metadata().statusCode());
599 Q_EMIT signalErrorCode(KNSCore::ErrorCode::OcsError,
600 i18n(
"Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()),
601 job->metadata().statusCode());
605 if (
auto searchRequestVar = job->
property(
"searchRequest"); searchRequestVar.isValid()) {
606 SearchRequest req = searchRequestVar.value<SearchRequest>();
607 Q_EMIT loadingFailed(req);
612Entry AtticaProvider::entryFromAtticaContent(
const Attica::Content &content)
616 entry.setProviderId(
id());
617 entry.setUniqueId(content.
id());
618 entry.setStatus(KNSCore::Entry::Downloadable);
619 entry.setVersion(content.version());
621 entry.setCategory(content.
attribute(QStringLiteral(
"typeid")));
623 int index = mCachedEntries.indexOf(entry);
625 Entry &cacheEntry = mCachedEntries[index];
627 if (((cacheEntry.status() == KNSCore::Entry::Installed) || (cacheEntry.status() == KNSCore::Entry::Updateable))
628 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
629 cacheEntry.setStatus(KNSCore::Entry::Updateable);
630 cacheEntry.setUpdateVersion(entry.version());
631 cacheEntry.setUpdateReleaseDate(entry.releaseDate());
635 mCachedEntries.append(entry);
638 entry.setName(content.
name());
640 entry.setRating(content.
rating());
642 entry.setDownloadCount(content.
downloads());
643 entry.setNumberFans(content.
attribute(QStringLiteral(
"fans")).toInt());
644 entry.setDonationLink(content.
attribute(QStringLiteral(
"donationpage")));
645 entry.setKnowledgebaseLink(content.
attribute(QStringLiteral(
"knowledgebasepage")));
646 entry.setNumberKnowledgebaseEntries(content.
attribute(QStringLiteral(
"knowledgebaseentries")).toInt());
649 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral(
"1")), Entry::PreviewSmall1);
650 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral(
"2")), Entry::PreviewSmall2);
651 entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral(
"3")), Entry::PreviewSmall3);
653 entry.setPreviewUrl(content.previewPicture(QStringLiteral(
"1")), Entry::PreviewBig1);
654 entry.setPreviewUrl(content.previewPicture(QStringLiteral(
"2")), Entry::PreviewBig2);
655 entry.setPreviewUrl(content.previewPicture(QStringLiteral(
"3")), Entry::PreviewBig3);
657 entry.setLicense(content.license());
659 author.setId(content.author());
660 author.setName(content.author());
661 author.setHomepage(content.
attribute(QStringLiteral(
"profilepage")));
662 entry.setAuthor(author);
664 entry.setSource(Entry::Online);
666 entry.setShortSummary(content.
summary());
667 entry.setChangelog(content.changelog());
668 entry.setTags(content.
tags());
670 entry.clearDownloadLinkInformation();
673 Entry::DownloadLinkInformation info;
674 info.name = desc.name();
675 info.priceAmount = desc.priceAmount();
676 info.distributionType = desc.distributionType();
677 info.descriptionLink = desc.link();
679 info.size = desc.size();
680 info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload;
682 entry.appendDownloadLinkInformation(info);
690#include "moc_atticaprovider_p.cpp"
QString description() const
QList< DownloadDescription > downloadUrlDescriptions() const
QString attribute(const QString &key) const
int numberOfComments() const
QDateTime updated() const
DownloadDescription downloadUrlDescription(int number) const
void setAdditionalAgentInformation(const QString &additionalInformation)
KNewStuff data entry container.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Category category(StandardShortcut id)
QString name(StandardShortcut id)
KEDUVOCDOCUMENT_EXPORT QStringList comments(const QString &language=QString())
qint64 currentMSecsSinceEpoch()
qint64 toMSecsSinceEpoch() const const
QString tagName() const const
QDomNode cloneNode(bool deep) const const
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
qsizetype size() const const
QVariant property(const char *name) const const
bool setProperty(const char *name, QVariant &&value)
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString host(ComponentFormattingOptions options) const const
QVariant fromValue(T &&value)
QDateTime toDateTime() const const
QString toString() const const
used to keep track of a search