File indexing completed on 2024-10-13 03:39:10

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_p.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     setIcon(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 (name().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                 setName(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 (name().isEmpty()) {
0095         // Just a fallback, because those are quite nice to have...
0096         setName(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::Entry::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, Entry::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, Entry::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->setFilter(request.filter);
0171         loader->setSearchTerm(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     Entry::List entries;
0215     QDomElement element;
0216 
0217     TagsFilterChecker checker(tagFilter());
0218     TagsFilterChecker downloadschecker(downloadTagFilter());
0219     element = doc.documentElement();
0220     QDomElement n;
0221     for (n = element.firstChildElement(); !n.isNull(); n = n.nextSiblingElement()) {
0222         Entry entry;
0223         entry.setEntryXML(n.toElement());
0224         entry.setStatus(KNSCore::Entry::Downloadable);
0225         entry.setProviderId(mId);
0226 
0227         int index = mCachedEntries.indexOf(entry);
0228         if (index >= 0) {
0229             Entry cacheEntry = mCachedEntries.takeAt(index);
0230             // check if updateable
0231             if ((cacheEntry.status() == KNSCore::Entry::Installed)
0232                 && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) {
0233                 entry.setStatus(KNSCore::Entry::Updateable);
0234                 entry.setUpdateVersion(entry.version());
0235                 entry.setVersion(cacheEntry.version());
0236                 entry.setUpdateReleaseDate(entry.releaseDate());
0237                 entry.setReleaseDate(cacheEntry.releaseDate());
0238             } else {
0239                 entry.setStatus(cacheEntry.status());
0240             }
0241             cacheEntry = entry;
0242         }
0243 
0244         if (checker.filterAccepts(entry.tags())) {
0245             bool filterAcceptsDownloads = true;
0246             if (entry.downloadCount() > 0) {
0247                 const auto downloadInfoList = entry.downloadLinkInformationList();
0248                 for (const KNSCore::Entry::DownloadLinkInformation &dli : downloadInfoList) {
0249                     if (downloadschecker.filterAccepts(dli.tags)) {
0250                         filterAcceptsDownloads = true;
0251                         break;
0252                     }
0253                 }
0254             }
0255             if (filterAcceptsDownloads) {
0256                 mCachedEntries.append(entry);
0257 
0258                 if (searchIncludesEntry(entry)) {
0259                     switch (loader->filter()) {
0260                     case Installed:
0261                         // This is dealth with in loadEntries separately
0262                         Q_UNREACHABLE();
0263                     case Updates:
0264                         if (entry.status() == KNSCore::Entry::Updateable) {
0265                             entries << entry;
0266                         }
0267                         break;
0268                     case ExactEntryId:
0269                         if (entry.uniqueId() == loader->searchTerm()) {
0270                             entries << entry;
0271                         }
0272                         break;
0273                     case None:
0274                         entries << entry;
0275                         break;
0276                     }
0277                 }
0278             } else {
0279                 qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on download filter" << downloadTagFilter();
0280             }
0281         } else {
0282             qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on entry filter" << tagFilter();
0283         }
0284     }
0285     Q_EMIT loadingFinished(mCurrentRequest, entries);
0286 }
0287 
0288 void StaticXmlProvider::slotFeedFailed()
0289 {
0290     Q_EMIT loadingFailed(mCurrentRequest);
0291 }
0292 
0293 bool StaticXmlProvider::searchIncludesEntry(const KNSCore::Entry &entry) const
0294 {
0295     if (mCurrentRequest.filter == Updates) {
0296         if (entry.status() != KNSCore::Entry::Updateable) {
0297             return false;
0298         }
0299     }
0300 
0301     if (mCurrentRequest.searchTerm.isEmpty()) {
0302         return true;
0303     }
0304     QString search = mCurrentRequest.searchTerm;
0305     if (entry.name().contains(search, Qt::CaseInsensitive) || entry.summary().contains(search, Qt::CaseInsensitive)
0306         || entry.author().name().contains(search, Qt::CaseInsensitive)) {
0307         return true;
0308     }
0309     return false;
0310 }
0311 
0312 void StaticXmlProvider::loadPayloadLink(const KNSCore::Entry &entry, int)
0313 {
0314     qCDebug(KNEWSTUFFCORE) << "Payload: " << entry.payload();
0315     Q_EMIT payloadLinkLoaded(entry);
0316 }
0317 
0318 Entry::List StaticXmlProvider::installedEntries() const
0319 {
0320     Entry::List entries;
0321     for (const Entry &entry : std::as_const(mCachedEntries)) {
0322         if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) {
0323             entries.append(entry);
0324         }
0325     }
0326     return entries;
0327 }
0328 
0329 }
0330 
0331 #include "moc_staticxmlprovider_p.cpp"