KNewStuff

engine.cpp
1 /*
2  knewstuff3/engine.cpp
3  SPDX-FileCopyrightText: 2007 Josef Spillner <[email protected]>
4  SPDX-FileCopyrightText: 2007-2010 Frederik Gladhorn <[email protected]>
5  SPDX-FileCopyrightText: 2009 Jeremy Whiting <[email protected]>
6  SPDX-FileCopyrightText: 2010 Matthias Fuchs <[email protected]>
7 
8  SPDX-License-Identifier: LGPL-2.1-or-later
9 */
10 
11 #include "engine.h"
12 
13 #include "../entry.h"
14 #include "commentsmodel.h"
15 #include "installation.h"
16 #include "question.h"
17 #include "xmlloader.h"
18 #include "imageloader_p.h"
19 
20 #include <memory>
21 #include <KConfig>
22 #include <KConfigGroup>
23 #include <knewstuffcore_debug.h>
24 #include <KLocalizedString>
25 #include <QDesktopServices>
26 
27 #include <QTimer>
28 #include <QDir>
29 #include <qdom.h>
30 #include <QUrlQuery>
31 #include <QThreadStorage>
32 
33 #if defined(Q_OS_WIN)
34 #include <windows.h>
35 #include <shlobj.h>
36 #endif
37 
38 // libattica
39 #include <attica/providermanager.h>
40 #include <qstandardpaths.h>
41 
42 // own
43 #include "../attica/atticaprovider_p.h"
44 #include "cache.h"
45 #include "../staticxml/staticxmlprovider_p.h"
46 
47 using namespace KNSCore;
48 
50 Q_GLOBAL_STATIC(QThreadStorage<EngineProviderLoaderHash>, s_engineProviderLoaders)
51 
52 class EnginePrivate {
53 public:
54  QList<Provider::CategoryMetadata> categoriesMetadata;
55  Attica::ProviderManager *m_atticaProviderManager = nullptr;
56  QStringList tagFilter;
57  QStringList downloadTagFilter;
58  bool configLocationFallback = true;
59  QString name;
61 
62  // Used for updating purposes - we ought to be saving this information, but we also have to deal with old stuff, and so... this will have to do for now, and so
63  // TODO KF6: Installed state needs to move onto a per-downloadlink basis rather than per-entry
65  QMap<EntryInternal, QString> payloadToIdentify;
66  Engine::BusyState busyState;
67  QString busyMessage;
68 };
69 
71  : QObject(parent)
72  , m_installation(new Installation)
73  , m_cache()
74  , m_searchTimer(new QTimer)
75  , d(new EnginePrivate)
76  , m_currentPage(-1)
77  , m_pageSize(20)
78  , m_numDataJobs(0)
79  , m_numPictureJobs(0)
80  , m_numInstallJobs(0)
81  , m_initialized(false)
82 {
83  m_searchTimer->setSingleShot(true);
84  m_searchTimer->setInterval(1000);
85  connect(m_searchTimer, &QTimer::timeout, this, &Engine::slotSearchTimerExpired);
86  connect(m_installation, &Installation::signalInstallationFinished, this, &Engine::slotInstallationFinished);
87  connect(m_installation, &Installation::signalInstallationFailed, this, &Engine::slotInstallationFailed);
88  connect(m_installation, &Installation::signalInstallationError, this, [this](const QString &message){ emit signalErrorCode(ErrorCode::InstallationError, i18n("An error occurred during the installation process:\n%1", message), QVariant()); });
89  // Pass along old error signal through new signal for locations which have not been updated yet
90  connect(this, &Engine::signalError, this, [this](const QString& message){ emit signalErrorCode(ErrorCode::UnknownError, message, QVariant()); });
91 }
92 
94 {
95  if (m_cache) {
96  m_cache->writeRegistry();
97  }
98  delete d->m_atticaProviderManager;
99  delete m_searchTimer;
100  delete m_installation;
101  delete d;
102 }
103 
104 bool Engine::init(const QString &configfile)
105 {
106  qCDebug(KNEWSTUFFCORE) << "Initializing KNSCore::Engine from '" << configfile << "'";
107 
108  setBusy(BusyOperation::Initializing, i18n("Initializing"));
109 
113  bool isRelativeConfig = QFileInfo(configfile).isRelative();
114  QString actualConfig;
115  if (isRelativeConfig) {
116  // Don't do the expensive search unless the config is relative
117  actualConfig = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("knsrcfiles/%1").arg(configfile));
118  }
119  QString configFileName{configfile};
120  if (isRelativeConfig && d->configLocationFallback && actualConfig.isEmpty()) {
121  conf.reset(new KConfig(configfile));
122  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";
123  } else if (isRelativeConfig) {
124  configFileName = QFileInfo(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("knsrcfiles/%1").arg(configfile))).baseName();
125  conf.reset(new KConfig(QString::fromLatin1("knsrcfiles/%1").arg(configfile), KConfig::FullConfig, QStandardPaths::GenericDataLocation));
126  } else {
127  configFileName = QFileInfo(configfile).baseName();
128  conf.reset(new KConfig(configfile));
129  }
130 
131  if (conf->accessMode() == KConfig::NoAccess) {
132  emit signalErrorCode(KNSCore::ConfigFileError, i18n("Configuration file exists, but cannot be opened: \"%1\"", configfile), configfile);
133  qCCritical(KNEWSTUFFCORE) << "The knsrc file '" << configfile << "' was found but could not be opened.";
134  return false;
135  }
136 
137  KConfigGroup group;
138  if (conf->hasGroup("KNewStuff3")) {
139  qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff3 config: " << configfile;
140  group = conf->group("KNewStuff3");
141  } else if (conf->hasGroup("KNewStuff2")) {
142  qCDebug(KNEWSTUFFCORE) << "Loading KNewStuff2 config: " << configfile;
143  group = conf->group("KNewStuff2");
144  } else {
145  emit signalErrorCode(KNSCore::ConfigFileError, i18n("Configuration file is invalid: \"%1\"", configfile), configfile);
146  qCCritical(KNEWSTUFFCORE) << configfile << " doesn't contain a KNewStuff3 section.";
147  return false;
148  }
149 
150  d->name = group.readEntry("Name", QString());
151  m_categories = group.readEntry("Categories", QStringList());
152  m_adoptionCommand = group.readEntry("AdoptionCommand", QString());
153 
154  qCDebug(KNEWSTUFFCORE) << "Categories: " << m_categories;
155  m_providerFileUrl = group.readEntry("ProvidersUrl", QString());
156 
157  d->tagFilter = group.readEntry("TagFilter", QStringList());
158  if (d->tagFilter.isEmpty()) {
159  d->tagFilter.append(QStringLiteral("ghns_excluded!=1"));
160  }
161  d->downloadTagFilter = group.readEntry("DownloadTagFilter", QStringList());
162 
163  // Make sure that config is valid
164  if (!m_installation->readConfig(group)) {
165  Q_EMIT signalError(i18n("Could not initialise the installation handler for %1\n"
166  "This is a critical error and should be reported to the application author", configfile));
167  return false;
168  }
169 
170  connect(m_installation, &Installation::signalEntryChanged, this, &Engine::slotEntryChanged);
171 
172  m_cache = Cache::getCache(configFileName);
173  qCDebug(KNEWSTUFFCORE) << "Cache is" << m_cache << "for" << configFileName;
174  connect(this, &Engine::signalEntryChanged, m_cache.data(), &Cache::registerChangedEntry);
175  m_cache->readRegistry();
176 
177  // Cache cleanup option, to help work around people deleting files from underneath KNewStuff (this
178  // happens a lot with e.g. wallpapers and icons)
179  bool shouldRemoveDeletedEntries{false};
181  shouldRemoveDeletedEntries = true;
182  }
183  shouldRemoveDeletedEntries = group.readEntry("RemoveDeadEntries", shouldRemoveDeletedEntries);
184  if (shouldRemoveDeletedEntries) {
185  m_cache->removeDeletedEntries();
186  }
187 
188  m_initialized = true;
189 
190  // load the providers
191  loadProviders();
192 
193  return true;
194 }
195 
197 {
198  return d->name;
199 }
200 
202 {
203  return m_categories;
204 }
205 
207 {
208  return m_currentRequest.categories;
209 }
210 
212 {
213  return d->categoriesMetadata;
214 }
215 
216 void Engine::loadProviders()
217 {
218  if (m_providerFileUrl.isEmpty()) {
219  // it would be nicer to move the attica stuff into its own class
220  qCDebug(KNEWSTUFFCORE) << "Using OCS default providers";
221  delete d->m_atticaProviderManager;
222  d->m_atticaProviderManager = new Attica::ProviderManager;
223  connect(d->m_atticaProviderManager, &Attica::ProviderManager::providerAdded, this, &Engine::atticaProviderLoaded);
224  connect(d->m_atticaProviderManager, &Attica::ProviderManager::failedToLoad, this, &Engine::slotProvidersFailed);
225  d->m_atticaProviderManager->loadDefaultProviders();
226  } else {
227  qCDebug(KNEWSTUFFCORE) << "loading providers from " << m_providerFileUrl;
228  setBusy(BusyOperation::LoadingData, i18n("Loading provider information"));
229 
230  XmlLoader *loader = s_engineProviderLoaders()->localData().value(m_providerFileUrl);
231  if (!loader) {
232  qCDebug(KNEWSTUFFCORE) << "No xml loader for this url yet, so create one and temporarily store that" << m_providerFileUrl;
233  loader = new XmlLoader(this);
234  s_engineProviderLoaders()->localData().insert(m_providerFileUrl, loader);
235  connect(loader, &XmlLoader::signalLoaded, this, [this](){ s_engineProviderLoaders()->localData().remove(m_providerFileUrl); });
236  connect(loader, &XmlLoader::signalFailed, this, [this](){ s_engineProviderLoaders()->localData().remove(m_providerFileUrl); });
237  loader->load(QUrl(m_providerFileUrl));
238  }
239  connect(loader, &XmlLoader::signalLoaded, this, &Engine::slotProviderFileLoaded);
240  connect(loader, &XmlLoader::signalFailed, this, &Engine::slotProvidersFailed);
241  }
242 }
243 
244 void Engine::slotProviderFileLoaded(const QDomDocument &doc)
245 {
246  qCDebug(KNEWSTUFFCORE) << "slotProvidersLoaded";
247 
248  bool isAtticaProviderFile = false;
249 
250  // get each provider element, and create a provider object from it
252 
253  if (providers.tagName() == QLatin1String("providers")) {
254  isAtticaProviderFile = true;
255  } else if (providers.tagName() != QLatin1String("ghnsproviders") && providers.tagName() != QLatin1String("knewstuffproviders")) {
256  qWarning() << "No document in providers.xml.";
257  emit signalErrorCode(KNSCore::ProviderError, i18n("Could not load get hot new stuff providers from file: %1", m_providerFileUrl), m_providerFileUrl);
258  return;
259  }
260 
261  QDomElement n = providers.firstChildElement(QStringLiteral("provider"));
262  while (!n.isNull()) {
263  qCDebug(KNEWSTUFFCORE) << "Provider attributes: " << n.attribute(QStringLiteral("type"));
264 
266  if (isAtticaProviderFile || n.attribute(QStringLiteral("type")).toLower() == QLatin1String("rest")) {
267  provider.reset(new AtticaProvider(m_categories, d->name));
268  connect(provider.data(), &Provider::categoriesMetadataLoded,
269  this, [this](const QList<Provider::CategoryMetadata> &categories){
270  d->categoriesMetadata = categories;
271  emit signalCategoriesMetadataLoded(categories);
272  });
273  } else {
274  provider.reset(new StaticXmlProvider);
275  }
276 
277  if (provider->setProviderXML(n)) {
278  addProvider(provider);
279  } else {
280  emit signalErrorCode(KNSCore::ProviderError, i18n("Error initializing provider."), m_providerFileUrl);
281  }
282  n = n.nextSiblingElement();
283  }
284  setBusy(BusyOperation::LoadingData, i18n("Loading data"));
285 }
286 
287 void Engine::atticaProviderLoaded(const Attica::Provider &atticaProvider)
288 {
289  qCDebug(KNEWSTUFFCORE) << "atticaProviderLoaded called";
290  if (!atticaProvider.hasContentService()) {
291  qCDebug(KNEWSTUFFCORE) << "Found provider: " << atticaProvider.baseUrl() << " but it does not support content";
292  return;
293  }
295  QSharedPointer<KNSCore::Provider> (new AtticaProvider(atticaProvider, m_categories, d->name));
296  connect(provider.data(), &Provider::categoriesMetadataLoded,
297  this, [this](const QList<Provider::CategoryMetadata> &categories){
298  d->categoriesMetadata = categories;
299  emit signalCategoriesMetadataLoded(categories);
300  });
301  addProvider(provider);
302 }
303 
304 void Engine::addProvider(QSharedPointer<KNSCore::Provider> provider)
305 {
306  qCDebug(KNEWSTUFFCORE) << "Engine addProvider called with provider with id " << provider->id();
307  m_providers.insert(provider->id(), provider);
308  provider->setTagFilter(d->tagFilter);
309  provider->setDownloadTagFilter(d->downloadTagFilter);
310  connect(provider.data(), &Provider::providerInitialized, this, &Engine::providerInitialized);
311  connect(provider.data(), &Provider::loadingFinished, this, &Engine::slotEntriesLoaded);
312  connect(provider.data(), &Provider::entryDetailsLoaded, this, &Engine::slotEntryDetailsLoaded);
313  connect(provider.data(), &Provider::payloadLinkLoaded, this, &Engine::downloadLinkLoaded);
314  connect(provider.data(), &Provider::signalError, this, &Engine::signalError);
315  connect(provider.data(), &Provider::signalErrorCode, this, &Engine::signalErrorCode);
316  connect(provider.data(), &Provider::signalInformation, this, [this](const QString &message) {
317  Q_EMIT signalMessage(message);
318  });
319 }
320 
321 void Engine::providerJobStarted(KJob *job)
322 {
323  emit jobStarted(job, i18n("Loading data from provider"));
324 }
325 
326 void Engine::slotProvidersFailed()
327 {
328  emit signalErrorCode(KNSCore::ProviderError, i18n("Loading of providers from file: %1 failed", m_providerFileUrl), m_providerFileUrl);
329 }
330 
331 void Engine::providerInitialized(Provider *p)
332 {
333  qCDebug(KNEWSTUFFCORE) << "providerInitialized" << p->name();
334  p->setCachedEntries(m_cache->registryForProvider(p->id()));
335  updateStatus();
336 
337  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
338  if (!p->isInitialized()) {
339  return;
340  }
341  }
342  emit signalProvidersLoaded();
343 }
344 
345 void Engine::slotEntriesLoaded(const KNSCore::Provider::SearchRequest &request, KNSCore::EntryInternal::List entries)
346 {
347  m_currentPage = qMax<int>(request.page, m_currentPage);
348  qCDebug(KNEWSTUFFCORE) << "loaded page " << request.page << "current page" << m_currentPage << "count:" << entries.count();
349 
350  if (request.filter == Provider::Updates) {
351  emit signalUpdateableEntriesLoaded(entries);
352  } else {
353  m_cache->insertRequest(request, entries);
354  emit signalEntriesLoaded(entries);
355  }
356 
357  --m_numDataJobs;
358  updateStatus();
359 }
360 
361 void Engine::reloadEntries()
362 {
363  emit signalResetView();
364  m_currentPage = -1;
365  m_currentRequest.pageSize = m_pageSize;
366  m_currentRequest.page = 0;
367  m_numDataJobs = 0;
368 
369  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
370  if (p->isInitialized()) {
371  if (m_currentRequest.filter == Provider::Installed) {
372  // when asking for installed entries, never use the cache
373  p->loadEntries(m_currentRequest);
374  } else {
375  // take entries from cache until there are no more
376  EntryInternal::List cache;
377  EntryInternal::List lastCache = m_cache->requestFromCache(m_currentRequest);
378  while (!lastCache.isEmpty()) {
379  qCDebug(KNEWSTUFFCORE) << "From cache";
380  cache << lastCache;
381 
382  m_currentPage = m_currentRequest.page;
383  ++m_currentRequest.page;
384  lastCache = m_cache->requestFromCache(m_currentRequest);
385  }
386 
387  // Since the cache has no more pages, reset the request's page
388  if (m_currentPage >= 0) {
389  m_currentRequest.page = m_currentPage;
390  }
391 
392  if (!cache.isEmpty()) {
393  emit signalEntriesLoaded(cache);
394  } else {
395  qCDebug(KNEWSTUFFCORE) << "From provider";
396  p->loadEntries(m_currentRequest);
397 
398  ++m_numDataJobs;
399  updateStatus();
400  }
401  }
402  }
403  }
404 }
405 
407 {
408  m_currentRequest.categories = categories;
409  reloadEntries();
410 }
411 
412 void Engine::setSortMode(Provider::SortMode mode)
413 {
414  if (m_currentRequest.sortMode != mode) {
415  m_currentRequest.page = -1;
416  }
417  m_currentRequest.sortMode = mode;
418  reloadEntries();
419 }
420 
421 Provider::SortMode KNSCore::Engine::sortMode() const
422 {
423  return m_currentRequest.sortMode;
424 }
425 
426 void KNSCore::Engine::setFilter(Provider::Filter filter)
427 {
428  if (m_currentRequest.filter != filter) {
429  m_currentRequest.page = -1;
430  }
431  m_currentRequest.filter = filter;
432  reloadEntries();
433 }
434 
435 Provider::Filter KNSCore::Engine::filter() const
436 {
437  return m_currentRequest.filter;
438 }
439 
441 {
442  m_searchTimer->stop();
443  m_currentRequest = KNSCore::Provider::SearchRequest(KNSCore::Provider::Newest, KNSCore::Provider::ExactEntryId, id);
444  m_currentRequest.pageSize = m_pageSize;
445 
446  EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest);
447  if (!cache.isEmpty()) {
448  reloadEntries();
449  } else {
450  m_searchTimer->start();
451  }
452 }
453 
454 void Engine::setSearchTerm(const QString &searchString)
455 {
456  m_searchTimer->stop();
457  m_currentRequest.searchTerm = searchString;
458  EntryInternal::List cache = m_cache->requestFromCache(m_currentRequest);
459  if (!cache.isEmpty()) {
460  reloadEntries();
461  } else {
462  m_searchTimer->start();
463  }
464 }
465 
467 {
468  return m_currentRequest.searchTerm;
469 }
470 
472 {
473  d->tagFilter = filter;
474  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
475  p->setTagFilter(d->tagFilter);
476  }
477 }
478 
480 {
481  return d->tagFilter;
482 }
483 
485 {
486  d->tagFilter << filter;
487  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
488  p->setTagFilter(d->tagFilter);
489  }
490 }
491 
493 {
494  d->downloadTagFilter = filter;
495  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
496  p->setDownloadTagFilter(d->downloadTagFilter);
497  }
498 }
499 
501 {
502  return d->downloadTagFilter;
503 }
504 
506 {
507  d->downloadTagFilter << filter;
508  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
509  p->setDownloadTagFilter(d->downloadTagFilter);
510  }
511 }
512 
513 void Engine::slotSearchTimerExpired()
514 {
515  reloadEntries();
516 }
517 
518 void Engine::requestMoreData()
519 {
520  qCDebug(KNEWSTUFFCORE) << "Get more data! current page: " << m_currentPage << " requested: " << m_currentRequest.page;
521 
522  if (m_currentPage < m_currentRequest.page) {
523  return;
524  }
525 
526  m_currentRequest.page++;
527  doRequest();
528 }
529 
530 void Engine::requestData(int page, int pageSize)
531 {
532  m_currentRequest.page = page;
533  m_currentRequest.pageSize = pageSize;
534  doRequest();
535 }
536 
537 void Engine::doRequest()
538 {
539  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
540  if (p->isInitialized()) {
541  p->loadEntries(m_currentRequest);
542  ++m_numDataJobs;
543  updateStatus();
544  }
545  }
546 }
547 
549 {
550  if (entry.status() == KNS3::Entry::Updateable) {
551  entry.setStatus(KNS3::Entry::Updating);
552  } else {
553  entry.setStatus(KNS3::Entry::Installing);
554  }
555  emit signalEntryChanged(entry);
556 
557  qCDebug(KNEWSTUFFCORE) << "Install " << entry.name()
558  << " from: " << entry.providerId();
559  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
560  if (p) {
561  // If linkId is -1, assume that it's an update and that we don't know what to update
562  if (entry.status() == KNS3::Entry::Updating && linkId == -1) {
563  if (entry.downloadLinkCount() == 1) {
564  // If there is only one downloadable item, then we can fairly safely assume that's what we're wanting
565  // to update, meaning we can bypass some of the more expensive operations in downloadLinkLoaded
566  qCDebug(KNEWSTUFFCORE) << "Just the one download link, so let's use that";
567  d->payloadToIdentify[entry] = QString{};
568  linkId = 1;
569  } else {
570  qCDebug(KNEWSTUFFCORE) << "Try and identify a download link to use from a total of" << entry.downloadLinkCount();
571  // While this seems silly, the payload gets reset when fetching the new download link information
572  d->payloadToIdentify[entry] = entry.payload();
573  // Drop a fresh list in place so we've got something to work with when we get the links
574  d->payloads[entry] = QStringList{};
575  linkId = 1;
576  }
577  } else {
578  qCDebug(KNEWSTUFFCORE) << "Link ID already known" << linkId;
579  // If there is no payload to identify, we will assume the payload is already known and just use that
580  d->payloadToIdentify[entry] = QString{};
581  }
582 
583  p->loadPayloadLink(entry, linkId);
584 
585  ++m_numInstallJobs;
586  updateStatus();
587  }
588 }
589 
590 void Engine::slotInstallationFinished()
591 {
592  --m_numInstallJobs;
593  updateStatus();
594 }
595 
596 void Engine::slotInstallationFailed(const QString &message)
597 {
598  --m_numInstallJobs;
600 }
601 
602 void Engine::slotEntryDetailsLoaded(const KNSCore::EntryInternal &entry)
603 {
604  emit signalEntryDetailsLoaded(entry);
605 }
606 
607 void Engine::downloadLinkLoaded(const KNSCore::EntryInternal &entry)
608 {
609  if (entry.status() == KNS3::Entry::Updating) {
610  if (d->payloadToIdentify.isEmpty()) {
611  // If there's nothing to identify, and we've arrived here, then we know what the payload is
612  qCDebug(KNEWSTUFFCORE) << "If there's nothing to identify, and we've arrived here, then we know what the payload is";
613  m_installation->install(entry);
614  } else if (d->payloads[entry].count() < entry.downloadLinkCount()) {
615  // We've got more to get before we can attempt to identify anything, so fetch the next one...
616  qCDebug(KNEWSTUFFCORE) << "We've got more to get before we can attempt to identify anything, so fetch the next one...";
617  QStringList payloads = d->payloads[entry];
618  payloads << entry.payload();
619  d->payloads[entry] = payloads;
620  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
621  if (p) {
622  // ok, so this should definitely always work, but... safety first, kids!
623  p->loadPayloadLink(entry, payloads.count());
624  }
625  } else {
626  // We now have all the links, so let's try and identify the correct one...
627  qCDebug(KNEWSTUFFCORE) << "We now have all the links, so let's try and identify the correct one...";
628  QString identifiedLink;
629  const QString payloadToIdentify = d->payloadToIdentify[entry];
631  const QStringList &payloads = d->payloads[entry];
632 
633  if (payloads.contains(payloadToIdentify)) {
634  // Simplest option, the link hasn't changed at all
635  qCDebug(KNEWSTUFFCORE) << "Simplest option, the link hasn't changed at all";
636  identifiedLink = payloadToIdentify;
637  } else {
638  // Next simplest option, filename is the same but in a different folder
639  qCDebug(KNEWSTUFFCORE) << "Next simplest option, filename is the same but in a different folder";
640  const QStringRef fileName = payloadToIdentify.splitRef(QChar::fromLatin1('/')).last();
641  for (const QString &payload : payloads) {
642  if (payload.endsWith(fileName)) {
643  identifiedLink = payload;
644  break;
645  }
646  }
647 
648  // Possibly the payload itself is named differently (by a CDN, for example), but the link identifier is the same...
649  qCDebug(KNEWSTUFFCORE) << "Possibly the payload itself is named differently (by a CDN, for example), but the link identifier is the same...";
650  QStringList payloadNames;
651  for (const EntryInternal::DownloadLinkInformation &downloadLink : downloadLinks) {
652  qCDebug(KNEWSTUFFCORE) << "Download link" << downloadLink.name << downloadLink.id << downloadLink.size << downloadLink.descriptionLink;
653  payloadNames << downloadLink.name;
654  if (downloadLink.name == fileName) {
655  identifiedLink = payloads[payloadNames.count() - 1];
656  qCDebug(KNEWSTUFFCORE) << "Found a suitable download link for" << fileName << "which should match" << identifiedLink;
657  }
658  }
659 
660  if (identifiedLink.isEmpty()) {
661  // Least simple option, no match - ask the user to pick (and if we still haven't got one... that's us done, no installation)
662  qCDebug(KNEWSTUFFCORE) << "Least simple option, no match - ask the user to pick (and if we still haven't got one... that's us done, no installation)";
663  auto question = std::make_unique<Question>(Question::SelectFromListQuestion);
664  question->setTitle(i18n("Pick Update Item"));
665  question->setQuestion(i18n("Please pick the item from the list below which should be used to apply this update. We were unable to identify which item to select, based on the original item, which was named %1", fileName.toString()));
666  question->setList(payloadNames);
667  if(question->ask() == Question::OKResponse) {
668  identifiedLink = payloads.value(payloadNames.indexOf(question->response()));
669  }
670  }
671  }
672  if (!identifiedLink.isEmpty()) {
673  KNSCore::EntryInternal theEntry(entry);
674  theEntry.setPayload(identifiedLink);
675  m_installation->install(theEntry);
676  } else {
677  qCWarning(KNEWSTUFFCORE) << "We failed to identify a good link for updating" << entry.name() << "and are unable to perform the update";
678  }
679  // As the serverside data may change before next time this is called, even in the same session,
680  // let's not make assumptions, and just get rid of this
681  d->payloads.remove(entry);
682  d->payloadToIdentify.remove(entry);
683  }
684  } else {
685  m_installation->install(entry);
686  }
687 }
688 
690 {
691  const KNSCore::EntryInternal::List list = m_cache->registryForProvider(entry.providerId());
692  //we have to use the cached entry here, not the entry from the provider
693  //since that does not contain the list of installed files
694  KNSCore::EntryInternal actualEntryForUninstall;
695  for (const KNSCore::EntryInternal &eInt : list) {
696  if (eInt.uniqueId() == entry.uniqueId()) {
697  actualEntryForUninstall = eInt;
698  break;
699  }
700  }
701  if (!actualEntryForUninstall.isValid()) {
702  qCDebug(KNEWSTUFFCORE) << "could not find a cached entry with following id:" << entry.uniqueId() <<
703  " -> using the non-cached version";
704  return;
705  }
706 
707  entry.setStatus(KNS3::Entry::Installing);
708  actualEntryForUninstall.setStatus(KNS3::Entry::Installing);
709  emit signalEntryChanged(entry);
710 
711  qCDebug(KNEWSTUFFCORE) << "about to uninstall entry " << entry.uniqueId();
712  m_installation->uninstall(actualEntryForUninstall);
713 
714  entry.setStatus(actualEntryForUninstall.status());
715  emit signalEntryChanged(entry);
716 }
717 
719 {
720  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
721  p->loadEntryDetails(entry);
722 }
723 
724 void Engine::loadPreview(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type)
725 {
726  qCDebug(KNEWSTUFFCORE) << "START preview: " << entry.name() << type;
727  ImageLoader *l = new ImageLoader(entry, type, this);
728  connect(l, &ImageLoader::signalPreviewLoaded, this, &Engine::slotPreviewLoaded);
729  connect(l, &ImageLoader::signalError, this, [this](const KNSCore::EntryInternal &entry,
730  EntryInternal::PreviewType type,
731  const QString &errorText) {
732  emit signalErrorCode(KNSCore::ImageError, errorText, QVariantList() << entry.name() << type);
733  qCDebug(KNEWSTUFFCORE) << "ERROR preview: " << errorText << entry.name() << type;
734  --m_numPictureJobs;
735  updateStatus();
736  });
737  l->start();
738  ++m_numPictureJobs;
739  updateStatus();
740 }
741 
742 void Engine::slotPreviewLoaded(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type)
743 {
744  qCDebug(KNEWSTUFFCORE) << "FINISH preview: " << entry.name() << type;
745  emit signalEntryPreviewLoaded(entry, type);
746  --m_numPictureJobs;
747  updateStatus();
748 }
749 
751 {
752  if (!entry.author().email().isEmpty()) {
753  // invoke mail with the address of the author
754  QUrl mailUrl;
755  mailUrl.setScheme(QStringLiteral("mailto"));
756  mailUrl.setPath(entry.author().email());
757  QUrlQuery query;
758  query.addQueryItem(QStringLiteral("subject"), i18n("Re: %1", entry.name()));
759  mailUrl.setQuery(query);
760  QDesktopServices::openUrl(mailUrl);
761  } else if (!entry.author().homepage().isEmpty()) {
763  }
764 }
765 
766 void Engine::slotEntryChanged(const KNSCore::EntryInternal &entry)
767 {
768  emit signalEntryChanged(entry);
769 }
770 
772 {
773  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
774  return p->userCanVote();
775 }
776 
777 void Engine::vote(const EntryInternal &entry, uint rating)
778 {
779  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
780  p->vote(entry, rating);
781 }
782 
784 {
785  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
786  return p->userCanBecomeFan();
787 }
788 
790 {
791  QSharedPointer<Provider> p = m_providers.value(entry.providerId());
792  p->becomeFan(entry);
793 }
794 
795 void Engine::updateStatus()
796 {
797  BusyState state;
799  if (m_numInstallJobs > 0) {
800  busyMessage = i18n("Installing");
801  state |= BusyOperation::InstallingEntry;
802  }
803  if (m_numPictureJobs > 0) {
804  busyMessage = i18np("Loading one preview", "Loading %1 previews", m_numPictureJobs);
805  state |= BusyOperation::LoadingPreview;
806  }
807  if (m_numDataJobs > 0) {
808  busyMessage = i18n("Loading data");
809  state |= BusyOperation::LoadingPreview;
810  }
811  setBusy(state, busyMessage);
812 }
813 
815 {
816  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
817  Provider::SearchRequest request(KNSCore::Provider::Newest, KNSCore::Provider::Updates);
818  p->loadEntries(request);
819  }
820 }
821 
823 {
824  for (const QSharedPointer<KNSCore::Provider> &p : qAsConst(m_providers)) {
825  Provider::SearchRequest request(KNSCore::Provider::Newest, KNSCore::Provider::Installed);
826  request.page = 0;
827  request.pageSize = m_pageSize;
828  p->loadEntries(request);
829  }
830 }
831 
836 static QDir sharedDir(QStringList dirs, QString rootPath)
837 {
838  // Ensure that rootPath definitely is a clean path with a slash at the end
839  rootPath = QDir::cleanPath(rootPath) + QStringLiteral("/");
840  qCInfo(KNEWSTUFFCORE) << Q_FUNC_INFO << dirs << rootPath;
841  while(!dirs.isEmpty()) {
842  QString thisDir(dirs.takeLast());
843  if (thisDir.endsWith(QStringLiteral("*"))) {
844  qCInfo(KNEWSTUFFCORE) << "Directory entry" << thisDir << "ends in a *, indicating this was installed from an archive - see Installation::archiveEntries";
845  thisDir.chop(1);
846  }
847 
848  const QString currentPath = QDir::cleanPath(thisDir);
849  qCInfo(KNEWSTUFFCORE) << "Current path is" << currentPath;
850  if (!currentPath.startsWith(rootPath)) {
851  qCInfo(KNEWSTUFFCORE) << "Current path" << currentPath << "does not start with" << rootPath << "and should be ignored";
852  continue;
853  }
854 
855  const QFileInfo current(currentPath);
856  qCInfo(KNEWSTUFFCORE) << "Current file info is" << current;
857  if (!current.isDir()) {
858  qCInfo(KNEWSTUFFCORE) << "Current path" << currentPath << "is not a directory, and should be ignored";
859  continue;
860  }
861 
862  const QDir dir(currentPath);
863  if (dir.path()==(rootPath+dir.dirName())) {
864  qCDebug(KNEWSTUFFCORE) << "Found directory" << dir;
865  return dir;
866  }
867  }
868  qCWarning(KNEWSTUFFCORE) << "Failed to locate any shared installed directory in" << dirs << "and this is almost certainly very bad.";
869  return {};
870 }
871 
873 {
874  auto adoption = m_adoptionCommand;
875  if(adoption.isEmpty())
876  return {};
877 
878  const QLatin1String dirReplace("%d");
879  if (adoption.contains(dirReplace)) {
880  QString installPath = sharedDir(entry.installedFiles(), m_installation->targetInstallationPath()).path();
881  adoption.replace(dirReplace, installPath);
882  }
883 
884  const QLatin1String fileReplace("%f");
885  if (adoption.contains(fileReplace)) {
886  if (entry.installedFiles().isEmpty()) {
887  qCWarning(KNEWSTUFFCORE) << "no installed files to adopt";
888  } else if (entry.installedFiles().count() != 1) {
889  qCWarning(KNEWSTUFFCORE) << "can only adopt one file, will be using the first" << entry.installedFiles().at(0);
890  }
891 
892  adoption.replace(fileReplace, entry.installedFiles().at(0));
893  }
894  return adoption;
895 }
896 
898 {
899  return !m_adoptionCommand.isEmpty();
900 }
901 
903 {
904  m_pageSize = pageSize;
905 }
906 
908 {
909  QStringList ret;
910  if(includeFallbackLocations) {
912  }
914  for( const QString& path : paths) {
915  ret << QString::fromLocal8Bit("%1/knsrcfiles").arg(path);
916  }
917  return ret;
918 }
919 
921 {
922  d->configLocationFallback = enableFallback;
923 }
924 
926 {
927  return m_providers.value(providerId);
928 }
929 
931 {
932  if (m_providers.count() > 0)
933  return m_providers.constBegin().value();
934  return nullptr;
935 }
936 
938 {
939  CommentsModel *model = d->commentsModels[entry];
940  if (!model) {
941  model = new CommentsModel(this);
942  model->setEntry(entry);
943  connect(model, &QObject::destroyed, this, [=](){
944  d->commentsModels.remove(entry);
945  });
946  d->commentsModels[entry] = model;
947  }
948  return model;
949 }
950 
952 {
953  return d->busyMessage;
954 }
955 
957 {
958  if (busyMessage != d->busyMessage) {
959  d->busyMessage = busyMessage;
961  }
962  // TODO KF6 Remove compat block
963  // Emit old signals for compatibility
964  if (busyMessage.isEmpty()) {
965  Q_EMIT signalIdle({});
966  } else {
967  Q_EMIT signalBusy(busyMessage);
968  }
969 }
970 
972 {
973  return d->busyState;
974 }
975 
977 {
978  if (d->busyState != state) {
979  d->busyState = state;
981  }
982 }
983 
985  setBusyState(state);
986  setBusyMessage(busyMessage);
987 }
void setInterval(int msec)
void signalLoaded(const QDomDocument &)
Indicates that the list of providers has been successfully loaded.
void uninstall(KNSCore::EntryInternal entry)
Uninstalls an entry.
Definition: engine.cpp:689
KNewStuff xml loader.
Definition: xmlloader.h:37
void loadPreview(const KNSCore::EntryInternal &entry, EntryInternal::PreviewType type)
Attempt to load a specific preview for the specified entry.
Definition: engine.cpp:724
The configuration file is missing or somehow incorrect. The configuration file filename will be held ...
Definition: errorcode.h:28
QList< Provider::CategoryMetadata > categoriesMetadata()
The list of metadata for the categories handled by this engine instance.
Definition: engine.cpp:211
QString busyMessage() const
String representation of the engines busy state.
QString name(const QVariant &location)
QString uniqueId() const
Get the object&#39;s unique ID.
virtual QString id() const =0
A unique Id for this provider (the url in most cases)
Author author() const
Retrieve the author of the object.
Provider::SortMode sortMode() const
The sort mode set on the current request.
Definition: engine.cpp:421
QString attribute(const QString &name, const QString &defValue) const const
void setTagFilter(const QStringList &tagFilter)
Set the tag filter used for entries by this provider.
Definition: provider.cpp:73
QString toString() const const
QStringList downloadTagFilter() const
Gets the current downloadlink tag filter list.
Definition: engine.cpp:500
Installation of a content item has failed.
Definition: errorcode.h:30
< As Archive, except that if there is more than an item in the file, put contents in a subdirectory w...
Definition: installation.h:62
bool hasContentService() const
const T & at(int i) const const
UncompressionOptions uncompressionSetting() const
Returns the uncompression setting, in a computer-readable format.
void checkForUpdates()
Request for packages that are installed and need update.
Definition: engine.cpp:814
Contains the core functionality for handling interaction with NewStuff providers. ...
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QStringList categories() const
The list of the server-side names of the categories handled by this engine instance.
Definition: engine.cpp:201
void setSearchTerm(const QString &searchString)
Sets a string search term.
Definition: engine.cpp:454
QDomElement nextSiblingElement(const QString &tagName) const const
bool userCanBecomeFan(const EntryInternal &entry)
Whether or not the user is allowed to become a fan of a particular entry.
Definition: engine.cpp:783
void setDownloadTagFilter(const QStringList &downloadTagFilter)
Set the tag filter used for download items by this provider.
Definition: provider.cpp:83
void addTagFilter(const QString &filter)
Add a single filter entry to the entry tag filter.
Definition: engine.cpp:484
QDomElement documentElement() const const
used to keep track of a search
Definition: provider.h:67
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
void setDownloadTagFilter(const QStringList &filter)
Sets a filter to be applied to the downloads for an entry.
Definition: engine.cpp:492
void fetchEntryById(const QString &id)
Convenience method to launch a search for one specific entry.
Definition: engine.cpp:440
bool hasAdoptionCommand() const
Whether or not an adoption command exists for this engine.
Definition: engine.cpp:897
void chop(int n)
T * data() const const
BusyState busyState() const
Busy state of the engine.
QCA_EXPORT ProviderList providers()
void setBusyMessage(const QString &busyMessage)
Definition: engine.cpp:956
int size() const const
QStringList standardLocations(QStandardPaths::StandardLocation type)
void reset(T *other)
Engine(QObject *parent=nullptr)
Constructor.
Definition: engine.cpp:70
Q_SIGNAL void busyStateChanged()
Signal gets emitted when the busy state changes.
void setPath(const QString &path, QUrl::ParsingMode mode)
void timeout()
void setPayload(const QString &url)
Sets the object&#39;s file.
void addQueryItem(const QString &key, const QString &value)
QString payload() const
Retrieve the file name of the object.
int count(const T &value) const const
QString fromLocal8Bit(const char *str, int size)
void contactAuthor(const EntryInternal &entry)
Try to contact the author of the entry by email or showing their homepage.
Definition: engine.cpp:750
QString path() const const
bool isDir() const const
CommentsModel * commentsForEntry(const KNSCore::EntryInternal &entry)
This function will return an instance of a model which contains comments for the entry passed to it...
Definition: engine.cpp:937
void setCategoriesFilter(const QStringList &categories)
Set the categories that will be included in searches.
Definition: engine.cpp:406
QList< DownloadLinkInformation > downloadLinkInformationList() const
A list of downloadable data for this entry.
QChar fromLatin1(char c)
void load(const QUrl &url)
Starts asynchronously loading the xml document from the specified URL.
Definition: xmlloader.cpp:28
void addDownloadTagFilter(const QString &filter)
Add a single filter entry to the download tag filter.
Definition: engine.cpp:505
Loading an image has failed. The entry name and preview type which failed will be held in the metadat...
Definition: errorcode.h:31
bool isEmpty() const const
void setConfigLocationFallback(bool enableFallback)
Sets whether or not the config file location discovery fallback should be active. ...
Definition: engine.cpp:920
QStringList installedFiles() const
Retrieve the locally installed files.
int downloadLinkCount() const
The number of available download options for this entry.
~Engine()
Destructor.
Definition: engine.cpp:93
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setScheme(const QString &scheme)
QString searchTerm() const
The search term for the current search (empty if none is set)
Definition: engine.cpp:466
void setBusyState(BusyState state)
Sets the busy state of the engine.
Definition: engine.cpp:976
void checkForInstalled()
Requests installed packages with an up to date state.
Definition: engine.cpp:822
QSharedPointer< Provider > provider(const QString &providerId) const
The Provider instance with the passed ID.
Definition: engine.cpp:925
A model which takes care of the comments for a single EntryInternal.
bool init(const QString &configfile)
Initializes the engine.
Definition: engine.cpp:104
QString homepage() const
Retrieve the author&#39;s homepage.
void setStatus(KNS3::Entry::Status status)
Sets the entry&#39;s status.
int indexOf(QStringView str, int from) const const
KNewStuff Base Provider class.
Definition: provider.h:42
void signalMessage(const QString &message)
Indicates a message to be added to the ui&#39;s log, or sent to a messagebox.
Provider::Filter filter() const
The result filter set on the current request.
Definition: engine.cpp:435
void setFilter(Provider::Filter filter)
Set a filter for results (defaults to none), which will allow you to show only installed entries...
Definition: engine.cpp:426
void setPageSize(int pageSize)
Set the page size for requests not made explicitly with requestData(int,int)
Definition: engine.cpp:902
void setSortMode(Provider::SortMode mode)
Set the order the search results are returned in.
Definition: engine.cpp:412
QString toLower() const const
QStringList tagFilter() const
Gets the current tag filter list.
Definition: engine.cpp:479
void signalInstallationError(const QString &message)
An informational signal fired when a serious error occurs during the installation.
void becomeFan(const EntryInternal &entry)
This will mark the user who is currently authenticated as a fan of the entry passed to the function...
Definition: engine.cpp:789
QString targetInstallationPath() const
void stop()
void uninstall(KNSCore::EntryInternal entry)
Uninstalls an entry.
bool isNull() const const
void vote(const EntryInternal &entry, uint rating)
Cast a vote on the passed entry.
Definition: engine.cpp:777
QString adoptionCommand(const KNSCore::EntryInternal &entry) const
The adoption command can be used to allow a user to make use of an entry&#39;s installed data...
Definition: engine.cpp:872
QString i18n(const char *text, const TYPE &arg...)
QString cleanPath(const QString &path)
T takeLast()
virtual QString name() const
Retrieves the common name of the provider.
Definition: provider.cpp:63
QString dirName() const const
bool isRelative() const const
virtual void loadEntries(const KNSCore::Provider::SearchRequest &request)=0
load the given search and return given page
QString name() const
The name as defined by the knsrc file.
Definition: engine.cpp:196
Q_SIGNAL void busyMessageChanged()
Signal gets emitted when the busy message changes.
KNewStuff data entry container.
Definition: entryinternal.h:49
void setBusy(BusyState state, const QString &busyMessage)
Utility method to set both the state and busyMessage.
Definition: engine.cpp:984
QVector< QStringRef > splitRef(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QDomElement firstChildElement(const QString &tagName) const const
QSharedPointer< Provider > defaultProvider() const
Return the first provider in the providers list (usually the default provider)
Definition: engine.cpp:930
A provider has failed to load or initialize. The provider file URL or provider URL will be held in th...
Definition: errorcode.h:29
KNewStuff entry installation.
Definition: installation.h:38
void setQuery(const QString &query, QUrl::ParsingMode mode)
QString fromLatin1(const char *str, int size)
void start(int msec)
void loadDetails(const KNSCore::EntryInternal &entry)
Get the full details of a specific entry.
Definition: engine.cpp:718
QStringList categoriesFilter() const
The list of categories searches will actually show results from.
Definition: engine.cpp:206
QString tagName() const const
void install(const KNSCore::EntryInternal &entry)
Installs an entry&#39;s payload file.
KNS3::Entry::Status status() const
Retrieves the entry&#39;s status.
bool openUrl(const QUrl &url)
static QStringList configSearchLocations(bool includeFallbackLocations=false)
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.
Definition: engine.cpp:907
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString name() const
Retrieve the name of the data object.
void signalErrorCode(const KNSCore::ErrorCode &errorCode, const QString &message, const QVariant &metadata)
Fires in the case of any critical or serious errors, such as network or API problems.
QString email() const
Retrieve the author&#39;s email address.
Definition: core/author.cpp:92
QUrl baseUrl() const
QString providerId() const
The id of the provider this entry belongs to.
bool userCanVote(const EntryInternal &entry)
Whether or not a user is able to vote on the passed entry.
Definition: engine.cpp:771
T readEntry(const QString &key, const T &aDefault) const
QString baseName() const const
void install(KNSCore::EntryInternal entry, int linkId=1)
Installs an entry&#39;s payload file.
Definition: engine.cpp:548
void destroyed(QObject *obj)
Q_EMITQ_EMIT
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
void setTagFilter(const QStringList &filter)
Set a filter for results, which filters out all entries which do not match the filter, as applied to the tags for the entry.
Definition: engine.cpp:471
void setSingleShot(bool singleShot)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Aug 11 2020 22:43:23 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.