| 1 | /* |
| 2 | knewstuff3/provider.cpp |
| 3 | SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org> |
| 4 | SPDX-FileCopyrightText: 2003-2007 Josef Spillner <spillner@kde.org> |
| 5 | SPDX-FileCopyrightText: 2009 Jeremy Whiting <jpwhiting@kde.org> |
| 6 | SPDX-FileCopyrightText: 2009-2010 Frederik Gladhorn <gladhorn@kde.org> |
| 7 | |
| 8 | SPDX-License-Identifier: LGPL-2.1-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "staticxmlprovider_p.h" |
| 12 | |
| 13 | #include "xmlloader_p.h" |
| 14 | |
| 15 | #include "searchrequest_p.h" |
| 16 | #include <QTimer> |
| 17 | #include <knewstuffcore_debug.h> |
| 18 | #include <tagsfilterchecker.h> |
| 19 | |
| 20 | namespace KNSCore |
| 21 | { |
| 22 | StaticXmlProvider::StaticXmlProvider() |
| 23 | : mInitialized(false) |
| 24 | { |
| 25 | } |
| 26 | |
| 27 | QString StaticXmlProvider::id() const |
| 28 | { |
| 29 | return mId; |
| 30 | } |
| 31 | |
| 32 | bool StaticXmlProvider::setProviderXML(const QDomElement &xmldata) |
| 33 | { |
| 34 | if (xmldata.tagName() != QLatin1String("provider" )) { |
| 35 | return false; |
| 36 | } |
| 37 | |
| 38 | mUploadUrl = QUrl(xmldata.attribute(QStringLiteral("uploadurl" ))); |
| 39 | mNoUploadUrl = QUrl(xmldata.attribute(QStringLiteral("nouploadurl" ))); |
| 40 | |
| 41 | QString url = xmldata.attribute(QStringLiteral("downloadurl" )); |
| 42 | if (!url.isEmpty()) { |
| 43 | mDownloadUrls.insert(key: QString(), value: QUrl(url)); |
| 44 | } |
| 45 | |
| 46 | url = xmldata.attribute(QStringLiteral("downloadurl-latest" )); |
| 47 | if (!url.isEmpty()) { |
| 48 | mDownloadUrls.insert(QStringLiteral("latest" ), value: QUrl(url)); |
| 49 | } |
| 50 | |
| 51 | url = xmldata.attribute(QStringLiteral("downloadurl-score" )); |
| 52 | if (!url.isEmpty()) { |
| 53 | mDownloadUrls.insert(QStringLiteral("score" ), value: QUrl(url)); |
| 54 | } |
| 55 | |
| 56 | url = xmldata.attribute(QStringLiteral("downloadurl-downloads" )); |
| 57 | if (!url.isEmpty()) { |
| 58 | mDownloadUrls.insert(QStringLiteral("downloads" ), value: QUrl(url)); |
| 59 | } |
| 60 | |
| 61 | // FIXME: this depends on freedesktop.org icon naming... introduce 'desktopicon'? |
| 62 | m_iconUrl = [&xmldata] { |
| 63 | QUrl iconurl(xmldata.attribute(QStringLiteral("icon" ))); |
| 64 | if (!iconurl.isValid()) { |
| 65 | iconurl = QUrl::fromLocalFile(localfile: xmldata.attribute(QStringLiteral("icon" ))); |
| 66 | } |
| 67 | return iconurl; |
| 68 | }(); |
| 69 | |
| 70 | QDomNode n; |
| 71 | QLocale::Language systemLanguage = QLocale::system().language(); |
| 72 | QString firstName; |
| 73 | for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) { |
| 74 | QDomElement e = n.toElement(); |
| 75 | if (e.tagName() == QLatin1String("title" )) { |
| 76 | const QString lang{e.attribute(name: QLatin1String("lang" ))}; |
| 77 | bool useThisTitle{false}; |
| 78 | if (name().isEmpty() && lang.isEmpty()) { |
| 79 | // If we have no title as yet, and we've also got no language defined, this is the default |
| 80 | // and name we need to set it, even if we might override it later |
| 81 | useThisTitle = true; |
| 82 | } else { |
| 83 | const QLocale locale(lang); |
| 84 | if (systemLanguage == locale.language()) { |
| 85 | useThisTitle = true; |
| 86 | } |
| 87 | } |
| 88 | if (useThisTitle) { |
| 89 | m_name = e.text().trimmed(); |
| 90 | qCDebug(KNEWSTUFFCORE) << "add name for provider (" << this << "): " << e.text(); |
| 91 | } |
| 92 | if (firstName.isEmpty()) { |
| 93 | firstName = e.text().trimmed(); |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | if (name().isEmpty()) { |
| 98 | // Just a fallback, because those are quite nice to have... |
| 99 | m_name = firstName; |
| 100 | } |
| 101 | |
| 102 | // Validation |
| 103 | if ((mNoUploadUrl.isValid()) && (mUploadUrl.isValid())) { |
| 104 | qWarning() << "StaticXmlProvider: both uploadurl and nouploadurl given" ; |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | if ((!mNoUploadUrl.isValid()) && (!mUploadUrl.isValid())) { |
| 109 | qWarning() << "StaticXmlProvider: neither uploadurl nor nouploadurl given" ; |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | mId = mDownloadUrls[QString()].url(); |
| 114 | if (mId.isEmpty()) { |
| 115 | mId = mDownloadUrls[mDownloadUrls.begin().key()].url(); |
| 116 | } |
| 117 | |
| 118 | QTimer::singleShot(interval: 0, receiver: this, slot: [this] { |
| 119 | mInitialized = true; |
| 120 | Q_EMIT providerInitialized(this); |
| 121 | Q_EMIT basicsLoaded(); |
| 122 | }); |
| 123 | |
| 124 | return true; |
| 125 | } |
| 126 | |
| 127 | bool StaticXmlProvider::isInitialized() const |
| 128 | { |
| 129 | return mInitialized; |
| 130 | } |
| 131 | |
| 132 | void StaticXmlProvider::setCachedEntries(const KNSCore::Entry::List &cachedEntries) |
| 133 | { |
| 134 | qCDebug(KNEWSTUFFCORE) << "Set cached entries " << cachedEntries.size(); |
| 135 | mCachedEntries.append(l: cachedEntries); |
| 136 | } |
| 137 | |
| 138 | void StaticXmlProvider::loadEntries(const KNSCore::SearchRequest &request) |
| 139 | { |
| 140 | // static providers only have on page containing everything |
| 141 | if (request.d->page > 0) { |
| 142 | Q_EMIT loadingDone(request); |
| 143 | return; |
| 144 | } |
| 145 | |
| 146 | if (request.d->filter == Filter::Installed) { |
| 147 | qCDebug(KNEWSTUFFCORE) << "Installed entries: " << mId << installedEntries().size(); |
| 148 | if (request.d->page == 0) { |
| 149 | Q_EMIT entriesLoaded(request, installedEntries()); |
| 150 | Q_EMIT loadingDone(request); |
| 151 | } else { |
| 152 | Q_EMIT loadingDone(request); |
| 153 | } |
| 154 | return; |
| 155 | } |
| 156 | |
| 157 | QUrl url = downloadUrl(mode: request.d->sortMode); |
| 158 | if (!url.isEmpty()) { |
| 159 | // TODO first get the entries, then filter with searchString, finally emit the finished signal... |
| 160 | // FIXME: don't create an endless number of xmlloaders! |
| 161 | XmlLoader *loader = new XmlLoader(this); |
| 162 | connect(sender: loader, signal: &XmlLoader::signalLoaded, context: this, slot: [this, request](const QDomDocument &doc) { |
| 163 | slotFeedFileLoaded(request, doc); |
| 164 | Q_EMIT loadingDone(request); |
| 165 | }); |
| 166 | connect(sender: loader, signal: &XmlLoader::signalFailed, context: this, slot: [this, request] { |
| 167 | Q_EMIT loadingFailed(request); |
| 168 | }); |
| 169 | loader->setFilter(request.d->filter); |
| 170 | loader->setSearchTerm(request.d->searchTerm); |
| 171 | |
| 172 | loader->load(url); |
| 173 | } else { |
| 174 | Q_EMIT loadingFailed(request); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | QUrl StaticXmlProvider::downloadUrl(SortMode mode) const |
| 179 | { |
| 180 | QUrl url; |
| 181 | switch (mode) { |
| 182 | case SortMode::Rating: |
| 183 | url = mDownloadUrls.value(QStringLiteral("score" )); |
| 184 | break; |
| 185 | case SortMode::Alphabetical: |
| 186 | url = mDownloadUrls.value(key: QString()); |
| 187 | break; |
| 188 | case SortMode::Newest: |
| 189 | url = mDownloadUrls.value(QStringLiteral("latest" )); |
| 190 | break; |
| 191 | case SortMode::Downloads: |
| 192 | url = mDownloadUrls.value(QStringLiteral("downloads" )); |
| 193 | break; |
| 194 | } |
| 195 | if (url.isEmpty()) { |
| 196 | url = mDownloadUrls.value(key: QString()); |
| 197 | } |
| 198 | return url; |
| 199 | } |
| 200 | |
| 201 | void StaticXmlProvider::slotFeedFileLoaded(const KNSCore::SearchRequest &request, const QDomDocument &doc) |
| 202 | { |
| 203 | XmlLoader *loader = qobject_cast<KNSCore::XmlLoader *>(object: sender()); |
| 204 | if (!loader) { |
| 205 | qWarning() << "Loader not found!" ; |
| 206 | Q_EMIT loadingFailed(request); |
| 207 | return; |
| 208 | } |
| 209 | |
| 210 | // load all the entries from the domdocument given |
| 211 | Entry::List entries; |
| 212 | QDomElement element; |
| 213 | |
| 214 | TagsFilterChecker checker(tagFilter()); |
| 215 | TagsFilterChecker downloadschecker(downloadTagFilter()); |
| 216 | element = doc.documentElement(); |
| 217 | QDomElement n; |
| 218 | for (n = element.firstChildElement(); !n.isNull(); n = n.nextSiblingElement()) { |
| 219 | Entry entry; |
| 220 | entry.setEntryXML(n.toElement()); |
| 221 | entry.setStatus(KNSCore::Entry::Downloadable); |
| 222 | entry.setProviderId(mId); |
| 223 | |
| 224 | int index = mCachedEntries.indexOf(t: entry); |
| 225 | if (index >= 0) { |
| 226 | Entry cacheEntry = mCachedEntries.takeAt(i: index); |
| 227 | // check if updateable |
| 228 | if ((cacheEntry.status() == KNSCore::Entry::Installed) |
| 229 | && ((cacheEntry.version() != entry.version()) || (cacheEntry.releaseDate() != entry.releaseDate()))) { |
| 230 | entry.setStatus(KNSCore::Entry::Updateable); |
| 231 | entry.setUpdateVersion(entry.version()); |
| 232 | entry.setVersion(cacheEntry.version()); |
| 233 | entry.setUpdateReleaseDate(entry.releaseDate()); |
| 234 | entry.setReleaseDate(cacheEntry.releaseDate()); |
| 235 | } else { |
| 236 | entry.setStatus(cacheEntry.status()); |
| 237 | } |
| 238 | cacheEntry = entry; |
| 239 | } |
| 240 | |
| 241 | if (checker.filterAccepts(tags: entry.tags())) { |
| 242 | bool filterAcceptsDownloads = true; |
| 243 | if (entry.downloadCount() > 0) { |
| 244 | const auto downloadInfoList = entry.downloadLinkInformationList(); |
| 245 | for (const KNSCore::Entry::DownloadLinkInformation &dli : downloadInfoList) { |
| 246 | if (downloadschecker.filterAccepts(tags: dli.tags)) { |
| 247 | filterAcceptsDownloads = true; |
| 248 | break; |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | if (filterAcceptsDownloads) { |
| 253 | mCachedEntries.append(t: entry); |
| 254 | |
| 255 | if (searchIncludesEntry(request, entry)) { |
| 256 | switch (loader->filter()) { |
| 257 | case Filter::Installed: |
| 258 | // This is dealt with in loadEntries separately |
| 259 | Q_UNREACHABLE(); |
| 260 | case Filter::Updates: |
| 261 | if (entry.status() == KNSCore::Entry::Updateable) { |
| 262 | entries << entry; |
| 263 | } |
| 264 | break; |
| 265 | case Filter::ExactEntryId: |
| 266 | if (entry.uniqueId() == loader->searchTerm()) { |
| 267 | entries << entry; |
| 268 | } |
| 269 | break; |
| 270 | case Filter::None: |
| 271 | entries << entry; |
| 272 | break; |
| 273 | } |
| 274 | } |
| 275 | } else { |
| 276 | qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on download filter" << downloadTagFilter(); |
| 277 | } |
| 278 | } else { |
| 279 | qCDebug(KNEWSTUFFCORE) << "Filter has excluded" << entry.name() << "on entry filter" << tagFilter(); |
| 280 | } |
| 281 | } |
| 282 | Q_EMIT entriesLoaded(request, entries); |
| 283 | } |
| 284 | |
| 285 | bool StaticXmlProvider::searchIncludesEntry(const KNSCore::SearchRequest &request, const KNSCore::Entry &entry) const |
| 286 | { |
| 287 | if (request.d->filter == Filter::Updates) { |
| 288 | if (entry.status() != KNSCore::Entry::Updateable) { |
| 289 | return false; |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | if (request.d->searchTerm.isEmpty()) { |
| 294 | return true; |
| 295 | } |
| 296 | QString search = request.d->searchTerm; |
| 297 | if (entry.name().contains(s: search, cs: Qt::CaseInsensitive) || entry.summary().contains(s: search, cs: Qt::CaseInsensitive) |
| 298 | || entry.author().name().contains(s: search, cs: Qt::CaseInsensitive)) { |
| 299 | return true; |
| 300 | } |
| 301 | return false; |
| 302 | } |
| 303 | |
| 304 | void StaticXmlProvider::loadPayloadLink(const KNSCore::Entry &entry, int) |
| 305 | { |
| 306 | qCDebug(KNEWSTUFFCORE) << "Payload: " << entry.payload(); |
| 307 | Q_EMIT payloadLinkLoaded(entry); |
| 308 | } |
| 309 | |
| 310 | Entry::List StaticXmlProvider::installedEntries() const |
| 311 | { |
| 312 | Entry::List entries; |
| 313 | for (const Entry &entry : std::as_const(t: mCachedEntries)) { |
| 314 | if (entry.status() == KNSCore::Entry::Installed || entry.status() == KNSCore::Entry::Updateable) { |
| 315 | entries.append(t: entry); |
| 316 | } |
| 317 | } |
| 318 | return entries; |
| 319 | } |
| 320 | |
| 321 | QString StaticXmlProvider::name() const |
| 322 | { |
| 323 | return m_name; |
| 324 | } |
| 325 | |
| 326 | QUrl StaticXmlProvider::icon() const |
| 327 | { |
| 328 | return m_iconUrl; |
| 329 | } |
| 330 | |
| 331 | QString StaticXmlProvider::version() |
| 332 | { |
| 333 | return {}; |
| 334 | } |
| 335 | |
| 336 | QUrl StaticXmlProvider::website() |
| 337 | { |
| 338 | return mUploadUrl.isValid() ? mUploadUrl : mNoUploadUrl; |
| 339 | } |
| 340 | |
| 341 | QUrl StaticXmlProvider::host() |
| 342 | { |
| 343 | return {}; |
| 344 | } |
| 345 | |
| 346 | QString StaticXmlProvider::contactEmail() |
| 347 | { |
| 348 | return {}; |
| 349 | } |
| 350 | |
| 351 | bool StaticXmlProvider::supportsSsl() |
| 352 | { |
| 353 | return false; |
| 354 | } |
| 355 | |
| 356 | } |
| 357 | |
| 358 | #include "moc_staticxmlprovider_p.cpp" |
| 359 | |