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

source code of knewstuff/src/staticxml/staticxmlprovider.cpp