diff --git a/src/attica/atticaprovider.cpp b/src/attica/atticaprovider.cpp index 79b6b62c..6b803abb 100644 --- a/src/attica/atticaprovider.cpp +++ b/src/attica/atticaprovider.cpp @@ -1,611 +1,616 @@ /* Copyright (c) 2009-2010 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "atticaprovider_p.h" #include "commentsmodel.h" #include "question.h" #include "tagsfilterchecker.h" #include #include #include #include #include #include #include #include #include #include using namespace Attica; namespace KNSCore { -AtticaProvider::AtticaProvider(const QStringList &categories) +AtticaProvider::AtticaProvider(const QStringList &categories, const QString& additionalAgentInformation) : mEntryJob(nullptr) , mInitialized(false) { // init categories map with invalid categories for (const QString &category : categories) { mCategoryMap.insert(category, Attica::Category()); } - connect(&m_providerManager, &ProviderManager::providerAdded, this, &AtticaProvider::providerLoaded); + connect(&m_providerManager, &ProviderManager::providerAdded, this, [=](const Attica::Provider &provider){ + providerLoaded(provider); + m_provider.setAdditionalAgentInformation(additionalAgentInformation); + }); connect(&m_providerManager, SIGNAL(authenticationCredentialsMissing(Provider)), SLOT(authenticationCredentialsMissing(Provider))); connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments); connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson); } -AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories) +AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString& additionalAgentInformation) : mEntryJob(nullptr) , mInitialized(false) { // init categories map with invalid categories for (const QString &category : categories) { mCategoryMap.insert(category, Attica::Category()); } providerLoaded(provider); + m_provider.setAdditionalAgentInformation(additionalAgentInformation); } QString AtticaProvider::id() const { return m_providerId; } void AtticaProvider::authenticationCredentialsMissing(const KNSCore::Provider &) { qCDebug(KNEWSTUFFCORE) << "Authentication missing!"; // FIXME Show autentication dialog } bool AtticaProvider::setProviderXML(const QDomElement &xmldata) { if (xmldata.tagName() != QLatin1String("provider")) { return false; } // FIXME this is quite ugly, repackaging the xml into a string QDomDocument doc(QStringLiteral("temp")); qCDebug(KNEWSTUFFCORE) << "setting provider xml" << doc.toString(); doc.appendChild(xmldata.cloneNode(true)); m_providerManager.addProviderFromXml(doc.toString()); if (!m_providerManager.providers().isEmpty()) { qCDebug(KNEWSTUFFCORE) << "base url of attica provider:" << m_providerManager.providers().constLast().baseUrl().toString(); } else { qCCritical(KNEWSTUFFCORE) << "Could not load provider."; return false; } return true; } void AtticaProvider::setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries) { mCachedEntries = cachedEntries; } void AtticaProvider::providerLoaded(const Attica::Provider &provider) { mName = provider.name(); qCDebug(KNEWSTUFFCORE) << "Added provider: " << provider.name(); m_provider = provider; + m_provider.setAdditionalAgentInformation(mName); m_providerId = provider.baseUrl().toString(); Attica::ListJob *job = m_provider.requestCategories(); connect(job, &BaseJob::finished, this, &AtticaProvider::listOfCategoriesLoaded); job->start(); } void AtticaProvider::listOfCategoriesLoaded(Attica::BaseJob *listJob) { if (!jobSuccess(listJob)) { return; } qCDebug(KNEWSTUFFCORE) << "loading categories: " << mCategoryMap.keys(); Attica::ListJob *job = static_cast*>(listJob); const Category::List categoryList = job->itemList(); QList categoryMetadataList; for (const Category &category : categoryList) { if (mCategoryMap.contains(category.name())) { qCDebug(KNEWSTUFFCORE) << "Adding category: " << category.name() << category.displayName(); //If there is only the placeholder category, replace it if (mCategoryMap.contains(category.name()) && !mCategoryMap.value(category.name()).isValid()) { mCategoryMap.insert(category.name(), category); } else { mCategoryMap.insertMulti(category.name(), category); } CategoryMetadata categoryMetadata; categoryMetadata.id = category.id(); categoryMetadata.name = category.name(); categoryMetadata.displayName = category.displayName(); categoryMetadataList << categoryMetadata; } } std::sort(categoryMetadataList.begin(), categoryMetadataList.end(), [](const AtticaProvider::CategoryMetadata &i, const AtticaProvider::CategoryMetadata &j) -> bool { const QString a(i.displayName.isEmpty() ? i.name : i.displayName); const QString b(j.displayName.isEmpty() ? j.name : j.displayName); return (QCollator().compare(a, b) < 0); }); bool correct = false; for(auto it = mCategoryMap.cbegin(), itEnd = mCategoryMap.cend(); it!=itEnd; ++it) { if (!it.value().isValid()) { qCWarning(KNEWSTUFFCORE) << "Could not find category" << it.key(); } else { correct = true; } } if (correct) { mInitialized = true; emit providerInitialized(this); emit categoriesMetadataLoded(categoryMetadataList); } else { emit signalErrorCode(KNSCore::ConfigFileError, i18n("All categories are missing"), QVariant()); } } bool AtticaProvider::isInitialized() const { return mInitialized; } void AtticaProvider::loadEntries(const KNSCore::Provider::SearchRequest &request) { if (mEntryJob) { mEntryJob->abort(); mEntryJob = nullptr; } mCurrentRequest = request; switch (request.filter) { case None: break; case ExactEntryId: { ItemJob *job = m_provider.requestContent(request.searchTerm); connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); job->start(); return; } case Installed: if (request.page == 0) { emit loadingFinished(request, installedEntries()); } else { emit loadingFinished(request, EntryInternal::List()); } return; case Updates: checkForUpdates(); return; } Attica::Provider::SortMode sorting = atticaSortMode(request.sortMode); Attica::Category::List categoriesToSearch; if (request.categories.isEmpty()) { // search in all categories categoriesToSearch = mCategoryMap.values(); } else { categoriesToSearch.reserve(request.categories.size()); for (const QString &categoryName : qAsConst(request.categories)) { categoriesToSearch.append(mCategoryMap.values(categoryName)); } } ListJob *job = m_provider.searchContents(categoriesToSearch, request.searchTerm, sorting, request.page, request.pageSize); connect(job, &BaseJob::finished, this, &AtticaProvider::categoryContentsLoaded); mEntryJob = job; job->start(); } void AtticaProvider::checkForUpdates() { for (const EntryInternal &e : qAsConst(mCachedEntries)) { ItemJob *job = m_provider.requestContent(e.uniqueId()); connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); m_updateJobs.insert(job); job->start(); qCDebug(KNEWSTUFFCORE) << "Checking for update: " << e.name(); } } void AtticaProvider::loadEntryDetails(const KNSCore::EntryInternal &entry) { ItemJob *job = m_provider.requestContent(entry.uniqueId()); connect(job, &BaseJob::finished, this, &AtticaProvider::detailsLoaded); job->start(); } void AtticaProvider::detailsLoaded(BaseJob *job) { if (jobSuccess(job)) { ItemJob *contentJob = static_cast*>(job); Content content = contentJob->result(); EntryInternal entry = entryFromAtticaContent(content); emit entryDetailsLoaded(entry); qCDebug(KNEWSTUFFCORE) << "check update finished: " << entry.name(); } if (m_updateJobs.remove(job) && m_updateJobs.isEmpty()) { qCDebug(KNEWSTUFFCORE) << "check update finished."; QList updatable; for (const EntryInternal &entry : qAsConst(mCachedEntries)) { if (entry.status() == KNS3::Entry::Updateable) { updatable.append(entry); } } emit loadingFinished(mCurrentRequest, updatable); } } void AtticaProvider::categoryContentsLoaded(BaseJob *job) { if (!jobSuccess(job)) { return; } ListJob *listJob = static_cast*>(job); Content::List contents = listJob->itemList(); EntryInternal::List entries; TagsFilterChecker checker(tagFilter()); TagsFilterChecker downloadschecker(downloadTagFilter()); for (const Content &content : contents) { if (!content.isValid()) { qCDebug(KNEWSTUFFCORE) << "Filtered out an invalid entry. This suggests something is not right on the originating server. Please contact the administrators of" << name() << "and inform them there is an issue with content in the category or categories" << mCurrentRequest.categories; continue; } if (checker.filterAccepts(content.tags())) { bool filterAcceptsDownloads = true; if (content.downloads() > 0) { filterAcceptsDownloads = false; for (const Attica::DownloadDescription &dli : content.downloadUrlDescriptions()) { if (downloadschecker.filterAccepts(dli.tags())) { filterAcceptsDownloads = true; break; } } } if (filterAcceptsDownloads) { mCachedContent.insert(content.id(), content); entries.append(entryFromAtticaContent(content)); } else { qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on download filter" << downloadTagFilter(); } } else { qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << content.name() << "on entry filter" << tagFilter(); } } qCDebug(KNEWSTUFFCORE) << "loaded: " << mCurrentRequest.hashForRequest() << " count: " << entries.size(); emit loadingFinished(mCurrentRequest, entries); mEntryJob = nullptr; } Attica::Provider::SortMode AtticaProvider::atticaSortMode(const SortMode &sortMode) { switch(sortMode) { case Newest: return Attica::Provider::Newest; case Alphabetical: return Attica::Provider::Alphabetical; case Downloads: return Attica::Provider::Downloads; default: return Attica::Provider::Rating; } } void AtticaProvider::loadPayloadLink(const KNSCore::EntryInternal &entry, int linkId) { Attica::Content content = mCachedContent.value(entry.uniqueId()); const DownloadDescription desc = content.downloadUrlDescription(linkId); if (desc.hasPrice()) { // Ask for balance, then show information... ItemJob *job = m_provider.requestAccountBalance(); connect(job, &BaseJob::finished, this, &AtticaProvider::accountBalanceLoaded); mDownloadLinkJobs[job] = qMakePair(entry, linkId); job->start(); qCDebug(KNEWSTUFFCORE) << "get account balance"; } else { ItemJob *job = m_provider.downloadLink(entry.uniqueId(), QString::number(linkId)); connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded); mDownloadLinkJobs[job] = qMakePair(entry, linkId); job->start(); qCDebug(KNEWSTUFFCORE) << " link for " << entry.uniqueId(); } } void AtticaProvider::loadComments(const EntryInternal &entry, int commentsPerPage, int page) { ListJob *job = m_provider.requestComments(Attica::Comment::ContentComment, entry.uniqueId(), QLatin1String("0"), page, commentsPerPage); connect(job, &BaseJob::finished, this, &AtticaProvider::loadedComments); job->start(); } /// TODO KF6 QList is discouraged, and we'll probably want to switch this (and the rest of the KNS library) to QVector instead QList> getCommentsList(const Attica::Comment::List &comments, std::shared_ptr parent) { QList> knsComments; for (const Attica::Comment &comment : comments) { qCDebug(KNEWSTUFFCORE) << "Appending comment with id" << comment.id() << ", which has" << comment.childCount() << "children"; auto knsComment = std::make_shared(); knsComment->id = comment.id(); knsComment->subject = comment.subject(); knsComment->text = comment.text(); knsComment->childCount = comment.childCount(); knsComment->username = comment.user(); knsComment->date = comment.date(); knsComment->score = comment.score(); knsComment->parent = parent; knsComments << knsComment; if (comment.childCount() > 0) { qCDebug(KNEWSTUFFCORE) << "Getting more comments, as this one has children, and we currently have this number of comments:" << knsComments.count(); knsComments << getCommentsList(comment.children(), knsComment); qCDebug(KNEWSTUFFCORE) << "After getting the children, we now have the following number of comments:" << knsComments.count(); } } return knsComments; } void AtticaProvider::loadedComments(Attica::BaseJob *baseJob) { if (!jobSuccess(baseJob)) { return; } ListJob *job = static_cast*>(baseJob); Attica::Comment::List comments = job->itemList(); QList> receivedComments = getCommentsList(comments, nullptr); emit commentsLoaded(receivedComments); } void AtticaProvider::loadPerson(const QString &username) { if (m_provider.hasPersonService()) { ItemJob *job = m_provider.requestPerson(username); job->setProperty("username", username); connect(job, &BaseJob::finished, this, &AtticaProvider::loadedPerson); job->start(); } } void AtticaProvider::loadedPerson(Attica::BaseJob *baseJob) { if (!jobSuccess(baseJob)) { return; } ItemJob *job = static_cast*>(baseJob); Attica::Person person = job->result(); auto author = std::make_shared(); author->setId(job->property("username").toString()); // This is a touch hack-like, but it ensures we actually have the data in case it is not returned by the server author->setName(QString::fromLatin1("%1 %2").arg(person.firstName()).arg(person.lastName()).trimmed()); author->setHomepage(person.homepage()); author->setProfilepage(person.extendedAttribute(QStringLiteral("profilepage"))); author->setAvatarUrl(person.avatarUrl()); author->setDescription(person.extendedAttribute(QStringLiteral("description"))); emit personLoaded(author); } void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob) { if (!jobSuccess(baseJob)) { return; } ItemJob *job = static_cast*>(baseJob); AccountBalance item = job->result(); QPair pair = mDownloadLinkJobs.take(job); EntryInternal entry(pair.first); Content content = mCachedContent.value(entry.uniqueId()); if (content.downloadUrlDescription(pair.second).priceAmount() < item.balance()) { qCDebug(KNEWSTUFFCORE) << "Your balance is greater than the price." << content.downloadUrlDescription(pair.second).priceAmount() << " balance: " << item.balance(); Question question; question.setQuestion(i18nc("the price of a download item, parameter 1 is the currency, 2 is the price", "This item costs %1 %2.\nDo you want to buy it?", item.currency(), content.downloadUrlDescription(pair.second).priceAmount() )); if(question.ask() == Question::YesResponse) { ItemJob *job = m_provider.downloadLink(entry.uniqueId(), QString::number(pair.second)); connect(job, &BaseJob::finished, this, &AtticaProvider::downloadItemLoaded); mDownloadLinkJobs[job] = qMakePair(entry, pair.second); job->start(); } else { return; } } else { qCDebug(KNEWSTUFFCORE) << "You don't have enough money on your account!" << content.downloadUrlDescription(0).priceAmount() << " balance: " << item.balance(); emit signalInformation(i18n("Your account balance is too low:\nYour balance: %1\nPrice: %2", item.balance(), content.downloadUrlDescription(0).priceAmount())); } } void AtticaProvider::downloadItemLoaded(BaseJob *baseJob) { if (!jobSuccess(baseJob)) { return; } ItemJob *job = static_cast*>(baseJob); DownloadItem item = job->result(); EntryInternal entry = mDownloadLinkJobs.take(job).first; entry.setPayload(QString(item.url().toString())); emit payloadLinkLoaded(entry); } EntryInternal::List AtticaProvider::installedEntries() const { EntryInternal::List entries; for (const EntryInternal &entry : qAsConst(mCachedEntries)) { if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) { entries.append(entry); } } return entries; } void AtticaProvider::vote(const EntryInternal &entry, uint rating) { PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating); connect(job, &BaseJob::finished, this, &AtticaProvider::votingFinished); job->start(); } void AtticaProvider::votingFinished(Attica::BaseJob *job) { if (!jobSuccess(job)) { return; } emit signalInformation(i18nc("voting for an item (good/bad)", "Your vote was recorded.")); } void AtticaProvider::becomeFan(const EntryInternal &entry) { PostJob *job = m_provider.becomeFan(entry.uniqueId()); connect(job, &BaseJob::finished, this, &AtticaProvider::becomeFanFinished); job->start(); } void AtticaProvider::becomeFanFinished(Attica::BaseJob *job) { if (!jobSuccess(job)) { return; } emit signalInformation(i18n("You are now a fan.")); } bool AtticaProvider::jobSuccess(Attica::BaseJob *job) const { if (job->metadata().error() == Attica::Metadata::NoError) { return true; } qCDebug(KNEWSTUFFCORE) << "job error: " << job->metadata().error() << " status code: " << job->metadata().statusCode() << job->metadata().message(); if (job->metadata().error() == Attica::Metadata::NetworkError) { emit signalErrorCode(KNSCore::NetworkError, i18n("Network error %1: %2", job->metadata().statusCode(), job->metadata().statusString()), job->metadata().statusCode()); } if (job->metadata().error() == Attica::Metadata::OcsError) { if (job->metadata().statusCode() == 200) { emit signalErrorCode(KNSCore::OcsError, i18n("Too many requests to server. Please try again in a few minutes."), job->metadata().statusCode()); } else if (job->metadata().statusCode() == 405) { emit signalErrorCode(KNSCore::OcsError, i18n("The Open Collaboration Services instance %1 does not support the attempted function.", name()), job->metadata().statusCode()); } else { emit signalErrorCode(KNSCore::OcsError, i18n("Unknown Open Collaboration Service API error. (%1)", job->metadata().statusCode()), job->metadata().statusCode()); } } return false; } EntryInternal AtticaProvider::entryFromAtticaContent(const Attica::Content &content) { EntryInternal entry; entry.setProviderId(id()); entry.setUniqueId(content.id()); entry.setStatus(KNS3::Entry::Downloadable); entry.setVersion(content.version()); entry.setReleaseDate(content.updated().date()); entry.setCategory(content.attribute(QStringLiteral("typeid"))); int index = mCachedEntries.indexOf(entry); if (index >= 0) { EntryInternal &cacheEntry = mCachedEntries[index]; // check if updateable if (((cacheEntry.status() == KNS3::Entry::Installed) || (cacheEntry.status() == KNS3::Entry::Updateable)) && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) { cacheEntry.setStatus(KNS3::Entry::Updateable); cacheEntry.setUpdateVersion(entry.version()); cacheEntry.setUpdateReleaseDate(entry.releaseDate()); } entry = cacheEntry; } else { mCachedEntries.append(entry); } entry.setName(content.name()); entry.setHomepage(content.detailpage()); entry.setRating(content.rating()); entry.setNumberOfComments(content.numberOfComments()); entry.setDownloadCount(content.downloads()); entry.setNumberFans(content.attribute(QStringLiteral("fans")).toInt()); entry.setDonationLink(content.attribute(QStringLiteral("donationpage"))); entry.setKnowledgebaseLink(content.attribute(QStringLiteral("knowledgebasepage"))); entry.setNumberKnowledgebaseEntries(content.attribute(QStringLiteral("knowledgebaseentries")).toInt()); entry.setHomepage(content.detailpage()); entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("1")), EntryInternal::PreviewSmall1); entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("2")), EntryInternal::PreviewSmall2); entry.setPreviewUrl(content.smallPreviewPicture(QStringLiteral("3")), EntryInternal::PreviewSmall3); entry.setPreviewUrl(content.previewPicture(QStringLiteral("1")), EntryInternal::PreviewBig1); entry.setPreviewUrl(content.previewPicture(QStringLiteral("2")), EntryInternal::PreviewBig2); entry.setPreviewUrl(content.previewPicture(QStringLiteral("3")), EntryInternal::PreviewBig3); entry.setLicense(content.license()); Author author; author.setId(content.author()); author.setName(content.author()); author.setHomepage(content.attribute(QStringLiteral("profilepage"))); entry.setAuthor(author); entry.setSource(EntryInternal::Online); entry.setSummary(content.description()); entry.setShortSummary(content.summary()); entry.setChangelog(content.changelog()); entry.setTags(content.tags()); entry.clearDownloadLinkInformation(); const QList descs = content.downloadUrlDescriptions(); for (const Attica::DownloadDescription &desc : descs) { EntryInternal::DownloadLinkInformation info; info.name = desc.name(); info.priceAmount = desc.priceAmount(); info.distributionType = desc.distributionType(); info.descriptionLink = desc.link(); info.id = desc.id(); info.size = desc.size(); info.isDownloadtypeLink = desc.type() == Attica::DownloadDescription::LinkDownload; info.tags = desc.tags(); entry.appendDownloadLinkInformation(info); } return entry; } } // namespace diff --git a/src/attica/atticaprovider_p.h b/src/attica/atticaprovider_p.h index 90eb5c88..c4421d95 100644 --- a/src/attica/atticaprovider_p.h +++ b/src/attica/atticaprovider_p.h @@ -1,141 +1,141 @@ /* Copyright (c) 2009-2010 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KNEWSTUFF3_ATTICAPROVIDER_P_H #define KNEWSTUFF3_ATTICAPROVIDER_P_H #include #include #include #include #include #include "provider.h" namespace Attica { class BaseJob; } namespace KNSCore { /** * @short KNewStuff Attica Provider class. * * This class provides accessors for the provider object. * It should not be used directly by the application. * This class is the base class and will be instantiated for * websites that implement the Open Collaboration Services. * * @author Frederik Gladhorn * * @internal */ class AtticaProvider: public Provider { Q_OBJECT public: - explicit AtticaProvider(const QStringList &categories); - AtticaProvider(const Attica::Provider &provider, const QStringList &categories); + explicit AtticaProvider(const QStringList &categories, const QString& additionalAgentInformation); + AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString& additionalAgentInformation); QString id() const override; /** * set the provider data xml, to initialize the provider */ bool setProviderXML(const QDomElement &xmldata) override; bool isInitialized() const override; void setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries) override; void loadEntries(const KNSCore::Provider::SearchRequest &request) override; void loadEntryDetails(const KNSCore::EntryInternal &entry) override; void loadPayloadLink(const EntryInternal &entry, int linkId) override; /** * The slot which causes loading of comments for the Attica provider * @see Provider::loadComments(const EntryInternal &entry, int commentsPerPage, int page) */ Q_SLOT void loadComments(const EntryInternal &entry, int commentsPerPage, int page); /** * The slot which causes loading of a person's details * @see Provider::loadPerson(const QString &username) */ Q_SLOT void loadPerson(const QString &username); bool userCanVote() override { return true; } void vote(const EntryInternal &entry, uint rating) override; bool userCanBecomeFan() override { return true; } void becomeFan(const EntryInternal &entry) override; private Q_SLOTS: void providerLoaded(const Attica::Provider &provider); void listOfCategoriesLoaded(Attica::BaseJob *); void categoryContentsLoaded(Attica::BaseJob *job); void downloadItemLoaded(Attica::BaseJob *job); void accountBalanceLoaded(Attica::BaseJob *job); void authenticationCredentialsMissing(const Provider &); void votingFinished(Attica::BaseJob *); void becomeFanFinished(Attica::BaseJob *job); void detailsLoaded(Attica::BaseJob *job); void loadedComments(Attica::BaseJob *job); void loadedPerson(Attica::BaseJob *job); private: void checkForUpdates(); EntryInternal::List installedEntries() const; bool jobSuccess(Attica::BaseJob *job) const; Attica::Provider::SortMode atticaSortMode(const SortMode &sortMode); EntryInternal entryFromAtticaContent(const Attica::Content &); // the attica categories we are interested in (e.g. Wallpaper, Application, Vocabulary File...) QHash mCategoryMap; Attica::ProviderManager m_providerManager; Attica::Provider m_provider; KNSCore::EntryInternal::List mCachedEntries; QHash mCachedContent; // Associate job and entry, this is needed when fetching // download links or the account balance in order to continue // when the result is there. QHash > mDownloadLinkJobs; // keep track of the current request QPointer mEntryJob; Provider::SearchRequest mCurrentRequest; QSet m_updateJobs; bool mInitialized; QString m_providerId; Q_DISABLE_COPY(AtticaProvider) }; } #endif diff --git a/src/core/engine.cpp b/src/core/engine.cpp index a1aaca26..b7d286a2 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -1,815 +1,815 @@ /* knewstuff3/engine.cpp Copyright (c) 2007 Josef Spillner Copyright (C) 2007-2010 Frederik Gladhorn Copyright (c) 2009 Jeremy Whiting Copyright (c) 2010 Matthias Fuchs This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "engine.h" #include "../entry.h" #include "commentsmodel.h" #include "installation.h" #include "xmlloader.h" #include "imageloader_p.h" #include #include #include #include #include #include #include #include #include #include #if defined(Q_OS_WIN) #include #include #endif // libattica #include #include // own #include "../attica/atticaprovider_p.h" #include "cache.h" #include "../staticxml/staticxmlprovider_p.h" using namespace KNSCore; typedef QHash EngineProviderLoaderHash; Q_GLOBAL_STATIC(QThreadStorage, s_engineProviderLoaders) class EnginePrivate { public: QList categoriesMetadata; Attica::ProviderManager *m_atticaProviderManager = nullptr; QStringList tagFilter; QStringList downloadTagFilter; bool configLocationFallback = true; QString name; QMap commentsModels; }; Engine::Engine(QObject *parent) : QObject(parent) , m_installation(new Installation) , m_cache() , m_searchTimer(new QTimer) , d(new EnginePrivate) , m_currentPage(-1) , m_pageSize(20) , m_numDataJobs(0) , m_numPictureJobs(0) , m_numInstallJobs(0) , m_initialized(false) { m_searchTimer->setSingleShot(true); m_searchTimer->setInterval(1000); connect(m_searchTimer, &QTimer::timeout, this, &Engine::slotSearchTimerExpired); connect(m_installation, &Installation::signalInstallationFinished, this, &Engine::slotInstallationFinished); connect(m_installation, &Installation::signalInstallationFailed, this, &Engine::slotInstallationFailed); // Pass along old error signal through new signal for locations which have not been updated yet connect(this, &Engine::signalError, this, [this](const QString& message){ emit signalErrorCode(ErrorCode::UnknownError, message, QVariant()); }); } Engine::~Engine() { if (m_cache) { m_cache->writeRegistry(); } delete d->m_atticaProviderManager; delete m_searchTimer; delete m_installation; delete d; } bool Engine::init(const QString &configfile) { qCDebug(KNEWSTUFFCORE) << "Initializing KNSCore::Engine from '" << configfile << "'"; emit signalBusy(i18n("Initializing")); QScopedPointer conf; /// TODO KF6: This is fallback logic for an old location for the knsrc files. This should be considered deprecated in KF5, /// and it would make a lot of sense to disable it entirely for KF6 bool isRelativeConfig = QFileInfo(configfile).isRelative(); QString actualConfig; if (isRelativeConfig) { // Don't do the expensive search unless the config is relative actualConfig = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("knsrcfiles/%1").arg(configfile)); } QString configFileName{configfile}; if (isRelativeConfig && d->configLocationFallback && actualConfig.isEmpty()) { conf.reset(new KConfig(configfile)); qCWarning(KNEWSTUFFCORE) << "Using a deprecated location for the knsrc file" << configfile << " - please contact the author of the software which provides this file to get it updated to use the new location"; } else if (isRelativeConfig) { configFileName = QFileInfo(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("knsrcfiles/%1").arg(configfile))).baseName(); conf.reset(new KConfig(QString::fromLatin1("knsrcfiles/%1").arg(configfile), KConfig::FullConfig, QStandardPaths::GenericDataLocation)); } else { configFileName = QFileInfo(configfile).baseName(); conf.reset(new KConfig(configfile)); } if (conf->accessMode() == KConfig::NoAccess) { emit signalErrorCode(KNSCore::ConfigFileError, i18n("Configuration file exists, but cannot be opened: \"%1\"", configfile), configfile); qCCritical(KNEWSTUFFCORE) << "The knsrc file '" << configfile << "' was found but could not be opened."; return false; } KConfigGroup group; if (conf->hasGroup("KNewStuff3")) { qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff3 config: " << configfile; group = conf->group("KNewStuff3"); } else if (conf->hasGroup("KNewStuff2")) { qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff2 config: " << configfile; group = conf->group("KNewStuff2"); } else { emit signalErrorCode(KNSCore::ConfigFileError, i18n("Configuration file is invalid: \"%1\"", configfile), configfile); qCCritical(KNEWSTUFFCORE) << configfile << " doesn't contain a KNewStuff3 section."; return false; } d->name = group.readEntry("Name", QString()); m_categories = group.readEntry("Categories", QStringList()); m_adoptionCommand = group.readEntry("AdoptionCommand", QString()); qCDebug(KNEWSTUFFCORE) << "Categories: " << m_categories; m_providerFileUrl = group.readEntry("ProvidersUrl", QString()); d->tagFilter = group.readEntry("TagFilter", QStringList()); if (d->tagFilter.isEmpty()) { d->tagFilter.append(QStringLiteral("ghns_excluded!=1")); } d->downloadTagFilter = group.readEntry("DownloadTagFilter", QStringList()); // let installation read install specific config if (!m_installation->readConfig(group)) { return false; } connect(m_installation, &Installation::signalEntryChanged, this, &Engine::slotEntryChanged); m_cache = Cache::getCache(configFileName); qCDebug(KNEWSTUFFCORE) << "Cache is" << m_cache << "for" << configFileName; connect(this, &Engine::signalEntryChanged, m_cache.data(), &Cache::registerChangedEntry); m_cache->readRegistry(); m_initialized = true; // load the providers loadProviders(); return true; } QString KNSCore::Engine::name() const { return d->name; } QStringList Engine::categories() const { return m_categories; } QStringList Engine::categoriesFilter() const { return m_currentRequest.categories; } QList Engine::categoriesMetadata() { return d->categoriesMetadata; } void Engine::loadProviders() { if (m_providerFileUrl.isEmpty()) { // it would be nicer to move the attica stuff into its own class qCDebug(KNEWSTUFFCORE) << "Using OCS default providers"; delete d->m_atticaProviderManager; d->m_atticaProviderManager = new Attica::ProviderManager; connect(d->m_atticaProviderManager, &Attica::ProviderManager::providerAdded, this, &Engine::atticaProviderLoaded); connect(d->m_atticaProviderManager, &Attica::ProviderManager::failedToLoad, this, &Engine::slotProvidersFailed); d->m_atticaProviderManager->loadDefaultProviders(); } else { qCDebug(KNEWSTUFFCORE) << "loading providers from " << m_providerFileUrl; emit signalBusy(i18n("Loading provider information")); XmlLoader *loader = s_engineProviderLoaders()->localData().value(m_providerFileUrl); if (!loader) { qCDebug(KNEWSTUFFCORE) << "No xml loader for this url yet, so create one and temporarily store that" << m_providerFileUrl; loader = new XmlLoader(this); s_engineProviderLoaders()->localData().insert(m_providerFileUrl, loader); connect(loader, &XmlLoader::signalLoaded, this, [this](){ s_engineProviderLoaders()->localData().remove(m_providerFileUrl); }); connect(loader, &XmlLoader::signalFailed, this, [this](){ s_engineProviderLoaders()->localData().remove(m_providerFileUrl); }); loader->load(QUrl(m_providerFileUrl)); } connect(loader, &XmlLoader::signalLoaded, this, &Engine::slotProviderFileLoaded); connect(loader, &XmlLoader::signalFailed, this, &Engine::slotProvidersFailed); } } void Engine::slotProviderFileLoaded(const QDomDocument &doc) { qCDebug(KNEWSTUFFCORE) << "slotProvidersLoaded"; bool isAtticaProviderFile = false; // get each provider element, and create a provider object from it QDomElement providers = doc.documentElement(); if (providers.tagName() == QLatin1String("providers")) { isAtticaProviderFile = true; } else if (providers.tagName() != QLatin1String("ghnsproviders") && providers.tagName() != QLatin1String("knewstuffproviders")) { qWarning() << "No document in providers.xml."; emit signalErrorCode(KNSCore::ProviderError, i18n("Could not load get hot new stuff providers from file: %1", m_providerFileUrl), m_providerFileUrl); return; } QDomElement n = providers.firstChildElement(QStringLiteral("provider")); while (!n.isNull()) { qCDebug(KNEWSTUFFCORE) << "Provider attributes: " << n.attribute(QStringLiteral("type")); QSharedPointer provider; if (isAtticaProviderFile || n.attribute(QStringLiteral("type")).toLower() == QLatin1String("rest")) { - provider.reset(new AtticaProvider(m_categories)); + provider.reset(new AtticaProvider(m_categories, d->name)); connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList &categories){ d->categoriesMetadata = categories; emit signalCategoriesMetadataLoded(categories); }); } else { provider.reset(new StaticXmlProvider); } if (provider->setProviderXML(n)) { addProvider(provider); } else { emit signalErrorCode(KNSCore::ProviderError, i18n("Error initializing provider."), m_providerFileUrl); } n = n.nextSiblingElement(); } emit signalBusy(i18n("Loading data")); } void Engine::atticaProviderLoaded(const Attica::Provider &atticaProvider) { qCDebug(KNEWSTUFFCORE) << "atticaProviderLoaded called"; if (!atticaProvider.hasContentService()) { qCDebug(KNEWSTUFFCORE) << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content"; return; } QSharedPointer provider = - QSharedPointer (new AtticaProvider(atticaProvider, m_categories)); + QSharedPointer (new AtticaProvider(atticaProvider, m_categories, d->name)); connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList &categories){ d->categoriesMetadata = categories; emit signalCategoriesMetadataLoded(categories); }); addProvider(provider); } void Engine::addProvider(QSharedPointer provider) { qCDebug(KNEWSTUFFCORE) << "Engine addProvider called with provider with id " << provider->id(); m_providers.insert(provider->id(), provider); provider->setTagFilter(d->tagFilter); provider->setDownloadTagFilter(d->downloadTagFilter); connect(provider.data(), &Provider::providerInitialized, this, &Engine::providerInitialized); connect(provider.data(), &Provider::loadingFinished, this, &Engine::slotEntriesLoaded); connect(provider.data(), &Provider::entryDetailsLoaded, this, &Engine::slotEntryDetailsLoaded); connect(provider.data(), &Provider::payloadLinkLoaded, this, &Engine::downloadLinkLoaded); connect(provider.data(), &Provider::signalError, this, &Engine::signalError); connect(provider.data(), &Provider::signalErrorCode, this, &Engine::signalErrorCode); connect(provider.data(), &Provider::signalInformation, this, &Engine::signalIdle); } void Engine::providerJobStarted(KJob *job) { emit jobStarted(job, i18n("Loading data from provider")); } void Engine::slotProvidersFailed() { emit signalErrorCode(KNSCore::ProviderError, i18n("Loading of providers from file: %1 failed", m_providerFileUrl), m_providerFileUrl); } void Engine::providerInitialized(Provider *p) { qCDebug(KNEWSTUFFCORE) << "providerInitialized" << p->name(); p->setCachedEntries(m_cache->registryForProvider(p->id())); updateStatus(); for (const QSharedPointer &p : qAsConst(m_providers)) { if (!p->isInitialized()) { return; } } emit signalProvidersLoaded(); } void Engine::slotEntriesLoaded(const KNSCore::Provider::SearchRequest &request, KNSCore::EntryInternal::List entries) { m_currentPage = qMax(request.page, m_currentPage); qCDebug(KNEWSTUFFCORE) << "loaded page " << request.page << "current page" << m_currentPage << "count:" << entries.count(); if (request.filter == Provider::Updates) { emit signalUpdateableEntriesLoaded(entries); } else { m_cache->insertRequest(request, entries); emit signalEntriesLoaded(entries); } --m_numDataJobs; updateStatus(); } void Engine::reloadEntries() { emit signalResetView(); m_currentPage = -1; m_currentRequest.pageSize = m_pageSize; m_currentRequest.page = 0; m_numDataJobs = 0; for (const QSharedPointer &p : qAsConst(m_providers)) { if (p->isInitialized()) { if (m_currentRequest.filter == Provider::Installed) { // when asking for installed entries, never use the cache p->loadEntries(m_currentRequest); } else { // take entries from cache until there are no more EntryInternal::List cache; EntryInternal::List lastCache = m_cache->requestFromCache(m_currentRequest); while (!lastCache.isEmpty()) { qCDebug(KNEWSTUFFCORE) << "From cache"; cache << lastCache; m_currentPage = m_currentRequest.page; ++m_currentRequest.page; lastCache = m_cache->requestFromCache(m_currentRequest); } // Since the cache has no more pages, reset the request's page if (m_currentPage >= 0) { m_currentRequest.page = m_currentPage; } if (!cache.isEmpty()) { emit signalEntriesLoaded(cache); } else { qCDebug(KNEWSTUFFCORE) << "From provider"; p->loadEntries(m_currentRequest); ++m_numDataJobs; updateStatus(); } } } } } void Engine::setCategoriesFilter(const QStringList &categories) { m_currentRequest.categories = categories; reloadEntries(); } void Engine::setSortMode(Provider::SortMode mode) { if (m_currentRequest.sortMode != mode) { m_currentRequest.page = -1; } m_currentRequest.sortMode = mode; reloadEntries(); } Provider::SortMode KNSCore::Engine::sortMode() const { return m_currentRequest.sortMode; } void KNSCore::Engine::setFilter(Provider::Filter filter) { if (m_currentRequest.filter != filter) { m_currentRequest.page = -1; } m_currentRequest.filter = filter; reloadEntries(); } Provider::Filter KNSCore::Engine::filter() const { return m_currentRequest.filter; } void KNSCore::Engine::fetchEntryById(const QString &id) { m_searchTimer->stop(); m_currentRequest = KNSCore::Provider::SearchRequest(KNSCore::Provider::Newest, KNSCore::Provider::ExactEntryId, id); m_currentRequest.pageSize = m_pageSize; EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); if (!cache.isEmpty()) { reloadEntries(); } else { m_searchTimer->start(); } } void Engine::setSearchTerm(const QString &searchString) { m_searchTimer->stop(); m_currentRequest.searchTerm = searchString; EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest); if (!cache.isEmpty()) { reloadEntries(); } else { m_searchTimer->start(); } } QString KNSCore::Engine::searchTerm() const { return m_currentRequest.searchTerm; } void Engine::setTagFilter(const QStringList &filter) { d->tagFilter = filter; for (const QSharedPointer &p : qAsConst(m_providers)) { p->setTagFilter(d->tagFilter); } } QStringList Engine::tagFilter() const { return d->tagFilter; } void KNSCore::Engine::addTagFilter(const QString &filter) { d->tagFilter << filter; for (const QSharedPointer &p : qAsConst(m_providers)) { p->setTagFilter(d->tagFilter); } } void Engine::setDownloadTagFilter(const QStringList &filter) { d->downloadTagFilter = filter; for (const QSharedPointer &p : qAsConst(m_providers)) { p->setDownloadTagFilter(d->downloadTagFilter); } } QStringList Engine::downloadTagFilter() const { return d->downloadTagFilter; } void Engine::addDownloadTagFilter(const QString &filter) { d->downloadTagFilter << filter; for (const QSharedPointer &p : qAsConst(m_providers)) { p->setDownloadTagFilter(d->downloadTagFilter); } } void Engine::slotSearchTimerExpired() { reloadEntries(); } void Engine::requestMoreData() { qCDebug(KNEWSTUFFCORE) << "Get more data! current page: " << m_currentPage << " requested: " << m_currentRequest.page; if (m_currentPage < m_currentRequest.page) { return; } m_currentRequest.page++; doRequest(); } void Engine::requestData(int page, int pageSize) { m_currentRequest.page = page; m_currentRequest.pageSize = pageSize; doRequest(); } void Engine::doRequest() { for (const QSharedPointer &p : qAsConst(m_providers)) { if (p->isInitialized()) { p->loadEntries(m_currentRequest); ++m_numDataJobs; updateStatus(); } } } void Engine::install(KNSCore::EntryInternal entry, int linkId) { if (entry.status() == KNS3::Entry::Updateable) { entry.setStatus(KNS3::Entry::Updating); } else { entry.setStatus(KNS3::Entry::Installing); } emit signalEntryChanged(entry); qCDebug(KNEWSTUFFCORE) << "Install " << entry.name() << " from: " << entry.providerId(); QSharedPointer p = m_providers.value(entry.providerId()); if (p) { p->loadPayloadLink(entry, linkId); ++m_numInstallJobs; updateStatus(); } } void Engine::slotInstallationFinished() { --m_numInstallJobs; updateStatus(); } void Engine::slotInstallationFailed(const QString &message) { --m_numInstallJobs; emit signalErrorCode(KNSCore::InstallationError, message, QVariant()); } void Engine::slotEntryDetailsLoaded(const KNSCore::EntryInternal &entry) { emit signalEntryDetailsLoaded(entry); } void Engine::downloadLinkLoaded(const KNSCore::EntryInternal &entry) { m_installation->install(entry); } void Engine::uninstall(KNSCore::EntryInternal entry) { const KNSCore::EntryInternal::List list = m_cache->registryForProvider(entry.providerId()); //we have to use the cached entry here, not the entry from the provider //since that does not contain the list of installed files KNSCore::EntryInternal actualEntryForUninstall; for (const KNSCore::EntryInternal &eInt : list) { if (eInt.uniqueId() == entry.uniqueId()) { actualEntryForUninstall = eInt; break; } } if (!actualEntryForUninstall.isValid()) { qCDebug(KNEWSTUFFCORE) << "could not find a cached entry with following id:" << entry.uniqueId() << " -> using the non-cached version"; return; } entry.setStatus(KNS3::Entry::Installing); actualEntryForUninstall.setStatus(KNS3::Entry::Installing); emit signalEntryChanged(entry); qCDebug(KNEWSTUFFCORE) << "about to uninstall entry " << entry.uniqueId(); // FIXME: change the status? m_installation->uninstall(actualEntryForUninstall); entry.setStatus(KNS3::Entry::Deleted); //status for actual entry gets set in m_installation->uninstall() emit signalEntryChanged(entry); } void Engine::loadDetails(const KNSCore::EntryInternal &entry) { QSharedPointer p = m_providers.value(entry.providerId()); p->loadEntryDetails(entry); } void Engine::loadPreview(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type) { qCDebug(KNEWSTUFFCORE) << "START preview: " << entry.name() << type; ImageLoader *l = new ImageLoader(entry, type, this); connect(l, &ImageLoader::signalPreviewLoaded, this, &Engine::slotPreviewLoaded); connect(l, &ImageLoader::signalError, this, [this](const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type, const QString &errorText) { emit signalErrorCode(KNSCore::ImageError, errorText, QVariantList() << entry.name() << type); qCDebug(KNEWSTUFFCORE) << "ERROR preview: " << errorText << entry.name() << type; --m_numPictureJobs; updateStatus(); }); l->start(); ++m_numPictureJobs; updateStatus(); } void Engine::slotPreviewLoaded(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type) { qCDebug(KNEWSTUFFCORE) << "FINISH preview: " << entry.name() << type; emit signalEntryPreviewLoaded(entry, type); --m_numPictureJobs; updateStatus(); } void Engine::contactAuthor(const EntryInternal &entry) { if (!entry.author().email().isEmpty()) { // invoke mail with the address of the author QUrl mailUrl; mailUrl.setScheme(QStringLiteral("mailto")); mailUrl.setPath(entry.author().email()); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), i18n("Re: %1", entry.name())); mailUrl.setQuery(query); QDesktopServices::openUrl(mailUrl); } else if (!entry.author().homepage().isEmpty()) { QDesktopServices::openUrl(QUrl(entry.author().homepage())); } } void Engine::slotEntryChanged(const KNSCore::EntryInternal &entry) { emit signalEntryChanged(entry); } bool Engine::userCanVote(const EntryInternal &entry) { QSharedPointer p = m_providers.value(entry.providerId()); return p->userCanVote(); } void Engine::vote(const EntryInternal &entry, uint rating) { QSharedPointer p = m_providers.value(entry.providerId()); p->vote(entry, rating); } bool Engine::userCanBecomeFan(const EntryInternal &entry) { QSharedPointer p = m_providers.value(entry.providerId()); return p->userCanBecomeFan(); } void Engine::becomeFan(const EntryInternal &entry) { QSharedPointer p = m_providers.value(entry.providerId()); p->becomeFan(entry); } void Engine::updateStatus() { if (m_numDataJobs > 0) { emit signalBusy(i18n("Loading data")); } else if (m_numPictureJobs > 0) { emit signalBusy(i18np("Loading one preview", "Loading %1 previews", m_numPictureJobs)); } else if (m_numInstallJobs > 0) { emit signalBusy(i18n("Installing")); } else { emit signalIdle(QString()); } } void Engine::checkForUpdates() { for (const QSharedPointer &p : qAsConst(m_providers)) { Provider::SearchRequest request(KNSCore::Provider::Newest, KNSCore::Provider::Updates); p->loadEntries(request); } } void KNSCore::Engine::checkForInstalled() { for (const QSharedPointer &p : qAsConst(m_providers)) { Provider::SearchRequest request(KNSCore::Provider::Newest, KNSCore::Provider::Installed); request.page = 0; request.pageSize = m_pageSize; p->loadEntries(request); } } /** * we look for the directory where all the resources got installed. * assuming it was extracted into a directory */ static QDir sharedDir(QStringList dirs, const QString &rootPath) { while(!dirs.isEmpty()) { const QString currentPath = QDir::cleanPath(dirs.takeLast()); if (!currentPath.startsWith(rootPath)) continue; const QFileInfo current(currentPath); if (!current.isDir()) continue; const QDir dir = current.dir(); if (dir.path()==(rootPath+dir.dirName())) { return dir; } } return {}; } QString Engine::adoptionCommand(const KNSCore::EntryInternal& entry) const { auto adoption = m_adoptionCommand; if(adoption.isEmpty()) return {}; const QLatin1String dirReplace("%d"); if (adoption.contains(dirReplace)) { QString installPath = sharedDir(entry.installedFiles(), m_installation->targetInstallationPath()).path(); adoption.replace(dirReplace, installPath); } const QLatin1String fileReplace("%f"); if (adoption.contains(fileReplace)) { if (entry.installedFiles().isEmpty()) { qCWarning(KNEWSTUFFCORE) << "no installed files to adopt"; } else if (entry.installedFiles().count() != 1) { qCWarning(KNEWSTUFFCORE) << "can only adopt one file, will be using the first" << entry.installedFiles().at(0); } adoption.replace(fileReplace, entry.installedFiles().at(0)); } return adoption; } bool KNSCore::Engine::hasAdoptionCommand() const { return !m_adoptionCommand.isEmpty(); } void KNSCore::Engine::setPageSize(int pageSize) { m_pageSize = pageSize; } QStringList KNSCore::Engine::configSearchLocations(bool includeFallbackLocations) { QStringList ret; if(includeFallbackLocations) { ret += QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); } QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for( const QString& path : paths) { ret << QString::fromLocal8Bit("%1/knsrcfiles").arg(path); } return ret; } void KNSCore::Engine::setConfigLocationFallback(bool enableFallback) { d->configLocationFallback = enableFallback; } QSharedPointer KNSCore::Engine::provider(const QString &providerId) const { return m_providers.value(providerId); } QSharedPointer KNSCore::Engine::defaultProvider() const { if (m_providers.count() > 0) return m_providers.constBegin().value(); return nullptr; } KNSCore::CommentsModel *KNSCore::Engine::commentsForEntry(const KNSCore::EntryInternal &entry) { CommentsModel *model = d->commentsModels[entry]; if (!model) { model = new CommentsModel(this); model->setEntry(entry); connect(model, &QObject::destroyed, this, [=](){ d->commentsModels.remove(entry); }); d->commentsModels[entry] = model; } return model; }