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 | |
19 | namespace KNSCore |
20 | { |
21 | StaticXmlProvider::StaticXmlProvider() |
22 | : mInitialized(false) |
23 | { |
24 | } |
25 | |
26 | QString StaticXmlProvider::id() const |
27 | { |
28 | return mId; |
29 | } |
30 | |
31 | bool 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 | |
126 | void StaticXmlProvider::slotEmitProviderInitialized() |
127 | { |
128 | mInitialized = true; |
129 | Q_EMIT providerInitialized(this); |
130 | } |
131 | |
132 | bool StaticXmlProvider::isInitialized() const |
133 | { |
134 | return mInitialized; |
135 | } |
136 | |
137 | void StaticXmlProvider::setCachedEntries(const KNSCore::Entry::List &cachedEntries) |
138 | { |
139 | qCDebug(KNEWSTUFFCORE) << "Set cached entries " << cachedEntries.size(); |
140 | mCachedEntries.append(l: cachedEntries); |
141 | } |
142 | |
143 | void 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 | |
181 | QUrl 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 | |
204 | void 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 | |
288 | void StaticXmlProvider::slotFeedFailed() |
289 | { |
290 | Q_EMIT loadingFailed(mCurrentRequest); |
291 | } |
292 | |
293 | bool 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 | |
312 | void StaticXmlProvider::loadPayloadLink(const KNSCore::Entry &entry, int) |
313 | { |
314 | qCDebug(KNEWSTUFFCORE) << "Payload: " << entry.payload(); |
315 | Q_EMIT payloadLinkLoaded(entry); |
316 | } |
317 | |
318 | Entry::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 | |