File indexing completed on 2024-04-21 15:02:26

0001 /*
0002     knewstuff3/provider.cpp
0003     SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org>
0004     SPDX-FileCopyrightText: 2003-2007 Josef Spillner <spillner@kde.org>
0005     SPDX-FileCopyrightText: 2009 Jeremy Whiting <jpwhiting@kde.org>
0006     SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.1-or-later
0009 */
0010 
0011 #include "staticxmlprovider_p.h"
0012 
0013 #include "xmlloader.h"
0014 
0015 #include <QTimer>
0016 #include <knewstuffcore_debug.h>
0017 #include <tagsfilterchecker.h>
0018 
0019 namespace KNSCore
0020 {
0021 StaticXmlProvider::StaticXmlProvider()
0022     : mInitialized(false)
0023 {
0024 }
0025 
0026 QString StaticXmlProvider::id() const
0027 {
0028     return mId;
0029 }
0030 
0031 bool StaticXmlProvider::setProviderXML(const QDomElement &xmldata)
0032 {
0033     if (xmldata.tagName() != QLatin1String("provider")) {
0034         return false;
0035     }
0036 
0037     mUploadUrl = QUrl(xmldata.attribute(QStringLiteral("uploadurl")));
0038     mNoUploadUrl = QUrl(xmldata.attribute(QStringLiteral("nouploadurl")));
0039 
0040     QString url = xmldata.attribute(QStringLiteral("downloadurl"));
0041     if (!url.isEmpty()) {
0042         mDownloadUrls.insert(QString(), QUrl(url));
0043     }
0044 
0045     url = xmldata.attribute(QStringLiteral("downloadurl-latest"));
0046     if (!url.isEmpty()) {
0047         mDownloadUrls.insert(QStringLiteral("latest"), QUrl(url));
0048     }
0049 
0050     url = xmldata.attribute(QStringLiteral("downloadurl-score"));
0051     if (!url.isEmpty()) {
0052         mDownloadUrls.insert(QStringLiteral("score"), QUrl(url));
0053     }
0054 
0055     url = xmldata.attribute(QStringLiteral("downloadurl-downloads"));
0056     if (!url.isEmpty()) {
0057         mDownloadUrls.insert(QStringLiteral("downloads"), QUrl(url));
0058     }
0059 
0060     // FIXME: this depends on freedesktop.org icon naming... introduce 'desktopicon'?
0061     QUrl iconurl(xmldata.attribute(QStringLiteral("icon")));
0062     if (!iconurl.isValid()) {
0063         iconurl = QUrl::fromLocalFile(xmldata.attribute(QStringLiteral("icon")));
0064     }
0065     mIcon = iconurl;
0066 
0067     QDomNode n;
0068     QLocale::Language systemLanguage = QLocale::system().language();
0069     QString firstName;
0070     for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) {
0071         QDomElement e = n.toElement();
0072         if (e.tagName() == QLatin1String("title")) {
0073             const QString lang{e.attribute(QLatin1String("lang"))};
0074             bool useThisTitle{false};
0075             if (mName.isEmpty() && lang.isEmpty()) {
0076                 // If we have no title as yet, and we've also got no language defined, this is the default
0077                 // and name we need to set it, even if we might override it later
0078                 useThisTitle = true;
0079             } else {
0080                 const QLocale locale(lang);
0081                 if (systemLanguage == locale.language()) {
0082                     useThisTitle = true;
0083                 }
0084             }
0085             if (useThisTitle) {
0086                 mName = e.text().trimmed();
0087                 qCDebug(KNEWSTUFFCORE) << "add name for provider (" << this << "): " << e.text();
0088             }
0089             if (firstName.isEmpty()) {
0090                 firstName = e.text().trimmed();
0091             }
0092         }
0093     }
0094     if (mName.isEmpty()) {
0095         // Just a fallback, because those are quite nice to have...
0096         mName = firstName;
0097     }
0098 
0099     // Validation
0100     if ((mNoUploadUrl.isValid()) && (mUploadUrl.isValid())) {
0101         qWarning() << "StaticXmlProvider: both uploadurl and nouploadurl given";
0102         return false;
0103     }
0104 
0105     if ((!mNoUploadUrl.isValid()) && (!mUploadUrl.isValid())) {
0106         qWarning() << "StaticXmlProvider: neither uploadurl nor nouploadurl given";
0107         return false;
0108     }
0109 
0110     if (mUploadUrl.isValid()) {
0111         setWebsite(mUploadUrl);
0112     } else {
0113         setWebsite(mNoUploadUrl);
0114     }
0115 
0116     mId = mDownloadUrls[QString()].url();
0117     if (mId.isEmpty()) {
0118         mId = mDownloadUrls[mDownloadUrls.begin().key()].url();
0119     }
0120 
0121     QTimer::singleShot(0, this, &StaticXmlProvider::slotEmitProviderInitialized);
0122 
0123     return true;
0124 }
0125 
0126 void StaticXmlProvider::slotEmitProviderInitialized()
0127 {
0128     mInitialized = true;
0129     Q_EMIT providerInitialized(this);
0130 }
0131 
0132 bool StaticXmlProvider::isInitialized() const
0133 {
0134     return mInitialized;
0135 }
0136 
0137 void StaticXmlProvider::setCachedEntries(const KNSCore::EntryInternal::List &cachedEntries)
0138 {
0139     qCDebug(KNEWSTUFFCORE) << "Set cached entries " << cachedEntries.size();
0140     mCachedEntries.append(cachedEntries);
0141 }
0142 
0143 void StaticXmlProvider::loadEntries(const KNSCore::Provider::SearchRequest &request)
0144 {
0145     mCurrentRequest = request;
0146 
0147     // static providers only have on page containing everything
0148     if (request.page > 0) {
0149         Q_EMIT loadingFinished(request, EntryInternal::List());
0150         return;
0151     }
0152 
0153     if (request.filter == Installed) {
0154         qCDebug(KNEWSTUFFCORE) << "Installed entries: " << mId << installedEntries().size();
0155         if (request.page == 0) {
0156             Q_EMIT loadingFinished(request, installedEntries());
0157         } else {
0158             Q_EMIT loadingFinished(request, EntryInternal::List());
0159         }
0160         return;
0161     }
0162 
0163     QUrl url = downloadUrl(request.sortMode);
0164     if (!url.isEmpty()) {
0165         // TODO first get the entries, then filter with searchString, finally emit the finished signal...
0166         // FIXME: don't create an endless number of xmlloaders!
0167         XmlLoader *loader = new XmlLoader(this);
0168         connect(loader, &XmlLoader::signalLoaded, this, &StaticXmlProvider::slotFeedFileLoaded);
0169         connect(loader, &XmlLoader::signalFailed, this, &StaticXmlProvider::slotFeedFailed);
0170         loader->setProperty("filter", request.filter);
0171         loader->setProperty("searchTerm", request.searchTerm);
0172 
0173         mFeedLoaders.insert(request.sortMode, loader);
0174 
0175         loader->load(url);
0176     } else {
0177         Q_EMIT loadingFailed(request);
0178     }
0179 }
0180 
0181 QUrl StaticXmlProvider::downloadUrl(SortMode mode) const
0182 {
0183     QUrl url;
0184     switch (mode) {
0185     case Rating:
0186         url = mDownloadUrls.value(QStringLiteral("score"));
0187         break;
0188     case Alphabetical:
0189         url = mDownloadUrls.value(QString());
0190         break;
0191     case Newest:
0192         url = mDownloadUrls.value(QStringLiteral("latest"));
0193         break;
0194     case Downloads:
0195         url = mDownloadUrls.value(QStringLiteral("downloads"));
0196         break;
0197     }
0198     if (url.isEmpty()) {
0199         url = mDownloadUrls.value(QString());
0200     }
0201     return url;
0202 }
0203 
0204 void StaticXmlProvider::slotFeedFileLoaded(const QDomDocument &doc)
0205 {
0206     XmlLoader *loader = qobject_cast<KNSCore::XmlLoader *>(sender());
0207     if (!loader) {
0208         qWarning() << "Loader not found!";
0209         Q_EMIT loadingFailed(mCurrentRequest);
0210         return;
0211     }
0212 
0213     // load all the entries from the domdocument given
0214     EntryInternal::List entries;
0215     QDomElement element;
0216     const Provider::Filter filter = loader->property("filter").value<Provider::Filter>();
0217     const QString searchTerm = loader->property("searchTerm").toString();
0218 
0219     TagsFilterChecker checker(tagFilter());
0220     TagsFilterChecker downloadschecker(downloadTagFilter());
0221     element = doc.documentElement();
0222     QDomElement n;
0223     for (n = element.firstChildElement(); !n.isNull(); n = n.nextSiblingElement()) {
0224         EntryInternal entry;
0225         entry.setEntryXML(n.toElement());
0226         entry.setStatus(KNS3::Entry::Downloadable);
0227         entry.setProviderId(mId);
0228 
0229         int index = mCachedEntries.indexOf(entry);
0230         if (index >= 0) {
0231             EntryInternal cacheEntry = mCachedEntries.takeAt(index);
0232             // check if updateable
0233             if ((cacheEntry.status() == KNS3::Entry::Installed)
0234                 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
0235                 entry.setStatus(KNS3::Entry::Updateable);
0236                 entry.setUpdateVersion(entry.version());
0237                 entry.setVersion(cacheEntry.version());
0238                 entry.setUpdateReleaseDate(entry.releaseDate());
0239                 entry.setReleaseDate(cacheEntry.releaseDate());
0240             } else {
0241                 entry.setStatus(cacheEntry.status());
0242             }
0243             cacheEntry = entry;
0244         }
0245 
0246         if (checker.filterAccepts(entry.tags())) {
0247             bool filterAcceptsDownloads = true;
0248             if (entry.downloadCount() > 0) {
0249                 const auto downloadInfoList = entry.downloadLinkInformationList();
0250                 for (const KNSCore::EntryInternal::DownloadLinkInformation &dli : downloadInfoList) {
0251                     if (downloadschecker.filterAccepts(dli.tags)) {
0252                         filterAcceptsDownloads = true;
0253                         break;
0254                     }
0255                 }
0256             }
0257             if (filterAcceptsDownloads) {
0258                 mCachedEntries.append(entry);
0259 
0260                 if (searchIncludesEntry(entry)) {
0261                     switch (filter) {
0262                     case Installed:
0263                         // This is dealth with in loadEntries separately
0264                         Q_UNREACHABLE();
0265                     case Updates:
0266                         if (entry.status() == KNS3::Entry::Updateable) {
0267                             entries << entry;
0268                         }
0269                         break;
0270                     case ExactEntryId:
0271                         if (entry.uniqueId() == searchTerm) {
0272                             entries << entry;
0273                         }
0274                         break;
0275                     case None:
0276                         entries << entry;
0277                         break;
0278                     }
0279                 }
0280             } else {
0281                 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on download filter" << downloadTagFilter();
0282             }
0283         } else {
0284             qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on entry filter" << tagFilter();
0285         }
0286     }
0287     Q_EMIT loadingFinished(mCurrentRequest, entries);
0288 }
0289 
0290 void StaticXmlProvider::slotFeedFailed()
0291 {
0292     Q_EMIT loadingFailed(mCurrentRequest);
0293 }
0294 
0295 bool StaticXmlProvider::searchIncludesEntry(const KNSCore::EntryInternal &entry) const
0296 {
0297     if (mCurrentRequest.filter == Updates) {
0298         if (entry.status() != KNS3::Entry::Updateable) {
0299             return false;
0300         }
0301     }
0302 
0303     if (mCurrentRequest.searchTerm.isEmpty()) {
0304         return true;
0305     }
0306     QString search = mCurrentRequest.searchTerm;
0307     if (entry.name().contains(search, Qt::CaseInsensitive) || entry.summary().contains(search, Qt::CaseInsensitive)
0308         || entry.author().name().contains(search, Qt::CaseInsensitive)) {
0309         return true;
0310     }
0311     return false;
0312 }
0313 
0314 void StaticXmlProvider::loadPayloadLink(const KNSCore::EntryInternal &entry, int)
0315 {
0316     qCDebug(KNEWSTUFFCORE) << "Payload: " << entry.payload();
0317     Q_EMIT payloadLinkLoaded(entry);
0318 }
0319 
0320 EntryInternal::List StaticXmlProvider::installedEntries() const
0321 {
0322     EntryInternal::List entries;
0323     for (const EntryInternal &entry : std::as_const(mCachedEntries)) {
0324         if (entry.status() == KNS3::Entry::Installed || entry.status() == KNS3::Entry::Updateable) {
0325             entries.append(entry);
0326         }
0327     }
0328     return entries;
0329 }
0330 
0331 }
0332 
0333 #include "moc_staticxmlprovider_p.cpp"