File indexing completed on 2024-11-17 04:55:41

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "KNSResource.h"
0008 #include "KNSBackend.h"
0009 #include <KLocalizedString>
0010 #include <KNSCore/EngineBase>
0011 #include <KNSCore/Transaction>
0012 #include <KShell>
0013 #include <QProcess>
0014 #include <QRegularExpression>
0015 
0016 #include "ReviewsBackend/Rating.h"
0017 #include <appstream/AppStreamUtils.h>
0018 #include <attica/provider.h>
0019 #include <utils.h>
0020 
0021 KNSResource::KNSResource(const KNSCore::Entry &entry, QStringList categories, KNSBackend *parent)
0022     : AbstractResource(parent)
0023     , m_categories(std::move(categories))
0024     , m_entry(entry)
0025     , m_lastStatus(entry.status())
0026 {
0027     connect(this, &KNSResource::stateChanged, parent, &KNSBackend::updatesCountChanged);
0028 }
0029 
0030 KNSResource::~KNSResource() = default;
0031 
0032 AbstractResource::State KNSResource::state()
0033 {
0034     switch (m_entry.status()) {
0035     case KNSCore::Entry::Invalid:
0036         return Broken;
0037     case KNSCore::Entry::Downloadable:
0038         return None;
0039     case KNSCore::Entry::Installed:
0040         return Installed;
0041     case KNSCore::Entry::Updateable:
0042         return Upgradeable;
0043     case KNSCore::Entry::Deleted:
0044     case KNSCore::Entry::Installing:
0045     case KNSCore::Entry::Updating:
0046         return None;
0047     }
0048     return None;
0049 }
0050 
0051 KNSBackend *KNSResource::knsBackend() const
0052 {
0053     return qobject_cast<KNSBackend *>(parent());
0054 }
0055 
0056 QVariant KNSResource::icon() const
0057 {
0058     const QString thumbnail = m_entry.previewUrl(KNSCore::Entry::PreviewSmall1);
0059     return thumbnail.isEmpty() ? knsBackend()->iconName() : m_entry.previewUrl(KNSCore::Entry::PreviewSmall1);
0060 }
0061 
0062 QString KNSResource::comment()
0063 {
0064     QString ret = m_entry.shortSummary();
0065     if (ret.isEmpty()) {
0066         ret = m_entry.summary();
0067         int newLine = ret.indexOf(QLatin1Char('\n'));
0068         if (newLine > 0) {
0069             ret.truncate(newLine);
0070         }
0071         ret.remove(QRegularExpression(QStringLiteral("\\[\\/?[a-z]*\\]")));
0072         ret.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
0073     }
0074     return ret;
0075 }
0076 
0077 QString KNSResource::longDescription()
0078 {
0079     QString ret = m_entry.summary();
0080     if (m_entry.shortSummary().isEmpty()) {
0081         const int newLine = ret.indexOf(QLatin1Char('\n'));
0082         if (newLine < 0)
0083             ret.clear();
0084         else
0085             ret = ret.mid(newLine + 1).trimmed();
0086     }
0087     ret.remove(QLatin1Char('\r'));
0088     ret.replace(QStringLiteral("[li]"), QStringLiteral("\n* "));
0089     // Get rid of all BBCode markup we don't handle above
0090     ret.remove(QRegularExpression(QStringLiteral("\\[\\/?[a-z]*\\]")));
0091     // Find anything that looks like a link (but which also is not some html
0092     // tag value or another already) and make it a link
0093     static const QRegularExpression urlRegExp(
0094         QStringLiteral("(^|\\s)(http[-a-zA-Z0-9@:%_\\+.~#?&//=]{2,256}\\.[a-z]{2,4}\\b(\\/[-a-zA-Z0-9@:;%_\\+.~#?&//=]*)?)"),
0095         QRegularExpression::CaseInsensitiveOption);
0096     ret.replace(urlRegExp, QStringLiteral("<a href=\"\\2\">\\2</a>"));
0097     return ret;
0098 }
0099 
0100 QString KNSResource::name() const
0101 {
0102     return m_entry.name();
0103 }
0104 
0105 QString KNSResource::packageName() const
0106 {
0107     return m_entry.uniqueId();
0108 }
0109 
0110 QStringList KNSResource::categories()
0111 {
0112     return m_categories;
0113 }
0114 
0115 QUrl KNSResource::homepage()
0116 {
0117     return m_entry.homepage();
0118 }
0119 
0120 void KNSResource::setEntry(const KNSCore::Entry &entry)
0121 {
0122     const bool diff = entry.status() != m_lastStatus;
0123     m_entry = entry;
0124     if (diff) {
0125         m_lastStatus = entry.status();
0126         Q_EMIT stateChanged();
0127     }
0128 }
0129 
0130 KNSCore::Entry KNSResource::entry() const
0131 {
0132     return m_entry;
0133 }
0134 
0135 QJsonArray KNSResource::licenses()
0136 {
0137     return {{AppStreamUtils::license(m_entry.license())}};
0138 }
0139 
0140 quint64 KNSResource::size()
0141 {
0142     const auto downloadInfo = m_entry.downloadLinkInformationList();
0143     return downloadInfo.isEmpty() ? 0 : downloadInfo.at(0).size * 1024;
0144 }
0145 
0146 QString KNSResource::installedVersion() const
0147 {
0148     return !m_entry.version().isEmpty() ? m_entry.version() : m_entry.releaseDate().toString();
0149 }
0150 
0151 QString KNSResource::availableVersion() const
0152 {
0153     return !m_entry.updateVersion().isEmpty()   ? m_entry.updateVersion()
0154         : !m_entry.updateReleaseDate().isNull() ? m_entry.updateReleaseDate().toString()
0155         : !m_entry.version().isEmpty()          ? m_entry.version()
0156                                                 : releaseDate().toString();
0157 }
0158 
0159 QString KNSResource::origin() const
0160 {
0161     return m_entry.providerId();
0162 }
0163 
0164 QString KNSResource::displayOrigin() const
0165 {
0166     if (auto providers = knsBackend()->engine()->atticaProviders(); !providers.isEmpty()) {
0167         auto provider = providers.constFirst();
0168         if (provider->name() == QLatin1String("api.kde-look.org")) {
0169             return i18nc("The name of the KDE Store", "KDE Store");
0170         }
0171         return providers.constFirst()->name();
0172     }
0173     return QUrl(m_entry.providerId()).host();
0174 }
0175 
0176 QString KNSResource::section()
0177 {
0178     return m_entry.category();
0179 }
0180 
0181 static bool isAnimated(const QString &path)
0182 {
0183     static const QVector<QLatin1String> s_extensions = {QLatin1String(".gif"), QLatin1String(".apng"), QLatin1String(".webp"), QLatin1String(".avif")};
0184     return kContains(s_extensions, [path](const QLatin1String &postfix) {
0185         return path.endsWith(postfix);
0186     });
0187 }
0188 
0189 static void appendIfValid(Screenshots &list, const QUrl &thumbnail, const QUrl &screenshot)
0190 {
0191     if (thumbnail.isEmpty() || screenshot.isEmpty()) {
0192         return;
0193     }
0194     list += {thumbnail, screenshot, isAnimated(thumbnail.path())};
0195 }
0196 
0197 void KNSResource::fetchScreenshots()
0198 {
0199     Screenshots ret;
0200     appendIfValid(ret, QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewSmall1)), QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewBig1)));
0201     appendIfValid(ret, QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewSmall2)), QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewBig2)));
0202     appendIfValid(ret, QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewSmall3)), QUrl(m_entry.previewUrl(KNSCore::Entry::PreviewBig3)));
0203     Q_EMIT screenshotsFetched(ret);
0204 }
0205 
0206 void KNSResource::fetchChangelog()
0207 {
0208     Q_EMIT changelogFetched(m_entry.changelog());
0209 }
0210 
0211 QStringList KNSResource::extends() const
0212 {
0213     return knsBackend()->extends();
0214 }
0215 
0216 QUrl KNSResource::url() const
0217 {
0218     return QUrl(QStringLiteral("kns://") + knsBackend()->name() + QLatin1Char('/') + QUrl(m_entry.providerId()).host() + QLatin1Char('/') + m_entry.uniqueId());
0219 }
0220 
0221 bool KNSResource::canExecute() const
0222 {
0223     return knsBackend()->engine()->hasAdoptionCommand();
0224 }
0225 
0226 void KNSResource::invokeApplication() const
0227 {
0228     KNSCore::Transaction::adopt(knsBackend()->engine(), m_entry);
0229 }
0230 
0231 QString KNSResource::executeLabel() const
0232 {
0233     return knsBackend()->engine()->useLabel();
0234 }
0235 
0236 QDate KNSResource::releaseDate() const
0237 {
0238     return m_entry.updateReleaseDate().isNull() ? m_entry.releaseDate() : m_entry.updateReleaseDate();
0239 }
0240 
0241 QVector<int> KNSResource::linkIds() const
0242 {
0243     QVector<int> ids;
0244     const auto linkInfo = m_entry.downloadLinkInformationList();
0245     for (const auto &e : linkInfo) {
0246         if (e.isDownloadtypeLink)
0247             ids << e.id;
0248     }
0249     return ids;
0250 }
0251 
0252 QUrl KNSResource::donationURL()
0253 {
0254     return QUrl(m_entry.donationLink());
0255 }
0256 
0257 Rating *KNSResource::ratingInstance()
0258 {
0259     if (!m_rating) {
0260         const int noc = m_entry.numberOfComments();
0261         const int rating = m_entry.rating();
0262         Q_ASSERT(rating <= 100);
0263         m_rating.reset(new Rating(packageName(), noc, rating / 10));
0264     }
0265     return m_rating.data();
0266 }
0267 
0268 QString KNSResource::author() const
0269 {
0270     return m_entry.author().name();
0271 }