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 // Make sure that config is valid
118 QString error;
119 if (!d->installation->readConfig(group, error)) {
120 Q_EMIT signalErrorCode(ErrorCode::ConfigFileError,
121 i18n("Could not initialise the installation handler for %1:\n%2\n"
122 "This is a critical error and should be reported to the application author",
124 error),
125 configfile);
126 return false;
127 }
128
130 d->cache = Cache::getCache(configFileBasename);
131 qCDebug(KNEWSTUFFCORE) << "Cache is" << d->cache << "for" << configFileBasename;
132 d->cache->readRegistry();
133
134 // Cache cleanup option, to help work around people deleting files from underneath KNewStuff (this
135 // happens a lot with e.g. wallpapers and icons)
136 if (d->installation->uncompressionSetting() == Installation::UseKPackageUncompression) {
137 d->shouldRemoveDeletedEntries = true;
138 }
139
140 d->shouldRemoveDeletedEntries = group.readEntry("RemoveDeadEntries", d->shouldRemoveDeletedEntries);
141 if (d->shouldRemoveDeletedEntries) {
142 d->cache->removeDeletedEntries();
143 }
144
145 loadProviders();
146
147 return true;
148}
149
150void EngineBase::loadProviders()
151{
152 if (d->providerFileUrl.isEmpty()) {
153 // it would be nicer to move the attica stuff into its own class
154 qCDebug(KNEWSTUFFCORE) << "Using OCS default providers";
155 delete d->atticaProviderManager;
156 d->atticaProviderManager = new Attica::ProviderManager;
157 connect(d->atticaProviderManager, &Attica::ProviderManager::providerAdded, this, &EngineBase::atticaProviderLoaded);
158 connect(d->atticaProviderManager, &Attica::ProviderManager::failedToLoad, this, &EngineBase::slotProvidersFailed);
159 d->atticaProviderManager->loadDefaultProviders();
160 } else {
161 qCDebug(KNEWSTUFFCORE) << "loading providers from " << d->providerFileUrl;
162 Q_EMIT loadingProvider();
163
164 XmlLoader *loader = s_engineProviderLoaders()->localData().value(d->providerFileUrl);
165 if (!loader) {
166 qCDebug(KNEWSTUFFCORE) << "No xml loader for this url yet, so create one and temporarily store that" << d->providerFileUrl;
167 loader = new XmlLoader(this);
168 s_engineProviderLoaders()->localData().insert(d->providerFileUrl, loader);
169 connect(loader, &XmlLoader::signalLoaded, this, [this]() {
170 s_engineProviderLoaders()->localData().remove(d->providerFileUrl);
171 });
172 connect(loader, &XmlLoader::signalFailed, this, [this]() {
173 s_engineProviderLoaders()->localData().remove(d->providerFileUrl);
174 });
175 connect(loader, &XmlLoader::signalHttpError, this, [this](int status, QList<QNetworkReply::RawHeaderPair> rawHeaders) {
176 if (status == 503) { // Temporarily Unavailable
178 static const QByteArray retryAfterKey{"Retry-After"};
179 for (const QNetworkReply::RawHeaderPair &headerPair : rawHeaders) {
180 if (headerPair.first == retryAfterKey) {
181 // Retry-After is not a known header, so we need to do a bit of running around to make that work
182 // Also, the fromHttpDate function is in the private qnetworkrequest header, so we can't use that
183 // So, simple workaround, just pass it through a dummy request and get a formatted date out (the
184 // cost is sufficiently low here, given we've just done a bunch of i/o heavy things, so...)
186 dummyRequest.setRawHeader(QByteArray{"Last-Modified"}, headerPair.second);
188 break;
189 }
190 }
191 QTimer::singleShot(retryAfter.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch(), this, &EngineBase::loadProviders);
192 // if it's a matter of a human moment's worth of seconds, just reload
193 if (retryAfter.toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch() > 2) {
194 // more than that, spit out TryAgainLaterError to let the user know what we're doing with their time
195 static const KFormat formatter;
196 Q_EMIT signalErrorCode(KNSCore::ErrorCode::TryAgainLaterError,
197 i18n("The service is currently undergoing maintenance and is expected to be back in %1.",
198 formatter.formatSpelloutDuration(retryAfter.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch())),
199 {retryAfter});
200 }
201 }
202 });
203 loader->load(d->providerFileUrl);
204 }
205 connect(loader, &XmlLoader::signalLoaded, this, &EngineBase::slotProviderFileLoaded);
206 connect(loader, &XmlLoader::signalFailed, this, &EngineBase::slotProvidersFailed);
207 }
208}
209
211{
212 return d->name;
213}
214
216{
217 return d->categories;
218}
219
221{
222 return d->categoriesMetadata;
223}
224
225QList<Provider::SearchPreset> EngineBase::searchPresets()
226{
227 return d->searchPresets;
228}
229
231{
232 return d->useLabel;
233}
234
236{
237 return d->uploadEnabled;
238}
239
241{
242 qCDebug(KNEWSTUFFCORE) << "Engine addProvider called with provider with id " << provider->id();
243 d->providers.insert(provider->id(), provider);
244 provider->setTagFilter(d->tagFilter);
245 provider->setDownloadTagFilter(d->downloadTagFilter);
246 connect(provider.data(), &Provider::providerInitialized, this, &EngineBase::providerInitialized);
247
248 connect(provider.data(), &Provider::signalError, this, [this, provider](const QString &msg) {
249 Q_EMIT signalErrorCode(ErrorCode::ProviderError, msg, d->providerFileUrl);
250 });
251 connect(provider.data(), &Provider::signalErrorCode, this, &EngineBase::signalErrorCode);
252 connect(provider.data(), &Provider::signalInformation, this, &EngineBase::signalMessage);
255}
256
257void EngineBase::providerInitialized(Provider *p)
258{
259 qCDebug(KNEWSTUFFCORE) << "providerInitialized" << p->name();
260 p->setCachedEntries(d->cache->registryForProvider(p->id()));
261
262 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
263 if (!p->isInitialized()) {
264 return;
265 }
266 }
267 Q_EMIT signalProvidersLoaded();
268}
269
270void EngineBase::slotProvidersFailed()
271{
272 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError,
273 i18n("Loading of providers from file: %1 failed", d->providerFileUrl.toString()),
274 d->providerFileUrl);
275}
276
277void EngineBase::slotProviderFileLoaded(const QDomDocument &doc)
278{
279 qCDebug(KNEWSTUFFCORE) << "slotProvidersLoaded";
280
281 bool isAtticaProviderFile = false;
282
283 // get each provider element, and create a provider object from it
284 QDomElement providers = doc.documentElement();
285
286 if (providers.tagName() == QLatin1String("providers")) {
288 } else if (providers.tagName() != QLatin1String("ghnsproviders") && providers.tagName() != QLatin1String("knewstuffproviders")) {
289 qWarning() << "No document in providers.xml.";
290 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError,
291 i18n("Could not load get hot new stuff providers from file: %1", d->providerFileUrl.toString()),
292 d->providerFileUrl);
293 return;
294 }
295
296 QDomElement n = providers.firstChildElement(QStringLiteral("provider"));
297 while (!n.isNull()) {
298 qCDebug(KNEWSTUFFCORE) << "Provider attributes: " << n.attribute(QStringLiteral("type"));
299
301 if (isAtticaProviderFile || n.attribute(QStringLiteral("type")).toLower() == QLatin1String("rest")) {
302 provider.reset(new AtticaProvider(d->categories, {}));
303 connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList<Provider::CategoryMetadata> &categories) {
304 d->categoriesMetadata = categories;
305 Q_EMIT signalCategoriesMetadataLoded(categories);
306 });
307#ifdef SYNDICATION_FOUND
308 } else if (n.attribute(QStringLiteral("type")).toLower() == QLatin1String("opds")) {
311 d->searchPresets = presets;
312 Q_EMIT signalSearchPresetsLoaded(presets);
313 });
314#endif
315 } else {
316 provider.reset(new StaticXmlProvider);
317 }
318
319 if (provider->setProviderXML(n)) {
321 } else {
322 Q_EMIT signalErrorCode(KNSCore::ErrorCode::ProviderError, i18n("Error initializing provider."), d->providerFileUrl);
323 }
324 n = n.nextSiblingElement();
325 }
326 Q_EMIT loadingProvider();
327}
328
329void EngineBase::atticaProviderLoaded(const Attica::Provider &atticaProvider)
330{
331 qCDebug(KNEWSTUFFCORE) << "atticaProviderLoaded called";
332 if (!atticaProvider.hasContentService()) {
333 qCDebug(KNEWSTUFFCORE) << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content";
334 return;
335 }
337 connect(provider.data(), &Provider::categoriesMetadataLoded, this, [this](const QList<Provider::CategoryMetadata> &categories) {
338 d->categoriesMetadata = categories;
339 Q_EMIT signalCategoriesMetadataLoded(categories);
340 });
342}
343
345{
346 return d->cache;
347}
348
350{
351 d->tagFilter = filter;
352 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
353 p->setTagFilter(d->tagFilter);
354 }
355}
356
358{
359 return d->tagFilter;
360}
361
363{
364 d->tagFilter << filter;
365 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
366 p->setTagFilter(d->tagFilter);
367 }
368}
369
371{
372 d->downloadTagFilter = filter;
373 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
374 p->setDownloadTagFilter(d->downloadTagFilter);
375 }
376}
377
379{
380 return d->downloadTagFilter;
381}
382
384{
385 d->downloadTagFilter << filter;
386 for (const QSharedPointer<KNSCore::Provider> &p : std::as_const(d->providers)) {
387 p->setDownloadTagFilter(d->downloadTagFilter);
388 }
389}
390
392{
394 ret.reserve(d->providers.size());
395 for (const auto &p : std::as_const(d->providers)) {
396 const auto atticaProvider = p.dynamicCast<AtticaProvider>();
397 if (atticaProvider) {
398 ret += atticaProvider->provider();
399 }
400 }
401 return ret;
402}
403
405{
406 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
407 return p->userCanVote();
408}
409
410void EngineBase::vote(const Entry &entry, uint rating)
411{
412 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
413 p->vote(entry, rating);
414}
415
417{
418 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
419 return p->userCanBecomeFan();
420}
421
422void EngineBase::becomeFan(const Entry &entry)
423{
424 QSharedPointer<Provider> p = d->providers.value(entry.providerId());
425 p->becomeFan(entry);
426}
427
429{
430 return d->providers.value(providerId);
431}
432
434{
435 if (d->providers.count() > 0) {
436 return d->providers.constBegin().value();
437 }
438 return nullptr;
439}
440
442{
443 return d->providers.keys();
444}
445
447{
448 return !d->adoptionCommand.isEmpty();
449}
450
451void EngineBase::updateStatus()
452{
453}
454
455Installation *EngineBase::installation() const
456{
457 return d->installation;
458}
459
461{
462 return new ResultsStream(request, this);
463}
464
465QList<QSharedPointer<Provider>> EngineBase::providers() const
466{
467 return d->providers.values();
468}
469
470#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:66
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.
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:58
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:71
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 Tue Mar 26 2024 11:21:35 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.