KNewStuff

enginebase.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Josef Spillner <spillner@kde.org>
3 SPDX-FileCopyrightText: 2007-2010 Frederik Gladhorn <gladhorn@kde.org>
4 SPDX-FileCopyrightText: 2009 Jeremy Whiting <jpwhiting@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "enginebase.h"
10#include "enginebase_p.h"
11#include <knewstuffcore_debug.h>
12
13#include <KConfig>
14#include <KConfigGroup>
15#include <KFileUtils>
16#include <KFormat>
17#include <KLocalizedString>
18
19#include <QFileInfo>
20#include <QNetworkRequest>
21#include <QProcess>
22#include <QStandardPaths>
23#include <QThreadStorage>
24#include <QTimer>
25
26#include "attica/atticaprovider_p.h"
27#include "opds/opdsprovider_p.h"
28#include "resultsstream.h"
29#include "staticxml/staticxmlprovider_p.h"
30#include "transaction.h"
31#include "xmlloader_p.h"
32
33using namespace KNSCore;
34
37
38EngineBase::EngineBase(QObject *parent)
39 : QObject(parent)
41{
42 connect(d->installation, &Installation::signalInstallationError, this, [this](const QString &message) {
43 Q_EMIT signalErrorCode(ErrorCode::InstallationError, i18n("An error occurred during the installation process:\n%1", message), QVariant());
44 });
45}
46
56
57EngineBase::~EngineBase()
58{
59 if (d->cache) {
60 d->cache->writeRegistry();
61 }
62 delete d->atticaProviderManager;
63 delete d->installation;
64}
65
67{
68 qCDebug(KNEWSTUFFCORE) << "Initializing KNSCore::EngineBase from" << configfile;
69
71 if (QFileInfo(configfile).isAbsolute()) {
72 resolvedConfigFilePath = configfile; // It is an absolute path
73 } else {
74 // Don't do the expensive search unless the config is relative
76 }
77
79 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError, i18n("Configuration file does not exist: \"%1\"", configfile), configfile);
80 qCCritical(KNEWSTUFFCORE) << "The knsrc file" << configfile << "does not exist";
81 return false;
82 }
83
85
86 if (conf.accessMode() == KConfig::NoAccess) {
87 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError, i18n("Configuration file exists, but cannot be opened: \"%1\"", configfile), configfile);
88 qCCritical(KNEWSTUFFCORE) << "The knsrc file" << configfile << "was found but could not be opened.";
89 return false;
90 }
91
92 const KConfigGroup group = conf.hasGroup(QStringLiteral("KNewStuff")) ? conf.group(QStringLiteral("KNewStuff")) : conf.group(QStringLiteral("KNewStuff3"));
93 if (!group.exists()) {
94 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ConfigFileError, i18n("Configuration file is invalid: \"%1\"", configfile), configfile);
95 qCCritical(KNEWSTUFFCORE) << configfile << "doesn't contain a KNewStuff or KNewStuff3 section.";
96 return false;
97 }
98
99 d->name = group.readEntry("Name");
100 d->categories = group.readEntry("Categories", QStringList());
101 qCDebug(KNEWSTUFFCORE) << "Categories: " << d->categories;
102 d->adoptionCommand = group.readEntry("AdoptionCommand");
103 d->useLabel = group.readEntry("UseLabel", i18n("Use"));
105 d->uploadEnabled = group.readEntry("UploadEnabled", true);
107
108 d->providerFileUrl = group.readEntry("ProvidersUrl", QUrl(QStringLiteral("https://autoconfig.kde.org/ocs/providers.xml")));
109 if (group.readEntry("UseLocalProvidersFile", false)) {
110 // The local providers file is called "appname.providers", to match "appname.knsrc"
111 d->providerFileUrl = QUrl::fromLocalFile(QLatin1String("%1.providers").arg(configfile.left(configfile.length() - 6)));
112 }
113
114 d->tagFilter = group.readEntry("TagFilter", QStringList(QStringLiteral("ghns_excluded!=1")));
115 d->downloadTagFilter = group.readEntry("DownloadTagFilter", QStringList());
116
117 QByteArray rawContentWarningType = group.readEntry("ContentWarning", QByteArrayLiteral("Static"));
118 bool ok = false;
119 int value = QMetaEnum::fromType<ContentWarningType>().keyToValue(rawContentWarningType.constData(), &ok);
120 if (ok) {
121 d->contentWarningType = static_cast<ContentWarningType>(value);
122 } else {
123 qCWarning(KNEWSTUFFCORE) << "Could not parse ContentWarning, invalid entry" << rawContentWarningType;
124 }
125
127
128 // Make sure that config is valid
129 QString error;
130 if (!d->installation->readConfig(group, error)) {
131 Q_EMIT signalErrorCode(ErrorCode::ConfigFileError,
132 i18n("Could not initialise the installation handler for %1:\n%2\n"
133 "This is a critical error and should be reported to the application author",
135 error),
136 configfile);
137 return false;
138 }
139
141 d->cache = Cache::getCache(configFileBasename);
142 qCDebug(KNEWSTUFFCORE) << "Cache is" << d->cache << "for" << configFileBasename;
143 d->cache->readRegistry();
144
145 // Cache cleanup option, to help work around people deleting files from underneath KNewStuff (this
146 // happens a lot with e.g. wallpapers and icons)
147 if (d->installation->uncompressionSetting() == Installation::UseKPackageUncompression) {
148 d->shouldRemoveDeletedEntries = true;
149 }
150
151 d->shouldRemoveDeletedEntries = group.readEntry("RemoveDeadEntries", d->shouldRemoveDeletedEntries);
152 if (d->shouldRemoveDeletedEntries) {
153 d->cache->removeDeletedEntries();
154 }
155
156 loadProviders();
157
158 return true;
159}
160
161void EngineBase::loadProviders()
162{
163 if (d->providerFileUrl.isEmpty()) {
164 // it would be nicer to move the attica stuff into its own class
165 qCDebug(KNEWSTUFFCORE) << "Using OCS default providers";
166 delete d->atticaProviderManager;
167 d->atticaProviderManager = new Attica::ProviderManager;
168 connect(d->atticaProviderManager, &Attica::ProviderManager::providerAdded, this, &EngineBase::atticaProviderLoaded);
169 connect(d->atticaProviderManager, &Attica::ProviderManager::failedToLoad, this, &EngineBase::slotProvidersFailed);
170 d->atticaProviderManager->loadDefaultProviders();
171 } else {
172 qCDebug(KNEWSTUFFCORE) << "loading providers from " << d->providerFileUrl;
173 Q_EMIT loadingProvider();
174
175 XmlLoader *loader = s_engineProviderLoaders()->localData().value(d->providerFileUrl);
176 if (!loader) {
177 qCDebug(KNEWSTUFFCORE) << "No xml loader for this url yet, so create one and temporarily store that" << d->providerFileUrl;
178 loader = new XmlLoader(this);
179 s_engineProviderLoaders()->localData().insert(d->providerFileUrl, loader);
180 connect(loader, &XmlLoader::signalLoaded, this, [this]() {
181 s_engineProviderLoaders()->localData().remove(d->providerFileUrl);
182 });
183 connect(loader, &XmlLoader::signalFailed, this, [this]() {
184 s_engineProviderLoaders()->localData().remove(d->providerFileUrl);
185 });
186 connect(loader, &XmlLoader::signalHttpError, this, [this](int status, QList<QNetworkReply::RawHeaderPair> rawHeaders) {
187 if (status == 503) { // Temporarily Unavailable
189 static const QByteArray retryAfterKey{"Retry-After"};
190 for (const QNetworkReply::RawHeaderPair &headerPair : rawHeaders) {
191 if (headerPair.first == retryAfterKey) {
192 // Retry-After is not a known header, so we need to do a bit of running around to make that work
193 // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that
194 // So, simple workaround, just pass it through a dummy request and get a formatted date out (the
195 // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...)
197 dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second);
199 break;
200 }
201 }
202 QTimer::singleShot(retryAfter.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), this, &EngineBase::loadProviders);
203 // if it's a matter of a human moment's worth of seconds, just reload
204 if (retryAfter.toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch() > 2) {
205 // more than that, spit out TryAgainLaterError to let the user know what we're doing with their time
206 static const KFormat formatter;
207 Q_EMIT signalErrorCode(KNSCore::ErrorCode::TryAgainLaterError,
208 i18n("The service is currently undergoing maintenance and is expected to be back in %1.",
209 formatter.formatSpelloutDuration(retryAfter.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch())),
210 {retryAfter});
211 }
212 }
213 });
214 loader->load(d->providerFileUrl);
215 }
216 connect(loader, &XmlLoader::signalLoaded, this, &EngineBase::slotProviderFileLoaded);
217 connect(loader, &XmlLoader::signalFailed, this, &EngineBase::slotProvidersFailed);
218 }
219}
220
222{
223 return d->name;
224}
225
227{
228 return d->categories;
229}
230
232{
233 return d->categoriesMetadata;
234}
235
236QList<Provider::SearchPreset> EngineBase::searchPresets()
237{
238 return d->searchPresets;
239}
240
242{
243 return d->useLabel;
244}
245
247{
248 return d->uploadEnabled;
249}
250
252{
253 qCDebug(KNEWSTUFFCORE) << "Engine addProvider called with provider with id " << provider->id();
254 d->providers.insert(provider->id(), provider);
255 provider->setTagFilter(d->tagFilter);
256 provider->setDownloadTagFilter(d->downloadTagFilter);
257 connect(provider.data(), &Provider::providerInitialized, this, &EngineBase::providerInitialized);
258
259 connect(provider.data(), &Provider::signalError, this, [this, provider](const QString &msg) {
260 Q_EMIT signalErrorCode(ErrorCode::ProviderError, msg, d->providerFileUrl);
261 });
262 connect(provider.data(), &Provider::signalErrorCode, this, &EngineBase::signalErrorCode);
263 connect(provider.data(), &Provider::signalInformation, this, &EngineBase::signalMessage);
266}
267
268void EngineBase::providerInitialized(Provider *p)
269{
270 qCDebug(KNEWSTUFFCORE) << "providerInitialized" << p->name();
271 p->setCachedEntries(d->cache->registryForProvider(p->id()));
272
273 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
274 if (!p->isInitialized()) {
275 return;
276 }
277 }
278 Q_EMIT signalProvidersLoaded();
279}
280
281void EngineBase::slotProvidersFailed()
282{
283 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError,
284 i18n("Loading of providers from file: %1 failed", d->providerFileUrl.toString()),
285 d->providerFileUrl);
286}
287
288void EngineBase::slotProviderFileLoaded(const QDomDocument &doc)
289{
290 qCDebug(KNEWSTUFFCORE) << "slotProvidersLoaded";
291
292 bool isAtticaProviderFile = false;
293
294 // get each provider element, and create a provider object from it
295 QDomElement providers = doc.documentElement();
296
297 if (providers.tagName() == QLatin1String("providers")) {
299 } else if (providers.tagName() != QLatin1String("ghnsproviders") && providers.tagName() != QLatin1String("knewstuffproviders")) {
300 qWarning() << "No document in providers.xml.";
301 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError,
302 i18n("Could not load get hot new stuff providers from file: %1", d->providerFileUrl.toString()),
303 d->providerFileUrl);
304 return;
305 }
306
307 QDomElement n = providers.firstChildElement(QStringLiteral("provider"));
308 while (!n.isNull()) {
309 qCDebug(KNEWSTUFFCORE) << "Provider attributes: " << n.attribute(QStringLiteral("type"));
310
312 if (isAtticaProviderFile || n.attribute(QStringLiteral("type")).toLower() == QLatin1String("rest")) {
313 provider.reset(new AtticaProvider(d->categories, {}));
314 connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList<Provider::CategoryMetadata> &categories) {
315 d->categoriesMetadata = categories;
316 Q_EMIT signalCategoriesMetadataLoded(categories);
317 });
318#ifdef SYNDICATION_FOUND
319 } else if (n.attribute(QStringLiteral("type")).toLower() == QLatin1String("opds")) {
322 d->searchPresets = presets;
323 Q_EMIT signalSearchPresetsLoaded(presets);
324 });
325#endif
326 } else {
327 provider.reset(new StaticXmlProvider);
328 }
329
330 if (provider->setProviderXML(n)) {
332 } else {
333 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError, i18n("Error initializing provider."), d->providerFileUrl);
334 }
335 n = n.nextSiblingElement();
336 }
337 Q_EMIT loadingProvider();
338}
339
340void EngineBase::atticaProviderLoaded(const Attica::Provider &atticaProvider)
341{
342 qCDebug(KNEWSTUFFCORE) << "atticaProviderLoaded called";
343 if (!atticaProvider.hasContentService()) {
344 qCDebug(KNEWSTUFFCORE) << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content";
345 return;
346 }
348 connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList<Provider::CategoryMetadata> &categories) {
349 d->categoriesMetadata = categories;
350 Q_EMIT signalCategoriesMetadataLoded(categories);
351 });
353}
354
356{
357 return d->cache;
358}
359
361{
362 d->tagFilter = filter;
363 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
364 p->setTagFilter(d->tagFilter);
365 }
366}
367
369{
370 return d->tagFilter;
371}
372
374{
375 d->tagFilter << filter;
376 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
377 p->setTagFilter(d->tagFilter);
378 }
379}
380
382{
383 d->downloadTagFilter = filter;
384 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
385 p->setDownloadTagFilter(d->downloadTagFilter);
386 }
387}
388
390{
391 return d->downloadTagFilter;
392}
393
395{
396 d->downloadTagFilter << filter;
397 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
398 p->setDownloadTagFilter(d->downloadTagFilter);
399 }
400}
401
403{
405 ret.reserve(d->providers.size());
406 for (const auto &p : std::as_const(d->providers)) {
407 const auto atticaProvider = p.dynamicCast<AtticaProvider>();
408 if (atticaProvider) {
409 ret += atticaProvider->provider();
410 }
411 }
412 return ret;
413}
414
416{
417 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
418 return p->userCanVote();
419}
420
421void EngineBase::vote(const Entry &entry, uint rating)
422{
423 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
424 p->vote(entry, rating);
425}
426
428{
429 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
430 return p->userCanBecomeFan();
431}
432
433void EngineBase::becomeFan(const Entry &entry)
434{
435 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
436 p->becomeFan(entry);
437}
438
440{
441 return d->providers.value(providerId);
442}
443
445{
446 if (d->providers.count() > 0) {
447 return d->providers.constBegin().value();
448 }
449 return nullptr;
450}
451
453{
454 return d->providers.keys();
455}
456
458{
459 return !d->adoptionCommand.isEmpty();
460}
461
462void EngineBase::updateStatus()
463{
464}
465
466Installation *EngineBase::installation() const
467{
468 return d->installation;
469}
470
472{
473 return new ResultsStream(request, this);
474}
475
477{
478 return d->contentWarningType;
479}
480
481QList<QSharedPointer<Provider>> EngineBase::providers() const
482{
483 return d->providers.values();
484}
485
486#include "moc_enginebase.cpp"
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool exists() const
bool uploadEnabled
Whether or not the configuration says that the providers are expected to support uploading.
Definition enginebase.h:67
ContentWarningType
The ContentWarningType enum.
Definition enginebase.h:348
void signalErrorCode(KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata)
Fires in the case of any critical or serious errors, such as network or API problems.
void addTagFilter(const QString &filter)
Add a single filter entry to the entry tag filter.
bool userCanBecomeFan(const Entry &entry)
Whether or not the user is allowed to become a fan of a particular entry.
QStringList categories() const
The list of the server-side names of the categories handled by this engine instance.
virtual void addProvider(QSharedPointer< KNSCore::Provider > provider)
Add a provider and connect it to the right slots.
void becomeFan(const Entry &entry)
This will mark the user who is currently authenticated as a fan of the entry passed to the function.
bool hasAdoptionCommand() const
Whether or not an adoption command exists for this engine.
QSharedPointer< Cache > cache() const
Get the entries cache for this engine (note that it may be null if the engine is not yet initialized)...
void setTagFilter(const QStringList &filter)
Set a filter for results, which filters out all entries which do not match the filter,...
QStringList tagFilter() const
Gets the current tag filter list.
QString name() const
The name as defined by the knsrc file.
Q_SIGNAL void contentWarningTypeChanged()
Emitted after the initial config load.
QSharedPointer< Provider > provider(const QString &providerId) const
The Provider instance with the passed ID.
Q_SIGNAL void uploadEnabledChanged()
Fired when the uploadEnabled property changes.
Q_SIGNAL void useLabelChanged()
Signal gets emitted when the useLabel property changes.
bool userCanVote(const Entry &entry)
Whether or not a user is able to vote on the passed entry.
static QStringList availableConfigFiles()
List of all available config files.
QStringList downloadTagFilter() const
Gets the current downloadlink tag filter list.
void providersChanged()
Fired whenever the list of providers changes.
QString useLabel
Text that should be displayed for the adoption button, this defaults to "Use".
Definition enginebase.h:59
virtual bool init(const QString &configfile)
Initializes the engine.
void vote(const Entry &entry, uint rating)
Cast a vote on the passed entry.
QList< Attica::Provider * > atticaProviders() const
QList< Provider::CategoryMetadata > categoriesMetadata()
The list of metadata for the categories handled by this engine instance.
ResultsStream * search(const KNSCore::Provider::SearchRequest &request)
Returns a stream object that will fulfill the request.
void addDownloadTagFilter(const QString &filter)
Add a single filter entry to the download tag filter.
QSharedPointer< Provider > defaultProvider() const
Return the first provider in the providers list (usually the default provider)
QStringList providerIDs
Definition enginebase.h:72
ContentWarningType contentWarningType
Definition enginebase.h:77
void setDownloadTagFilter(const QStringList &filter)
Sets a filter to be applied to the downloads for an entry.
void signalMessage(const QString &message)
Indicates a message to be added to the ui's log, or sent to a messagebox.
KNewStuff data entry container.
Definition entry.h:48
KNewStuff Base Provider class.
Definition provider.h:41
virtual QString id() const =0
A unique Id for this provider (the url in most cases)
void basicsLoaded()
Fired when the provider's basic information has been fetched and updated.
void setTagFilter(const QStringList &tagFilter)
Set the tag filter used for entries by this provider.
Definition provider.cpp:84
virtual QString name() const
Retrieves the common name of the provider.
Definition provider.cpp:74
void searchPresetsLoaded(const QList< KNSCore::Provider::SearchPreset > &presets)
Fires when the provider has loaded search presets.
void setDownloadTagFilter(const QStringList &downloadTagFilter)
Set the tag filter used for download items by this provider.
Definition provider.cpp:94
The ResultsStream is returned by EngineBase::search.
Q_SCRIPTABLE CaptureState status()
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QStringList findAllUniqueFiles(const QStringList &dirs, const QStringList &nameFilters={})
QCA_EXPORT ProviderList providers()
qint64 currentMSecsSinceEpoch()
qint64 currentSecsSinceEpoch()
QDomElement documentElement() const const
QString attribute(const QString &name, const QString &defValue) const const
bool isNull() const const
QDomElement nextSiblingElement(const QString &tagName, const QString &namespaceURI) const const
QString completeBaseName() const const
bool exists() const const
typedef RawHeaderPair
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T * data() const const
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList standardLocations(StandardLocation type)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromLocalFile(const QString &localFile)
used to keep track of a search
Definition provider.h:70
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 24 2024 11:54:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.