1/*
2 This file is part of KNewStuff2.
3 SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org>
4 SPDX-FileCopyrightText: 2003-2007 Josef Spillner <spillner@kde.org>
5 SPDX-FileCopyrightText: 2009 Frederik Gladhorn <gladhorn@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.1-or-later
8*/
9
10#include "entry.h"
11
12#include <QDomElement>
13#include <QMetaEnum>
14#include <QStringList>
15#include <QXmlStreamReader>
16#include <knewstuffcore_debug.h>
17
18#include "xmlloader_p.h"
19
20using namespace KNSCore;
21
22class KNSCore::EntryPrivate : public QSharedData
23{
24public:
25 EntryPrivate()
26 {
27 qRegisterMetaType<KNSCore::Entry::List>();
28 }
29
30 bool operator==(const EntryPrivate &other) const
31 {
32 return mUniqueId == other.mUniqueId && mProviderId == other.mProviderId;
33 }
34
35 QString mUniqueId;
36 QString mRequestedUniqueId; // We need to map the entry to the request in the ResultsStream, but invalid entries would have an empty ID
37 QString mName;
38 QUrl mHomepage;
39 QString mCategory;
40 QString mLicense;
41 QString mVersion;
42 QDate mReleaseDate = QDate::currentDate();
43
44 // Version and date if a newer version is found (updateable)
45 QString mUpdateVersion;
46 QDate mUpdateReleaseDate;
47
48 Author mAuthor;
49 int mRating = 0;
50 int mNumberOfComments = 0;
51 int mDownloadCount = 0;
52 int mNumberFans = 0;
53 int mNumberKnowledgebaseEntries = 0;
54 QString mKnowledgebaseLink;
55 QString mSummary;
56 QString mShortSummary;
57 QString mChangelog;
58 QString mPayload;
59 QStringList mInstalledFiles;
60 QString mProviderId;
61 QStringList mUnInstalledFiles;
62 QString mDonationLink;
63 QStringList mTags;
64
65 QString mChecksum;
66 QString mSignature;
67 KNSCore::Entry::Status mStatus = Entry::Invalid;
68 Entry::Source mSource = Entry::Online;
69 Entry::EntryType mEntryType = Entry::CatalogEntry;
70
71 QString mPreviewUrl[6];
72 QImage mPreviewImage[6];
73
74 QList<Entry::DownloadLinkInformation> mDownloadLinkInformationList;
75};
76
77Entry::Entry()
78 : d(new EntryPrivate())
79{
80}
81
82Entry::Entry(const Entry &other)
83 : d(other.d)
84{
85}
86
87Entry &Entry::operator=(const Entry &other)
88{
89 d = other.d;
90 return *this;
91}
92
93bool Entry::operator<(const KNSCore::Entry &other) const
94{
95 return d->mUniqueId < other.d->mUniqueId;
96}
97
98bool Entry::operator==(const KNSCore::Entry &other) const
99{
100 return d->mUniqueId == other.d->mUniqueId && d->mProviderId == other.d->mProviderId;
101}
102
103Entry::~Entry() = default;
104
105bool Entry::isValid() const
106{
107 return !d->mUniqueId.isEmpty(); // This should not use the uniqueId getter due to the fallback!
108}
109
110QString Entry::name() const
111{
112 return d->mName;
113}
114
115void Entry::setName(const QString &name)
116{
117 d->mName = name;
118}
119
120QString Entry::uniqueId() const
121{
122 return d->mUniqueId.isEmpty() ? d->mRequestedUniqueId : d->mUniqueId;
123}
124
125void Entry::setUniqueId(const QString &id)
126{
127 d->mUniqueId = id;
128}
129
130QString Entry::providerId() const
131{
132 return d->mProviderId;
133}
134
135void Entry::setProviderId(const QString &id)
136{
137 d->mProviderId = id;
138}
139
140QStringList KNSCore::Entry::tags() const
141{
142 return d->mTags;
143}
144
145void KNSCore::Entry::setTags(const QStringList &tags)
146{
147 d->mTags = tags;
148}
149
150QString Entry::category() const
151{
152 return d->mCategory;
153}
154
155void Entry::setCategory(const QString &category)
156{
157 d->mCategory = category;
158}
159
160QUrl Entry::homepage() const
161{
162 return d->mHomepage;
163}
164
165void Entry::setHomepage(const QUrl &page)
166{
167 d->mHomepage = page;
168}
169
170Author Entry::author() const
171{
172 return d->mAuthor;
173}
174
175void Entry::setAuthor(const KNSCore::Author &author)
176{
177 d->mAuthor = author;
178}
179
180QString Entry::license() const
181{
182 return d->mLicense;
183}
184
185void Entry::setLicense(const QString &license)
186{
187 d->mLicense = license;
188}
189
190QString Entry::summary() const
191{
192 return d->mSummary;
193}
194
195void Entry::setSummary(const QString &summary)
196{
197 d->mSummary = summary;
198}
199
200QString Entry::shortSummary() const
201{
202 return d->mShortSummary;
203}
204
205void Entry::setShortSummary(const QString &summary)
206{
207 d->mShortSummary = summary;
208}
209
210void Entry::setChangelog(const QString &changelog)
211{
212 d->mChangelog = changelog;
213}
214
215QString Entry::changelog() const
216{
217 return d->mChangelog;
218}
219
220QString Entry::version() const
221{
222 return d->mVersion;
223}
224
225void Entry::setVersion(const QString &version)
226{
227 d->mVersion = version;
228}
229
230QDate Entry::releaseDate() const
231{
232 return d->mReleaseDate;
233}
234
235void Entry::setReleaseDate(const QDate &releasedate)
236{
237 d->mReleaseDate = releasedate;
238}
239
240QString Entry::payload() const
241{
242 return d->mPayload;
243}
244
245void Entry::setPayload(const QString &url)
246{
247 d->mPayload = url;
248}
249
250QDate Entry::updateReleaseDate() const
251{
252 return d->mUpdateReleaseDate;
253}
254
255void Entry::setUpdateReleaseDate(const QDate &releasedate)
256{
257 d->mUpdateReleaseDate = releasedate;
258}
259
260QString Entry::updateVersion() const
261{
262 return d->mUpdateVersion;
263}
264
265void Entry::setUpdateVersion(const QString &version)
266{
267 d->mUpdateVersion = version;
268}
269
270QString Entry::previewUrl(PreviewType type) const
271{
272 return d->mPreviewUrl[type];
273}
274
275void Entry::setPreviewUrl(const QString &url, PreviewType type)
276{
277 d->mPreviewUrl[type] = url;
278}
279
280QImage Entry::previewImage(PreviewType type) const
281{
282 return d->mPreviewImage[type];
283}
284
285void Entry::setPreviewImage(const QImage &image, PreviewType type)
286{
287 d->mPreviewImage[type] = image;
288}
289
290int Entry::rating() const
291{
292 return d->mRating;
293}
294
295void Entry::setRating(int rating)
296{
297 d->mRating = rating;
298}
299
300int Entry::numberOfComments() const
301{
302 return d->mNumberOfComments;
303}
304
305void Entry::setNumberOfComments(int comments)
306{
307 d->mNumberOfComments = comments;
308}
309
310int Entry::downloadCount() const
311{
312 return d->mDownloadCount;
313}
314
315void Entry::setDownloadCount(int downloads)
316{
317 d->mDownloadCount = downloads;
318}
319
320int Entry::numberFans() const
321{
322 return d->mNumberFans;
323}
324
325void Entry::setNumberFans(int fans)
326{
327 d->mNumberFans = fans;
328}
329
330QString Entry::donationLink() const
331{
332 return d->mDonationLink;
333}
334
335void Entry::setDonationLink(const QString &link)
336{
337 d->mDonationLink = link;
338}
339
340int Entry::numberKnowledgebaseEntries() const
341{
342 return d->mNumberKnowledgebaseEntries;
343}
344void Entry::setNumberKnowledgebaseEntries(int num)
345{
346 d->mNumberKnowledgebaseEntries = num;
347}
348
349QString Entry::knowledgebaseLink() const
350{
351 return d->mKnowledgebaseLink;
352}
353void Entry::setKnowledgebaseLink(const QString &link)
354{
355 d->mKnowledgebaseLink = link;
356}
357
358Entry::Source Entry::source() const
359{
360 return d->mSource;
361}
362
363void Entry::setEntryType(Entry::EntryType type)
364{
365 d->mEntryType = type;
366}
367
368Entry::EntryType Entry::entryType() const
369{
370 return d->mEntryType;
371}
372
373void Entry::setSource(Source source)
374{
375 d->mSource = source;
376}
377
378KNSCore::Entry::Status Entry::status() const
379{
380 return d->mStatus;
381}
382
383void Entry::setStatus(KNSCore::Entry::Status status)
384{
385 d->mStatus = status;
386}
387
388void KNSCore::Entry::setInstalledFiles(const QStringList &files)
389{
390 d->mInstalledFiles = files;
391}
392
393QStringList KNSCore::Entry::installedFiles() const
394{
395 return d->mInstalledFiles;
396}
397
398QStringList KNSCore::Entry::uninstalledFiles() const
399{
400 return d->mUnInstalledFiles;
401}
402
403int KNSCore::Entry::downloadLinkCount() const
404{
405 return d->mDownloadLinkInformationList.size();
406}
407
408QList<KNSCore::Entry::DownloadLinkInformation> KNSCore::Entry::downloadLinkInformationList() const
409{
410 return d->mDownloadLinkInformationList;
411}
412
413void KNSCore::Entry::appendDownloadLinkInformation(const KNSCore::Entry::DownloadLinkInformation &info)
414{
415 d->mDownloadLinkInformationList.append(t: info);
416}
417
418void Entry::clearDownloadLinkInformation()
419{
420 d->mDownloadLinkInformationList.clear();
421}
422
423static QXmlStreamReader::TokenType readNextSkipComments(QXmlStreamReader *xml)
424{
425 do {
426 xml->readNext();
427 } while (xml->tokenType() == QXmlStreamReader::Comment || (xml->tokenType() == QXmlStreamReader::Characters && xml->text().trimmed().isEmpty()));
428 return xml->tokenType();
429}
430
431static QString readText(QXmlStreamReader *xml)
432{
433 Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
434 QString ret;
435 const auto token = readNextSkipComments(xml);
436 if (token == QXmlStreamReader::Characters) {
437 ret = xml->text().toString();
438 }
439 return ret;
440}
441
442static QString readStringTrimmed(QXmlStreamReader *xml)
443{
444 Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
445 QString ret = readText(xml).trimmed();
446
447 if (xml->tokenType() == QXmlStreamReader::Characters) {
448 readNextSkipComments(xml);
449 }
450 Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
451 return ret;
452}
453
454static int readInt(QXmlStreamReader *xml)
455{
456 Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
457 int ret = readText(xml).toInt();
458
459 xml->readNext();
460 Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
461 return ret;
462}
463
464bool KNSCore::Entry::setEntryXML(QXmlStreamReader &reader)
465{
466 if (reader.name() != QLatin1String("stuff")) {
467 qCWarning(KNEWSTUFFCORE) << "Parsing Entry from invalid XML. Reader tag name was expected to be \"stuff\", but was found as:" << reader.name();
468 return false;
469 }
470
471 d->mCategory = reader.attributes().value(QStringLiteral("category")).toString();
472
473 while (!reader.atEnd()) {
474 const auto token = readNextSkipComments(xml: &reader);
475 if (token == QXmlStreamReader::EndElement) {
476 break;
477 } else if (token != QXmlStreamReader::StartElement) {
478 continue;
479 }
480
481 if (reader.name() == QLatin1String("name")) {
482 // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
483 d->mName = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
484 } else if (reader.name() == QLatin1String("author")) {
485 // ### careful, the following variables are string views that become invalid when we
486 // proceed with reading from reader, such as the readStringTrimmed call below does!
487 const auto email = reader.attributes().value(QStringLiteral("email"));
488 const auto jabber = reader.attributes().value(QStringLiteral("im"));
489 const auto homepage = reader.attributes().value(QStringLiteral("homepage"));
490 d->mAuthor.setEmail(email.toString());
491 d->mAuthor.setJabber(jabber.toString());
492 d->mAuthor.setHomepage(homepage.toString());
493 d->mAuthor.setName(readStringTrimmed(xml: &reader));
494 } else if (reader.name() == QLatin1String("providerid")) {
495 d->mProviderId = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
496 } else if (reader.name() == QLatin1String("homepage")) {
497 d->mHomepage = QUrl(reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements));
498 } else if (reader.name() == QLatin1String("licence")) { // krazy:exclude=spelling
499 d->mLicense = readStringTrimmed(xml: &reader);
500 } else if (reader.name() == QLatin1String("summary")) {
501 d->mSummary = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
502 } else if (reader.name() == QLatin1String("changelog")) {
503 d->mChangelog = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
504 } else if (reader.name() == QLatin1String("version")) {
505 d->mVersion = readStringTrimmed(xml: &reader);
506 } else if (reader.name() == QLatin1String("releasedate")) {
507 d->mReleaseDate = QDate::fromString(string: readStringTrimmed(xml: &reader), format: Qt::ISODate);
508 } else if (reader.name() == QLatin1String("preview")) {
509 // TODO support for all 6 image links
510 d->mPreviewUrl[PreviewSmall1] = readStringTrimmed(xml: &reader);
511 } else if (reader.name() == QLatin1String("previewBig")) {
512 d->mPreviewUrl[PreviewBig1] = readStringTrimmed(xml: &reader);
513 } else if (reader.name() == QLatin1String("payload")) {
514 d->mPayload = readStringTrimmed(xml: &reader);
515 } else if (reader.name() == QLatin1String("rating")) {
516 d->mRating = readInt(xml: &reader);
517 } else if (reader.name() == QLatin1String("downloads")) {
518 d->mDownloadCount = readInt(xml: &reader);
519 } else if (reader.name() == QLatin1String("category")) {
520 d->mCategory = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
521 } else if (reader.name() == QLatin1String("signature")) {
522 d->mSignature = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
523 } else if (reader.name() == QLatin1String("checksum")) {
524 d->mChecksum = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
525 } else if (reader.name() == QLatin1String("installedfile")) {
526 d->mInstalledFiles.append(t: reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements));
527 } else if (reader.name() == QLatin1String("id")) {
528 d->mUniqueId = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements);
529 } else if (reader.name() == QLatin1String("tags")) {
530 d->mTags = reader.readElementText(behaviour: QXmlStreamReader::SkipChildElements).split(sep: QLatin1Char(','));
531 } else if (reader.name() == QLatin1String("status")) {
532 const auto statusText = readText(xml: &reader);
533 if (statusText == QLatin1String("installed")) {
534 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
535 d->mStatus = KNSCore::Entry::Installed;
536 } else if (statusText == QLatin1String("updateable")) {
537 d->mStatus = KNSCore::Entry::Updateable;
538 }
539 if (reader.tokenType() == QXmlStreamReader::Characters) {
540 readNextSkipComments(xml: &reader);
541 }
542 }
543 Q_ASSERT_X(reader.tokenType() == QXmlStreamReader::EndElement,
544 Q_FUNC_INFO,
545 QStringLiteral("token name was %1 and the type was %2").arg(reader.name().toString(), reader.tokenString()).toLocal8Bit().data());
546 }
547
548 // Validation
549 if (d->mName.isEmpty()) {
550 qWarning() << "Entry: no name given";
551 return false;
552 }
553
554 if (d->mUniqueId.isEmpty()) {
555 if (!d->mPayload.isEmpty()) {
556 d->mUniqueId = d->mPayload;
557 } else {
558 d->mUniqueId = d->mName;
559 }
560 }
561
562 if (d->mPayload.isEmpty()) {
563 qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
564 return false;
565 }
566 return true;
567}
568
569bool KNSCore::Entry::setEntryXML(const QDomElement &xmldata)
570{
571 if (xmldata.tagName() != QLatin1String("stuff")) {
572 qWarning() << "Parsing Entry from invalid XML";
573 return false;
574 }
575
576 d->mCategory = xmldata.attribute(QStringLiteral("category"));
577
578 QDomNode n;
579 for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) {
580 QDomElement e = n.toElement();
581 if (e.tagName() == QLatin1String("name")) {
582 // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
583 d->mName = e.text().trimmed();
584 } else if (e.tagName() == QLatin1String("author")) {
585 QString email = e.attribute(QStringLiteral("email"));
586 QString jabber = e.attribute(QStringLiteral("im"));
587 QString homepage = e.attribute(QStringLiteral("homepage"));
588 d->mAuthor.setName(e.text().trimmed());
589 d->mAuthor.setEmail(email);
590 d->mAuthor.setJabber(jabber);
591 d->mAuthor.setHomepage(homepage);
592 } else if (e.tagName() == QLatin1String("providerid")) {
593 d->mProviderId = e.text();
594 } else if (e.tagName() == QLatin1String("homepage")) {
595 d->mHomepage = QUrl(e.text());
596 } else if (e.tagName() == QLatin1String("licence")) { // krazy:exclude=spelling
597 d->mLicense = e.text().trimmed();
598 } else if (e.tagName() == QLatin1String("summary")) {
599 d->mSummary = e.text();
600 } else if (e.tagName() == QLatin1String("changelog")) {
601 d->mChangelog = e.text();
602 } else if (e.tagName() == QLatin1String("version")) {
603 d->mVersion = e.text().trimmed();
604 } else if (e.tagName() == QLatin1String("releasedate")) {
605 d->mReleaseDate = QDate::fromString(string: e.text().trimmed(), format: Qt::ISODate);
606 } else if (e.tagName() == QLatin1String("preview")) {
607 // TODO support for all 6 image links
608 d->mPreviewUrl[PreviewSmall1] = e.text().trimmed();
609 } else if (e.tagName() == QLatin1String("previewBig")) {
610 d->mPreviewUrl[PreviewBig1] = e.text().trimmed();
611 } else if (e.tagName() == QLatin1String("payload")) {
612 d->mPayload = e.text().trimmed();
613 } else if (e.tagName() == QLatin1String("rating")) {
614 d->mRating = e.text().toInt();
615 } else if (e.tagName() == QLatin1String("downloads")) {
616 d->mDownloadCount = e.text().toInt();
617 } else if (e.tagName() == QLatin1String("category")) {
618 d->mCategory = e.text();
619 } else if (e.tagName() == QLatin1String("signature")) {
620 d->mSignature = e.text();
621 } else if (e.tagName() == QLatin1String("checksum")) {
622 d->mChecksum = e.text();
623 } else if (e.tagName() == QLatin1String("installedfile")) {
624 // TODO KF6 Add a "installeddirectory" entry to avoid
625 // all the issues with the "/*" notation which is currently used as a workaround
626 d->mInstalledFiles.append(t: e.text());
627 } else if (e.tagName() == QLatin1String("id")) {
628 d->mUniqueId = e.text();
629 } else if (e.tagName() == QLatin1String("tags")) {
630 d->mTags = e.text().split(sep: QLatin1Char(','));
631 } else if (e.tagName() == QLatin1String("status")) {
632 QString statusText = e.text();
633 if (statusText == QLatin1String("installed")) {
634 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
635 d->mStatus = KNSCore::Entry::Installed;
636 } else if (statusText == QLatin1String("updateable")) {
637 d->mStatus = KNSCore::Entry::Updateable;
638 }
639 }
640 }
641
642 // Validation
643 if (d->mName.isEmpty()) {
644 qWarning() << "Entry: no name given";
645 return false;
646 }
647
648 if (d->mUniqueId.isEmpty()) {
649 if (!d->mPayload.isEmpty()) {
650 d->mUniqueId = d->mPayload;
651 } else {
652 d->mUniqueId = d->mName;
653 }
654 }
655
656 if (d->mPayload.isEmpty()) {
657 qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
658 return false;
659 }
660 return true;
661}
662
663/**
664 * get the xml string for the entry
665 */
666QDomElement KNSCore::Entry::entryXML() const
667{
668 Q_ASSERT(!d->mUniqueId.isEmpty());
669 Q_ASSERT(!d->mProviderId.isEmpty());
670
671 QDomDocument doc;
672
673 QDomElement el = doc.createElement(QStringLiteral("stuff"));
674 el.setAttribute(QStringLiteral("category"), value: d->mCategory);
675
676 QString name = d->mName;
677
678 QDomElement e;
679 e = addElement(doc, parent&: el, QStringLiteral("name"), value: name);
680 // todo: add language attribute
681 (void)addElement(doc, parent&: el, QStringLiteral("providerid"), value: d->mProviderId);
682
683 QDomElement author = addElement(doc, parent&: el, QStringLiteral("author"), value: d->mAuthor.name());
684 if (!d->mAuthor.email().isEmpty()) {
685 author.setAttribute(QStringLiteral("email"), value: d->mAuthor.email());
686 }
687 if (!d->mAuthor.homepage().isEmpty()) {
688 author.setAttribute(QStringLiteral("homepage"), value: d->mAuthor.homepage());
689 }
690 if (!d->mAuthor.jabber().isEmpty()) {
691 author.setAttribute(QStringLiteral("im"), value: d->mAuthor.jabber());
692 }
693 // FIXME: 'jabber' or 'im'? consult with kopete guys...
694 addElement(doc, parent&: el, QStringLiteral("homepage"), value: d->mHomepage.url());
695 (void)addElement(doc, parent&: el, QStringLiteral("licence"), value: d->mLicense); // krazy:exclude=spelling
696 (void)addElement(doc, parent&: el, QStringLiteral("version"), value: d->mVersion);
697 if ((d->mRating > 0) || (d->mDownloadCount > 0)) {
698 (void)addElement(doc, parent&: el, QStringLiteral("rating"), value: QString::number(d->mRating));
699 (void)addElement(doc, parent&: el, QStringLiteral("downloads"), value: QString::number(d->mDownloadCount));
700 }
701 if (!d->mSignature.isEmpty()) {
702 (void)addElement(doc, parent&: el, QStringLiteral("signature"), value: d->mSignature);
703 }
704 if (!d->mChecksum.isEmpty()) {
705 (void)addElement(doc, parent&: el, QStringLiteral("checksum"), value: d->mChecksum);
706 }
707 for (const QString &file : std::as_const(t&: d->mInstalledFiles)) {
708 (void)addElement(doc, parent&: el, QStringLiteral("installedfile"), value: file);
709 }
710 if (!d->mUniqueId.isEmpty()) {
711 addElement(doc, parent&: el, QStringLiteral("id"), value: d->mUniqueId);
712 }
713
714 (void)addElement(doc, parent&: el, QStringLiteral("releasedate"), value: d->mReleaseDate.toString(format: Qt::ISODate));
715
716 e = addElement(doc, parent&: el, QStringLiteral("summary"), value: d->mSummary);
717 e = addElement(doc, parent&: el, QStringLiteral("changelog"), value: d->mChangelog);
718 e = addElement(doc, parent&: el, QStringLiteral("preview"), value: d->mPreviewUrl[PreviewSmall1]);
719 e = addElement(doc, parent&: el, QStringLiteral("previewBig"), value: d->mPreviewUrl[PreviewBig1]);
720 e = addElement(doc, parent&: el, QStringLiteral("payload"), value: d->mPayload);
721 e = addElement(doc, parent&: el, QStringLiteral("tags"), value: d->mTags.join(sep: QLatin1Char(',')));
722
723 if (d->mStatus == KNSCore::Entry::Installed) {
724 (void)addElement(doc, parent&: el, QStringLiteral("status"), QStringLiteral("installed"));
725 }
726 if (d->mStatus == KNSCore::Entry::Updateable) {
727 (void)addElement(doc, parent&: el, QStringLiteral("status"), QStringLiteral("updateable"));
728 }
729
730 return el;
731}
732
733void KNSCore::Entry::setEntryDeleted()
734{
735 setStatus(Entry::Deleted);
736 d->mUnInstalledFiles = installedFiles();
737 setInstalledFiles(QStringList());
738}
739
740void KNSCore::Entry::setEntryRequestedId(const QString &id)
741{
742 d->mRequestedUniqueId = id;
743}
744
745QString KNSCore::replaceBBCode(const QString &unformattedText)
746{
747 QString text(unformattedText);
748 text.replace(before: QLatin1String("[b]"), after: QLatin1String("<b>"));
749 text.replace(before: QLatin1String("[/b]"), after: QLatin1String("</b>"));
750 text.replace(before: QLatin1String("[i]"), after: QLatin1String("<i>"));
751 text.replace(before: QLatin1String("[/i]"), after: QLatin1String("</i>"));
752 text.replace(before: QLatin1String("[u]"), after: QLatin1String("<i>"));
753 text.replace(before: QLatin1String("[/u]"), after: QLatin1String("</i>"));
754 text.replace(before: QLatin1String("\\\""), after: QLatin1String("\""));
755 text.replace(before: QLatin1String("\\\'"), after: QLatin1String("\'"));
756 text.replace(before: QLatin1String("[li]"), after: QLatin1String("* ")); // TODO: better replacement for list elements?
757 text.remove(QStringLiteral("[/li]"));
758 text.remove(QStringLiteral("[url]"));
759 text.remove(QStringLiteral("[/url]"));
760 return text;
761}
762
763QDebug KNSCore::operator<<(QDebug debug, const KNSCore::Entry &entry)
764{
765 QDebugStateSaver saver(debug);
766
767 const static QMetaEnum metaEnum = QMetaEnum::fromType<KNSCore::Entry::Status>();
768 bool deleted = entry.status() == Entry::Status::Deleted;
769
770 debug.nospace() << "KNSCore::Entry(uniqueId: " << entry.uniqueId() << ", name:" << entry.name() << ", status: " << metaEnum.valueToKey(value: entry.status())
771 << ", " << (deleted ? "uninstalled" : "installed") << "Files: " // When the entry is installed, it can not have uninstalledFiles
772 << (deleted ? entry.uninstalledFiles() : entry.installedFiles()) << ')';
773 return debug;
774}
775
776#include "moc_entry.cpp"
777

source code of knewstuff/src/core/entry.cpp