diff --git a/KF5NewStuffCoreConfig.cmake.in b/KF5NewStuffCoreConfig.cmake.in index 9fc2f986..52992bd2 100644 --- a/KF5NewStuffCoreConfig.cmake.in +++ b/KF5NewStuffCoreConfig.cmake.in @@ -1,7 +1,10 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(KF5Attica "@KF5_DEP_VERSION@") +include(KDEInstallDirs) + +set(KDE_INSTALL_KNSRCDIR "${KDE_INSTALL_DATADIR}/knsrcfiles") include("${CMAKE_CURRENT_LIST_DIR}/KF5NewStuffCoreTargets.cmake") @PACKAGE_INCLUDE_CORE_QCHTARGETS@ diff --git a/README.md b/README.md index c0f54f95..3e9215c3 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,79 @@ # KNewStuff Framework for downloading and sharing additional application data ## Introduction The KNewStuff library implements collaborative data sharing for applications. It uses libattica to support the Open Collaboration Services specification. ## Usage There are three parts to KNewStuff: * *KNewStuffCore* - The core functionality, which takes care of the actual work (downloading data and interacting with the remote services). Importantly, this library has no dependencies past Tier 1, and so while the entire framework is to be considered Tier 3, the KNewStuffCore library can be considered Tier 2 for integration purposes. * *KNewStuff* - A Qt Widget based UI library, designed for ease of implementation of various UI patterns found through KDE applications (such as the Get New Stuff buttons, as well as generic download and upload dialogues) * *KNewStuffQuick* - A set of Qt Quick based components designed to provide similar pattern support as KNewStuff, except for Qt Quick based applications, and specifically in Kirigami based applications. If you are using CMake, you need to find the modules, which can be done by doing one of the following in your CMakeLists.txt: Either use the more modern (and compact) component based method (only actually add the component you need, since both NewStuff and NewStuffQuick depend on NewStuffCore): find_package(KF5 COMPONENTS NewStuffCore NewStuff NewStuffQuick) Or use the old-fashioned syntax find_package(KF5NewStuffCore CONFIG) # for the KNewStuffCore library only find_package(KF5NewStuff CONFIG) # for the KNewStuff UI library, will pull in KNewStuffCore for you find_package(KF5NewStuffQuick CONFIG) # for the KNewStuffQuick UI library, will pull in KNewStuffCore for you Also remember to link to the library you are using (either KF5::NewStuff or KF5::NewStuffCore), and for the Qt Quick NewStuffQuick module, add the following to the QML files where you wish to use the components: import org.kde.newstuff 1.0 Finally, because KNewStuffQuick is not a link time requirement, it would be good form to mark it as a runtime requirement (and describing why you need them), which is done by adding the following in your CMakeLists.txt sometime after the find statement: set_package_properties(KF5NewStuffQuick PROPERTIES DESCRIPTION "Qt Quick components used for interacting with remote data services" URL "https://api.kde.org/frameworks/knewstuff/html/index.html" PURPOSE "Required to Get Hot New Stuff for my applicaton" TYPE RUNTIME) +When installing your knsrc configuration file, you should install it into the location +where KNewStuffCore expects it to be found. Do this by using the CMake variable +KDE_INSTALL_KNSRCDIR as provided by the KNewStuffCore module. You can also handle this +yourself, which means you will need to feed Engine::init() the full path to the knsrc file. + ## Which module should you use? When building applications designed to fit in with other classic, widget based applications, the application authors should use KNS3::DownloadDialog for downloading application content. For uploading KNS3::UploadDialog is used. When building Qt Quick (and in particular Kirigami) based applications, you can use the NewStuffList item from the org.kde.newstuff import to achieve a similar functionality to KNS3::DownloadDialog. You can also use the ItemsModel directly, if this is not featureful enough. Uploading is currently not exposed in KNewStuffQuick. If neither of these options are powerful enough for your needs, you can access the functionality directly through the classes in the KNSCore namespace. Related information such as creation of *.knsrc files can be found on techbase in the [Get Hot New Stuff tutorials](http://techbase.kde.org/Development/Tutorials#Get_Hot_New_Stuff). diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 718c9c80..12fdd496 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -1,726 +1,764 @@ /* 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 "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; }; 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")); - KConfig conf(configfile); - if (conf.accessMode() == KConfig::NoAccess) { + 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)); + } + if (isRelativeConfig && d->configLocationFallback && actualConfig.isEmpty()) { + conf.reset(new KConfig(configfile)); + qDebug() << "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) { + qDebug() << "Using the NEW location for knsrc file" << configfile; + conf.reset(new KConfig(QString::fromLatin1("knsrcfiles/%1").arg(configfile), KConfig::FullConfig, QStandardPaths::GenericDataLocation)); + } else { + qDebug() << "Absolute configuration path for" << configfile << ", this could be literally anywhere and we just do as we're told..."; + 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")) { + if (conf->hasGroup("KNewStuff3")) { qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff3 config: " << configfile; - group = conf.group("KNewStuff3"); - } else if (conf.hasGroup("KNewStuff2")) { + group = conf->group("KNewStuff3"); + } else if (conf->hasGroup("KNewStuff2")) { qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff2 config: " << configfile; - group = conf.group("KNewStuff2"); + 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; } 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()); const QString configFileName = QFileInfo(QDir::isAbsolutePath(configfile) ? configfile : QStandardPaths::locate(QStandardPaths::GenericConfigLocation, configfile)).baseName(); // 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); connect(this, &Engine::signalEntryChanged, m_cache.data(), &Cache::registerChangedEntry); m_cache->readRegistry(); m_initialized = true; // load the providers loadProviders(); return true; } 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)); 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)); 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(); } void KNSCore::Engine::setFilter(Provider::Filter filter) { if (m_currentRequest.filter != filter) { m_currentRequest.page = -1; } m_currentRequest.filter = filter; reloadEntries(); } 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(); } } 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; +} diff --git a/src/core/engine.h b/src/core/engine.h index aa2e033c..abea38f1 100644 --- a/src/core/engine.h +++ b/src/core/engine.h @@ -1,520 +1,541 @@ /* knewstuff3/engine.h. Copyright (c) 2007 Josef Spillner Copyright (C) 2007-2010 Frederik Gladhorn Copyright (c) 2009 Jeremy Whiting 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_ENGINE_P_H #define KNEWSTUFF3_ENGINE_P_H #include #include #include #include #include "provider.h" #include "entryinternal.h" #include "errorcode.h" #include "knewstuffcore_export.h" class QTimer; class KJob; class EnginePrivate; namespace Attica { class ProviderManager; class Provider; } /** * Contains the core functionality for handling interaction with NewStuff providers. * The entrypoint for most things will be the creation of an instance of KNSCore::Engine * which will other classes then either use or get instantiated from directly. * * NOTE: When implementing anything on top of KNSCore, without using either KNS3 or the * Qt Quick components, you will need to implement a custom QuestionListener (see that * class for instructions) * * @see KNSCore::Engine * @see KNSCore::ItemsModel * @see KNSCore::QuestionListener */ namespace KNSCore { class Cache; class Installation; /** * KNewStuff engine. * An engine keeps track of data which is available locally and remote * and offers high-level synchronization calls as well as upload and download * primitives using an underlying GHNS protocol. */ class KNEWSTUFFCORE_EXPORT Engine : public QObject { Q_OBJECT public: /** * Constructor. */ explicit Engine(QObject *parent = nullptr); /** * Destructor. Frees up all the memory again which might be taken * by cached entries and providers. */ ~Engine(); /** * Initializes the engine. This step is application-specific and relies * on an external configuration file, which determines all the details * about the initialization. * * @param configfile KNewStuff2 configuration file (*.knsrc) * @return \b true if any valid configuration was found, \b false otherwise */ bool init(const QString &configfile); /** * Installs an entry's payload file. This includes verification, if * necessary, as well as decompression and other steps according to the * application's *.knsrc file. * * @param entry Entry to be installed * * @see signalInstallationFinished * @see signalInstallationFailed */ void install(KNSCore::EntryInternal entry, int linkId = 1); /** * Uninstalls an entry. It reverses the steps which were performed * during the installation. * * @param entry The entry to deinstall */ void uninstall(KNSCore::EntryInternal entry); /** * Attempt to load a specific preview for the specified entry. * * @param entry The entry to fetch a preview for * @param type The particular preview to fetch * * @see signalEntryPreviewLoaded(KNSCore::EntryInternal, KNSCore::EntryInternal::PreviewType); * @see signalPreviewFailed(); */ void loadPreview(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type); /** * Get the full details of a specific entry * * @param entry The entry to get full details for * * @see Entry::signalEntryDetailsLoaded(KNSCore::EntryInternal) */ void loadDetails(const KNSCore::EntryInternal &entry); /** * Set the order the search results are returned in. * * Search requests default to showing the newest entries first. * * Note: This will automatically launch a search, which means * you do not need to call requestData manually. * * @see KNSCore::Provider::SearchRequest * @param mode The order you want search results to come back in. */ void setSortMode(Provider::SortMode mode); /** * Set a filter for results (defaults to none), which will allow you * to show only installed entries, installed entries which have updates, * or a specific item with a specified ID. The latter further requires * the search term to be the exact ID of the entry you wish to retrieve. * * Note: This will automatically launch a search, which means * you do not need to call requestData manually. * * @see fetchEntryById(QString) * @see setSearchTerm(QString) * @param filter The type of results you wish to see */ void setFilter(Provider::Filter filter); /** * Set the categories that will be included in searches * * Note: This will automatically launch a search, which means * you do not need to call requestData manually. * * @see KNSCore::Engine::categories() * @param categories A list of strings of categories */ void setCategoriesFilter(const QStringList &categories); /** * Sets a string search term. * * Note: This will automatically launch a search, which means * you do not need to call requestData manually. * * @param searchString The search term you wish to search for */ void setSearchTerm(const QString &searchString); void reloadEntries(); void requestMoreData(); void requestData(int page, int pageSize); /** * Set a filter for results, which filters out all entries which do not match * the filter, as applied to the tags for the entry. This filters only on the * tags specified for the entry itself. To filter the downloadlinks, use * setDownloadTagFilter(QStringList). * * @note The default filter if one is not set from your knsrc file will filter * out entries marked as ghns_excluded=1. To retain this when setting a custom * filter, add "ghns_excluded!=1" as one of the filters. * * @note Some tags provided by OCS do not supply a value (and are simply passed * as a key). These will be interpreted as having the value 1 for filtering * purposes. An example of this might be ghns_excluded, which in reality will * generally be passed through ocs as "ghns_excluded" rather than "ghns_excluded=1" * * @note As tags are metadata, they are provided in the form of adjectives. They * are never supplied as action verbs or instructions (as an example, a good tag * to suggest that for example a wallpaper is painted would be "painted" as opposed * to "paint", and another example might be that an item should be "excluded" as * opposed to "exclude"). * * == Examples of use == * Value for tag "tagname" must be exactly "tagdata": * tagname==tagdata * * Value for tag "tagname" must be different from "tagdata": * tagname!=tagdata * * == KNSRC entry == * A tag filter line in a .knsrc file, which is a comma semarated list of * tag/value pairs, might look like: * * TagFilter=ghns_excluded!=1,data##mimetype==application/cbr+zip,data##mimetype==application/cbr+rar * which would honour the exclusion and filter out anything that does not * include a comic book archive in either zip or rar format in one or more * of the download items. * Notice in particular that there are two data##mimetype entries. Use this * for when a tag may have multiple values. * * TagFilter=application##architecture==x86_64 * which would not honour the exclusion, and would filter out all entries * which do not mark themselves as having a 64bit application binary in at * least one download item. * * The value does not current suppport wildcards. The list should be considered * a binary AND operation (that is, all filter entries must match for the data * entry to be included in the return data) * * @param filter The filter in the form of a list of strings * @see setDownloadTagFilter(QStringList) * @since 5.51 */ void setTagFilter(const QStringList &filter); /** * Gets the current tag filter list * @see setTagFilter(QStringList) * @since 5.51 */ QStringList tagFilter() const; /** * Add a single filter entry to the entry tag filter. The filter should be in * the same form as the filter lines in the list used by setTagFilter(QStringList) * @param filter The filter in the form of a string * @see setTagFilter(QStringList) * @since 5.51 */ void addTagFilter(const QString &filter); /** * Sets a filter to be applied to the downloads for an entry. The logic is the * same as used in setTagFilter(QStringList), but vitally, only one downloadlink * is required to match the filter for the list to be valid. If you do not wish * to show the others in your client, you must hide them yourself. * * For an entry to be accepted when a download tag filter is set, it must also * be accepted by the entry filter (so, for example, while a list of downloads * might be accepted, if the entry has ghns_excluded set, and the default entry * filter is set, the entry will still be filtered out). * * In your knsrc file, set DownloadTagFilter to the filter you wish to apply, * using the same logic as described for the entry tagfilter. * * @param filter The filter in the form of a list of strings * @see setTagFilter(QStringList) * @since 5.51 */ void setDownloadTagFilter(const QStringList &filter); /** * Gets the current downloadlink tag filter list * @see setDownloadTagFilter(QStringList) * @since 5.51 */ QStringList downloadTagFilter() const; /** * Add a single filter entry to the download tag filter. The filter should be in * the same form as the filter lines in the list used by setDownloadsTagFilter(QStringList) * @param filter The filter in the form of a string * @see setTagFilter(QStringList) * @see setDownloadTagFilter(QStringList) * @since 5.51 */ void addDownloadTagFilter(const QString &filter); /** * Request for packages that are installed and need update * * These will be reported through the signal @see signalUpdateableEntriesLoaded(). */ void checkForUpdates(); /** * Requests installed packages with an up to date state * * @see signalEntriesLoaded() */ void checkForInstalled(); /** * Convenience method to launch a search for one specific entry. * * @note it will reset the engine state * * @param id The ID of the entry you wish to fetch */ void fetchEntryById(const QString &id); /** * Try to contact the author of the entry by email or showing their homepage. */ void contactAuthor(const EntryInternal &entry); /** * Whether or not a user is able to vote on the passed entry. * * @param entry The entry to check votability on * @return True if the user is able to vote on the entry */ bool userCanVote(const EntryInternal &entry); /** * Cast a vote on the passed entry. * * @param entry The entry to vote on * @param rating A number from 0 to 100, 50 being neutral, 0 being most negative and 100 being most positive. */ void vote(const EntryInternal &entry, uint rating); /** * Whether or not the user is allowed to become a fan of * a particular entry. * Not all providers (and consequently entries) support the fan functionality * and you can use this function to determine this ability. * @param entry The entry the user might wish to be a fan of * @return Whether or not it is possible for the user to become a fan of that entry */ bool userCanBecomeFan(const EntryInternal &entry); /** * This will mark the user who is currently authenticated as a fan * of the entry passed to the function. * @param entry The entry the user wants to be a fan of */ void becomeFan(const EntryInternal &entry); // FIXME There is currently no exposed API to remove the fan status /** * The list of the server-side names of the categories handled by this * engine instance. This corresponds directly to the list of categories * in your knsrc file. This is not supposed to be used as user-facing * strings - @see categoriesMetadata() for that. * * @return The categories which this instance of Engine handles */ QStringList categories() const; /** * The list of categories searches will actually show results from. This * is a subset of the categories() list. * * @see KNSCore::Engine::setCategoriesFilter(QString) */ QStringList categoriesFilter() const; /** * The list of metadata for the categories handled by this engine instance. * If you wish to show the categories to the user, this is the data to use. * The category name is the string used to set categories for the filter, * and also what is returned by both categories() and categoriesFilter(). * The human-readable name is displayName, and the only thing which should * be shown to the user. * * @return The metadata for all categories handled by this engine */ QList categoriesMetadata(); /** * The adoption command can be used to allow a user to make use of an entry's * installed data. For example, this command might be used to ask the system to * switch to a wallpaper or icon theme which was installed with KNS. * * The following is how this might look in a knsrc file. The example shows how * an external tool is called on the installed file represented by %d. *
        AdoptionCommand=/usr/lib64/libexec/plasma-changeicons %d
      * 
* * @param entry The entry to return an adoption command for * @return The command to run to adopt this entry's installed data */ QString adoptionCommand(const KNSCore::EntryInternal &entry) const; /** * Whether or not an adoption command exists for this engine * * @see adoptionCommand(KNSCore::EntryInternal) * @return True if an adoption command exists */ bool hasAdoptionCommand() const; /** * Set the page size for requests not made explicitly with requestData(int,int) * @param pageSize the default number of entries to request from the provider * @see requestData(int,int) */ void setPageSize(int pageSize); + + /** + * Get a list of all the locations which will be used when searching for knsrc + * files, in the order in which the search will occur. + * + * @param includeFallbackLocations Whether or not the deprecated search locations are included + * @return The search list for knsrc files + * @since 5.57 + */ + static QStringList configSearchLocations(bool includeFallbackLocations = false); + /** + * Sets whether or not the config file location discovery fallback should be active. + * If enabled (default), if the config file is not found in the knsrcfiles location, + * then the engine will also look in the systemwide config location (usually /etc/xdg + * on linux). If disabled, this fallback location will not be searched. + * + * @param enableFallback Whether or not the fallback discovery should be enabled + * @since 5.57 + */ + void setConfigLocationFallback(bool enableFallback); + Q_SIGNALS: /** * Indicates a message to be added to the ui's log, or sent to a messagebox */ void signalMessage(const QString &message); void signalProvidersLoaded(); void signalEntriesLoaded(const KNSCore::EntryInternal::List &entries); void signalUpdateableEntriesLoaded(const KNSCore::EntryInternal::List &entries); void signalEntryChanged(const KNSCore::EntryInternal &entry); void signalEntryDetailsLoaded(const KNSCore::EntryInternal &entry); // a new search result is there, clear the list of items void signalResetView(); void signalEntryPreviewLoaded(const KNSCore::EntryInternal &, KNSCore::EntryInternal::PreviewType); void signalPreviewFailed(); void signalEntryUploadFinished(); void signalEntryUploadFailed(); void signalDownloadDialogDone(KNSCore::EntryInternal::List); void jobStarted(KJob *, const QString &); QT_DEPRECATED void signalError(const QString &); void signalBusy(const QString &); void signalIdle(const QString &); /** * Fires in the case of any critical or serious errors, such as network or API problems. * @param errorCode Represents the specific type of error which has occurred * @param message A human-readable message which can be shown to the end user * @param metadata Any additional data which might be hepful to further work out the details of the error (see KNSCore::EntryInternal::ErrorCode for the metadata details) * @see KNSCore::EntryInternal::ErrorCode * @since 5.53 */ void signalErrorCode(const KNSCore::ErrorCode &errorCode, const QString &message, const QVariant &metadata); void signalCategoriesMetadataLoded(const QList &categories); private Q_SLOTS: // the .knsrc file was loaded void slotProviderFileLoaded(const QDomDocument &doc); // instead of getting providers from knsrc, use what was configured in ocs systemsettings void atticaProviderLoaded(const Attica::Provider &provider); // loading the .knsrc file failed void slotProvidersFailed(); // called when a provider is ready to work void providerInitialized(KNSCore::Provider *); void slotEntriesLoaded(const KNSCore::Provider::SearchRequest &, KNSCore::EntryInternal::List); void slotEntryDetailsLoaded(const KNSCore::EntryInternal &entry); void slotPreviewLoaded(const KNSCore::EntryInternal &entry, KNSCore::EntryInternal::PreviewType type); void slotSearchTimerExpired(); void slotEntryChanged(const KNSCore::EntryInternal &entry); void slotInstallationFinished(); void slotInstallationFailed(const QString &message); void downloadLinkLoaded(const KNSCore::EntryInternal &entry); void providerJobStarted(KJob *); private: /** * load providers from the providersurl in the knsrc file * creates providers based on their type and adds them to the list of providers */ void loadProviders(); /** Add a provider and connect it to the right slots */ void addProvider(QSharedPointer provider); void updateStatus(); void doRequest(); //FIXME KF6: move all of this in EnginePrivate // handle installation of entries Installation *m_installation; // read/write cache of entries QSharedPointer m_cache; QTimer *m_searchTimer; // The url of the file containing information about content providers QString m_providerFileUrl; // Categories from knsrc file QStringList m_categories; QHash > m_providers; QString m_adoptionCommand; // the current request from providers Provider::SearchRequest m_currentRequest; EnginePrivate * const d; // the page that is currently displayed, so it is not requested repeatedly int m_currentPage; // when requesting entries from a provider, how many to ask for int m_pageSize; int m_numDataJobs; int m_numPictureJobs; int m_numInstallJobs; // If the provider is ready to be used bool m_initialized; Q_DISABLE_COPY(Engine) }; } #endif