File indexing completed on 2024-11-24 04:54:55
0001 /* 0002 * SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0003 * SPDX-FileCopyrightText: 2017 Jan Grulich <jgrulich@redhat.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "FlatpakResource.h" 0009 #include "FlatpakBackend.h" 0010 #include "FlatpakFetchDataJob.h" 0011 #include "FlatpakSourcesBackend.h" 0012 #include "config-paths.h" 0013 0014 #include <Transaction/AddonList.h> 0015 0016 #include <AppStreamQt/developer.h> 0017 #include <AppStreamQt/icon.h> 0018 #include <AppStreamQt/screenshot.h> 0019 #include <AppStreamQt/utils.h> 0020 #include <AppStreamQt/version.h> 0021 #include <appstream/AppStreamUtils.h> 0022 0023 #include <KConfigGroup> 0024 #include <KDesktopFile> 0025 #include <KFormat> 0026 #include <KIO/ApplicationLauncherJob> 0027 #include <KLocalizedString> 0028 0029 #include <AppStreamQt/release.h> 0030 #include <QDebug> 0031 #include <QDesktopServices> 0032 #include <QDir> 0033 #include <QDirIterator> 0034 #include <QFileInfo> 0035 #include <QFutureWatcher> 0036 #include <QIcon> 0037 #include <QNetworkAccessManager> 0038 #include <QNetworkReply> 0039 #include <QNetworkRequest> 0040 #include <QProcess> 0041 #include <QStringList> 0042 #include <QTemporaryFile> 0043 #include <QTimer> 0044 #include <QUrlQuery> 0045 #include <QtConcurrentRun> 0046 0047 using namespace Qt::StringLiterals; 0048 0049 static QString iconCachePath(const AppStream::Icon &icon) 0050 { 0051 Q_ASSERT(icon.kind() == AppStream::Icon::KindRemote); 0052 return QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), icon.url().fileName()); 0053 } 0054 0055 const QStringList FlatpakResource::s_topObjects({ 0056 QStringLiteral("qrc:/qml/FlatpakAttention.qml"), 0057 QStringLiteral("qrc:/qml/FlatpakRemoveData.qml"), 0058 QStringLiteral("qrc:/qml/FlatpakOldBeta.qml"), 0059 QStringLiteral("qrc:/qml/FlatpakEolReason.qml"), 0060 }); 0061 const QStringList FlatpakResource::s_bottomObjects({QStringLiteral("qrc:/qml/PermissionsList.qml")}); 0062 0063 Q_GLOBAL_STATIC(QNetworkAccessManager, manager) 0064 0065 FlatpakResource::FlatpakResource(const AppStream::Component &component, FlatpakInstallation *installation, FlatpakBackend *parent) 0066 : AbstractResource(parent) 0067 , m_appdata(component) 0068 , m_id({component.id(), QString(), QString()}) 0069 , m_downloadSize(0) 0070 , m_installedSize(0) 0071 , m_propertyStates({{DownloadSize, NotKnownYet}, {InstalledSize, NotKnownYet}, {RequiredRuntime, NotKnownYet}}) 0072 , m_state(AbstractResource::None) 0073 , m_installation(installation) 0074 { 0075 setObjectName(packageName()); 0076 0077 // Start fetching remote icons during initialization 0078 const auto icons = m_appdata.icons(); 0079 if (icons.count() == 1 && icons.constFirst().kind() == AppStream::Icon::KindRemote) { 0080 const auto icon = icons.constFirst(); 0081 const QString fileName = iconCachePath(icon); 0082 if (!QFileInfo::exists(fileName)) { 0083 const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); 0084 // Create $HOME/.cache/discover/icons folder 0085 cacheDir.mkdir(QStringLiteral("icons")); 0086 auto reply = manager->get(QNetworkRequest(icon.url())); 0087 connect(reply, &QNetworkReply::finished, this, [this, icon, fileName, reply] { 0088 if (reply->error() == QNetworkReply::NoError) { 0089 QByteArray iconData = reply->readAll(); 0090 QFile file(fileName); 0091 if (file.open(QIODevice::WriteOnly)) { 0092 file.write(iconData); 0093 } else { 0094 qDebug() << "could not find icon for" << packageName() << reply->url(); 0095 QIcon::fromTheme(QStringLiteral("package-x-generic")).pixmap(32, 32).toImage().save(fileName); 0096 } 0097 file.close(); 0098 Q_EMIT iconChanged(); 0099 reply->deleteLater(); 0100 } 0101 }); 0102 } 0103 } 0104 0105 connect(this, &FlatpakResource::stateChanged, this, &FlatpakResource::hasDataChanged); 0106 } 0107 0108 AppStream::Component FlatpakResource::appstreamComponent() const 0109 { 0110 return m_appdata; 0111 } 0112 0113 QList<PackageState> FlatpakResource::addonsInformation() 0114 { 0115 return {}; 0116 } 0117 0118 QString FlatpakResource::availableVersion() const 0119 { 0120 if (m_availableVersion.isEmpty()) { 0121 #if ASQ_CHECK_VERSION(1, 0, 0) 0122 const auto releases = m_appdata.releasesPlain().entries(); 0123 #else 0124 const auto releases = m_appdata.releases(); 0125 #endif 0126 if (!releases.isEmpty()) { 0127 auto latestVersion = releases.constFirst().version(); 0128 for (const auto &release : releases) { 0129 if (AppStream::Utils::vercmpSimple(release.version(), latestVersion) > 0) { 0130 latestVersion = release.version(); 0131 } 0132 }; 0133 m_availableVersion = latestVersion; 0134 return m_availableVersion; 0135 } 0136 } else { 0137 return m_availableVersion; 0138 } 0139 return branch(); 0140 } 0141 0142 QString FlatpakResource::appstreamId() const 0143 { 0144 return m_id.id; 0145 } 0146 0147 QString FlatpakResource::arch() const 0148 { 0149 return m_id.arch; 0150 } 0151 0152 QString FlatpakResource::branch() const 0153 { 0154 return m_id.branch; 0155 } 0156 0157 bool FlatpakResource::canExecute() const 0158 { 0159 return (m_type == DesktopApp && (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable)); 0160 } 0161 0162 void FlatpakResource::updateFromRef(FlatpakRef *ref) 0163 { 0164 setArch(QString::fromUtf8(flatpak_ref_get_arch(ref))); 0165 setBranch(QString::fromUtf8(flatpak_ref_get_branch(ref))); 0166 setCommit(QString::fromUtf8(flatpak_ref_get_commit(ref))); 0167 setFlatpakName(QString::fromUtf8(flatpak_ref_get_name(ref))); 0168 setType(flatpak_ref_get_kind(ref) == FLATPAK_REF_KIND_APP ? DesktopApp : extends().isEmpty() ? Runtime : Extension); 0169 setObjectName(packageName()); 0170 } 0171 0172 void FlatpakResource::updateFromAppStream() 0173 { 0174 const QString refstr = m_appdata.bundle(AppStream::Bundle::KindFlatpak).id(); 0175 g_autoptr(GError) localError = nullptr; 0176 g_autoptr(FlatpakRef) ref = flatpak_ref_parse(refstr.toUtf8().constData(), &localError); 0177 if (!ref) { 0178 qDebug() << "failed to obtain ref" << refstr << localError->message; 0179 return; 0180 } 0181 updateFromRef(ref); 0182 } 0183 0184 QStringList FlatpakResource::categories() 0185 { 0186 auto cats = m_appdata.categories(); 0187 if (m_appdata.kind() != AppStream::Component::KindAddon) 0188 cats.append(QStringLiteral("Application")); 0189 return cats; 0190 } 0191 0192 QString FlatpakResource::comment() 0193 { 0194 const auto summary = m_appdata.summary(); 0195 if (!summary.isEmpty()) { 0196 return summary; 0197 } 0198 0199 return QString(); 0200 } 0201 0202 QString FlatpakResource::commit() const 0203 { 0204 return m_commit; 0205 } 0206 0207 quint64 FlatpakResource::downloadSize() const 0208 { 0209 return m_downloadSize; 0210 } 0211 0212 QVariant FlatpakResource::icon() const 0213 { 0214 QIcon ret; 0215 const auto icons = m_appdata.icons(); 0216 0217 if (!m_bundledIcon.isNull()) { 0218 ret = QIcon(m_bundledIcon); 0219 } else if (icons.isEmpty()) { 0220 ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); 0221 } else 0222 for (const AppStream::Icon &icon : icons) { 0223 switch (icon.kind()) { 0224 case AppStream::Icon::KindLocal: 0225 case AppStream::Icon::KindCached: { 0226 const QString path = icon.url().toLocalFile(); 0227 if (QDir::isRelativePath(path)) { 0228 const QString appstreamLocation = 0229 installationPath() + "/appstream/"_L1 + origin() + '/'_L1 + QString::fromUtf8(flatpak_get_default_arch()) + "/active/icons/"_L1; 0230 QDirIterator dit(appstreamLocation, QDirIterator::Subdirectories); 0231 while (dit.hasNext()) { 0232 const auto currentPath = dit.next(); 0233 if (dit.fileName() == path) { 0234 ret.addFile(currentPath, icon.size()); 0235 } 0236 } 0237 } else { 0238 ret.addFile(path, icon.size()); 0239 } 0240 } break; 0241 case AppStream::Icon::KindStock: { 0242 const auto ret = QIcon::fromTheme(icon.name()); 0243 if (!ret.isNull()) { 0244 return ret; 0245 } 0246 break; 0247 } 0248 case AppStream::Icon::KindRemote: { 0249 const QString fileName = iconCachePath(icon); 0250 if (QFileInfo::exists(fileName)) { 0251 ret.addFile(fileName, icon.size()); 0252 } 0253 break; 0254 } 0255 case AppStream::Icon::KindUnknown: 0256 break; 0257 } 0258 } 0259 0260 if (ret.isNull()) { 0261 ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); 0262 } 0263 0264 return ret; 0265 } 0266 0267 QString FlatpakResource::installedVersion() const 0268 { 0269 g_autoptr(FlatpakInstalledRef) ref = qobject_cast<FlatpakBackend *>(backend())->getInstalledRefForApp(this); 0270 if (ref) { 0271 const char *appdataVersion = flatpak_installed_ref_get_appdata_version(ref); 0272 0273 if (appdataVersion) { 0274 return QString::fromUtf8(appdataVersion); 0275 } 0276 } 0277 return branch(); 0278 } 0279 0280 quint64 FlatpakResource::installedSize() const 0281 { 0282 return m_installedSize; 0283 } 0284 0285 AbstractResource::Type FlatpakResource::type() const 0286 { 0287 switch (m_type) { 0288 case FlatpakResource::Runtime: 0289 return Technical; 0290 case FlatpakResource::Extension: 0291 return Addon; 0292 default: 0293 return Application; 0294 } 0295 } 0296 0297 QUrl FlatpakResource::homepage() 0298 { 0299 return m_appdata.url(AppStream::Component::UrlKindHomepage); 0300 } 0301 0302 QUrl FlatpakResource::helpURL() 0303 { 0304 return m_appdata.url(AppStream::Component::UrlKindHelp); 0305 } 0306 0307 QUrl FlatpakResource::bugURL() 0308 { 0309 return m_appdata.url(AppStream::Component::UrlKindBugtracker); 0310 } 0311 0312 QUrl FlatpakResource::donationURL() 0313 { 0314 return m_appdata.url(AppStream::Component::UrlKindDonation); 0315 } 0316 0317 QUrl FlatpakResource::contributeURL() 0318 { 0319 return m_appdata.url(AppStream::Component::UrlKindContribute); 0320 } 0321 0322 FlatpakResource::FlatpakFileType FlatpakResource::flatpakFileType() const 0323 { 0324 return m_flatpakFileType; 0325 } 0326 0327 QString FlatpakResource::flatpakName() const 0328 { 0329 // If the flatpak name is not known (known only for installed apps), then use 0330 // appstream id instead; 0331 if (m_flatpakName.isEmpty()) { 0332 return m_id.id; 0333 } 0334 0335 return m_flatpakName; 0336 } 0337 0338 QJsonArray FlatpakResource::licenses() 0339 { 0340 return AppStreamUtils::licenses(m_appdata); 0341 } 0342 0343 QString FlatpakResource::longDescription() 0344 { 0345 return m_appdata.description(); 0346 } 0347 0348 QString FlatpakResource::attentionText() const 0349 { 0350 if (m_flatpakFileType == FlatpakResource::FileFlatpakRef) { 0351 QUrl loc = m_resourceLocation; 0352 loc.setPath({}); 0353 loc.setQuery(QUrlQuery()); 0354 return xi18nc("@info", 0355 "<para>This application comes from \"%1\" (hosted at <link>%2</link>). Other software in this repository will also be made be available " 0356 "in Discover " 0357 "when the application is " 0358 "installed.</para>", 0359 m_origin, 0360 loc.toDisplayString()); 0361 } 0362 return {}; 0363 } 0364 0365 QAbstractListModel *FlatpakResource::permissionsModel() 0366 { 0367 if (m_permissions.empty()) { 0368 loadPermissions(); 0369 } 0370 return new FlatpakPermissionsModel(m_permissions); 0371 } 0372 0373 QString FlatpakResource::name() const 0374 { 0375 QString name = m_appdata.name(); 0376 if (name.isEmpty()) { 0377 name = flatpakName(); 0378 } 0379 0380 if (name.startsWith(QLatin1String("(Nightly) "))) { 0381 return name.mid(10); 0382 } 0383 0384 return name; 0385 } 0386 0387 QString FlatpakResource::origin() const 0388 { 0389 return m_origin; 0390 } 0391 0392 QString FlatpakResource::displayOrigin() const 0393 { 0394 return !m_displayOrigin.isEmpty() ? m_displayOrigin : m_origin; 0395 } 0396 0397 // Note: The following three methods are not using each other for optimization purposes: 0398 // use string builder with arguments directly instead of temporary allocated strings. 0399 QString FlatpakResource::packageName() const 0400 { 0401 return QStringLiteral("%1/%2/%3").arg(flatpakName(), arch(), branch()); 0402 } 0403 0404 QString FlatpakResource::ref() const 0405 { 0406 return QStringLiteral("%1/%2/%3/%4").arg(typeAsString(), flatpakName(), arch(), branch()); 0407 } 0408 0409 QString FlatpakResource::installPath() const 0410 { 0411 return QStringLiteral("%1/%2/%3/%4/%5/active").arg(installationPath(), typeAsString(), flatpakName(), arch(), branch()); 0412 } 0413 0414 FlatpakResource::PropertyState FlatpakResource::propertyState(FlatpakResource::PropertyKind kind) const 0415 { 0416 return m_propertyStates[kind]; 0417 } 0418 0419 QUrl FlatpakResource::resourceFile() const 0420 { 0421 return m_resourceFile; 0422 } 0423 0424 QString FlatpakResource::runtime() const 0425 { 0426 return m_runtime; 0427 } 0428 0429 QString FlatpakResource::section() 0430 { 0431 return QString(); 0432 } 0433 0434 quint64 FlatpakResource::size() 0435 { 0436 if (m_state == Installed) { 0437 return m_installedSize; 0438 } else { 0439 return m_downloadSize; 0440 } 0441 } 0442 0443 QString FlatpakResource::sizeDescription() 0444 { 0445 if (propertyState(InstalledSize) == NotKnownYet || propertyState(InstalledSize) == Fetching) { 0446 qobject_cast<FlatpakBackend *>(backend())->updateAppSize(this); 0447 return i18n("Retrieving size information"); 0448 } else if (propertyState(InstalledSize) == UnknownOrFailed) { 0449 return i18nc("@label app size", "Unknown"); 0450 } else { 0451 const KFormat f; 0452 return f.formatByteSize(installedSize()); 0453 } 0454 } 0455 0456 AbstractResource::State FlatpakResource::state() 0457 { 0458 return m_state; 0459 } 0460 0461 FlatpakResource::ResourceType FlatpakResource::resourceType() const 0462 { 0463 return m_type; 0464 } 0465 0466 QString FlatpakResource::typeAsString() const 0467 { 0468 switch (m_type) { 0469 case FlatpakResource::Runtime: 0470 case FlatpakResource::Extension: 0471 return QLatin1String("runtime"); 0472 case FlatpakResource::DesktopApp: 0473 case FlatpakResource::Source: 0474 default: 0475 return QLatin1String("app"); 0476 } 0477 } 0478 0479 FlatpakResource::Id FlatpakResource::uniqueId() const 0480 { 0481 return m_id; 0482 } 0483 0484 void FlatpakResource::invokeApplication() const 0485 { 0486 QString desktopFileName; 0487 auto launchables = m_appdata.launchable(AppStream::Launchable::KindDesktopId).entries(); 0488 if (!launchables.isEmpty()) { 0489 desktopFileName = launchables.constFirst(); 0490 } else { 0491 qWarning() << "Failed to find launchable for " << m_appdata.name() << ", using AppStream identifier instead"; 0492 desktopFileName = appstreamId(); 0493 } 0494 0495 KService::Ptr service = KService::serviceByStorageId(desktopFileName); 0496 0497 if (!service) { 0498 qWarning() << "Failed to find service" << desktopFileName; 0499 return; 0500 } 0501 0502 auto job = new KIO::ApplicationLauncherJob(service); 0503 connect(job, &KJob::finished, this, [this, service](KJob *job) { 0504 if (job->error()) { 0505 Q_EMIT backend()->passiveMessage(i18n("Failed to start '%1': %2", service->name(), job->errorString())); 0506 } 0507 }); 0508 0509 job->start(); 0510 } 0511 0512 void FlatpakResource::fetchChangelog() 0513 { 0514 Q_EMIT changelogFetched(AppStreamUtils::changelogToHtml(m_appdata)); 0515 } 0516 0517 void FlatpakResource::fetchScreenshots() 0518 { 0519 Q_EMIT screenshotsFetched(AppStreamUtils::fetchScreenshots(m_appdata)); 0520 } 0521 0522 void FlatpakResource::setArch(const QString &arch) 0523 { 0524 m_id.arch = arch; 0525 } 0526 0527 void FlatpakResource::setBranch(const QString &branch) 0528 { 0529 m_id.branch = branch; 0530 } 0531 0532 void FlatpakResource::setBundledIcon(const QPixmap &pixmap) 0533 { 0534 m_bundledIcon = pixmap; 0535 } 0536 0537 void FlatpakResource::setCommit(const QString &commit) 0538 { 0539 m_commit = commit; 0540 } 0541 0542 void FlatpakResource::setDownloadSize(quint64 size) 0543 { 0544 m_downloadSize = size; 0545 0546 setPropertyState(DownloadSize, AlreadyKnown); 0547 0548 Q_EMIT sizeChanged(); 0549 } 0550 0551 void FlatpakResource::setFlatpakFileType(FlatpakFileType fileType) 0552 { 0553 m_flatpakFileType = fileType; 0554 } 0555 0556 void FlatpakResource::setFlatpakName(const QString &name) 0557 { 0558 m_flatpakName = name; 0559 } 0560 0561 void FlatpakResource::setIconPath(const QString &path) 0562 { 0563 m_iconPath = path; 0564 } 0565 0566 void FlatpakResource::setInstalledSize(quint64 size) 0567 { 0568 m_installedSize = size; 0569 0570 setPropertyState(InstalledSize, AlreadyKnown); 0571 0572 Q_EMIT sizeChanged(); 0573 } 0574 0575 void FlatpakResource::setOrigin(const QString &origin) 0576 { 0577 m_origin = origin; 0578 } 0579 0580 void FlatpakResource::setDisplayOrigin(const QString &displayOrigin) 0581 { 0582 m_displayOrigin = displayOrigin; 0583 } 0584 0585 void FlatpakResource::setPropertyState(FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState newState) 0586 { 0587 auto &state = m_propertyStates[kind]; 0588 if (state != newState) { 0589 state = newState; 0590 0591 Q_EMIT propertyStateChanged(kind, newState); 0592 } 0593 } 0594 0595 void FlatpakResource::setResourceFile(const QUrl &url) 0596 { 0597 m_resourceFile = url; 0598 } 0599 0600 void FlatpakResource::setRuntime(const QString &runtime) 0601 { 0602 m_runtime = runtime; 0603 0604 setPropertyState(RequiredRuntime, AlreadyKnown); 0605 } 0606 0607 void FlatpakResource::setState(AbstractResource::State state, bool shouldEmit) 0608 { 0609 if (m_state != state) { 0610 m_state = state; 0611 0612 if (shouldEmit && qobject_cast<FlatpakBackend *>(backend())->isTracked(this)) { 0613 Q_EMIT stateChanged(); 0614 } 0615 } 0616 } 0617 0618 void FlatpakResource::setType(FlatpakResource::ResourceType type) 0619 { 0620 m_type = type; 0621 } 0622 0623 QString FlatpakResource::installationPath() const 0624 { 0625 return installationPath(m_installation); 0626 } 0627 0628 QString FlatpakResource::installationPath(FlatpakInstallation *flatpakInstallation) 0629 { 0630 g_autoptr(GFile) path = flatpak_installation_get_path(flatpakInstallation); 0631 g_autofree char *path_str = g_file_get_path(path); 0632 return QString::fromUtf8(path_str); 0633 } 0634 0635 QUrl FlatpakResource::url() const 0636 { 0637 if (!m_resourceFile.isEmpty()) { 0638 return m_resourceFile; 0639 } 0640 0641 QUrl ret(QStringLiteral("appstream:") + appstreamId()); 0642 const AppStream::Provided::Kind AppStream_Provided_KindId = (AppStream::Provided::Kind)12; // Should be AppStream::Provided::KindId when released 0643 const auto provided = m_appdata.provided(AppStream_Provided_KindId).items(); 0644 if (!provided.isEmpty()) { 0645 QUrlQuery qq; 0646 qq.addQueryItem(u"alt"_s, provided.join(QLatin1Char(','))); 0647 ret.setQuery(qq); 0648 } 0649 return ret; 0650 } 0651 0652 QDate FlatpakResource::releaseDate() const 0653 { 0654 #if ASQ_CHECK_VERSION(1, 0, 0) 0655 if (!m_appdata.releasesPlain().isEmpty()) { 0656 auto release = m_appdata.releasesPlain().indexSafe(0).value(); 0657 #else 0658 if (const auto releases = m_appdata.releases(); !releases.isEmpty()) { 0659 auto release = releases.constFirst(); 0660 #endif 0661 return release.timestamp().date(); 0662 } 0663 0664 return {}; 0665 } 0666 0667 QString FlatpakResource::sourceIcon() const 0668 { 0669 const auto sourceItem = qobject_cast<FlatpakBackend *>(backend())->sources()->sourceById(origin()); 0670 if (!sourceItem) { 0671 qWarning() << "Could not find source " << origin(); 0672 return QStringLiteral("flatpak-discover"); 0673 } 0674 0675 const auto iconUrl = sourceItem->data(FlatpakSourcesBackend::IconUrlRole).toString(); 0676 if (iconUrl.isEmpty()) { 0677 return QStringLiteral("flatpak-discover"); 0678 } 0679 return iconUrl; 0680 } 0681 0682 QString FlatpakResource::author() const 0683 { 0684 QString name = m_appdata.developer().name(); 0685 0686 if (name.isEmpty()) { 0687 name = m_appdata.projectGroup(); 0688 } 0689 0690 return name; 0691 } 0692 0693 QStringList FlatpakResource::extends() const 0694 { 0695 return m_appdata.extends(); 0696 } 0697 0698 QSet<QString> FlatpakResource::alternativeAppstreamIds() const 0699 { 0700 const AppStream::Provided::Kind AppStream_Provided_KindId = (AppStream::Provided::Kind)12; // Should be AppStream::Provided::KindId when released 0701 const auto ret = m_appdata.provided(AppStream_Provided_KindId).items(); 0702 0703 return QSet<QString>(ret.begin(), ret.end()); 0704 } 0705 0706 QStringList FlatpakResource::mimetypes() const 0707 { 0708 return m_appdata.provided(AppStream::Provided::KindMimetype).items(); 0709 } 0710 0711 QString FlatpakResource::versionString() 0712 { 0713 QString version; 0714 if (resourceType() == Source) { 0715 return {}; 0716 } 0717 if (isInstalled()) { 0718 auto ref = qobject_cast<FlatpakBackend *>(backend())->getInstalledRefForApp(this); 0719 if (ref) { 0720 version = QString::fromUtf8(flatpak_installed_ref_get_appdata_version(ref)); 0721 } 0722 #if ASQ_CHECK_VERSION(1, 0, 0) 0723 } else if (!m_appdata.releasesPlain().isEmpty()) { 0724 const auto release = m_appdata.releasesPlain().indexSafe(0).value(); 0725 #else 0726 } else if (!m_appdata.releases().isEmpty()) { 0727 const auto release = m_appdata.releases().constFirst(); 0728 #endif 0729 version = release.version(); 0730 } else { 0731 version = m_id.branch; 0732 } 0733 0734 return AppStreamUtils::versionString(version, m_appdata); 0735 } 0736 0737 QString translateSymbolicName(const QStringView &name) 0738 { 0739 if (name == QLatin1String("host")) { 0740 return i18n("All Files"); 0741 } else if (name == QLatin1String("home")) { 0742 return i18n("Home"); 0743 } else if (name == QLatin1String("xdg-download")) { 0744 return i18n("Downloads"); 0745 } else if (name == QLatin1String("xdg-music")) { 0746 return i18n("Music"); 0747 } 0748 return name.toString(); 0749 } 0750 0751 QString FlatpakResource::eolReason() 0752 { 0753 if (!m_eolReason.has_value()) { 0754 auto futureWatcher = new QFutureWatcher<FlatpakRemoteRef *>(this); 0755 connect(futureWatcher, &QFutureWatcher<FlatpakRemoteRef *>::finished, this, [this, futureWatcher]() { 0756 g_autoptr(FlatpakRemoteRef) rref = futureWatcher->result(); 0757 if (rref) { 0758 m_eolReason = QString::fromUtf8(flatpak_remote_ref_get_eol(rref)); 0759 Q_EMIT eolReasonChanged(); 0760 } 0761 futureWatcher->deleteLater(); 0762 }); 0763 0764 futureWatcher->setFuture(QtConcurrent::run(qobject_cast<FlatpakBackend *>(backend())->threadPool(), 0765 &FlatpakRunnables::findRemoteRef, 0766 this, 0767 qobject_cast<FlatpakBackend *>(backend())->cancellable())); 0768 return {}; 0769 } 0770 return m_eolReason.value_or(QString()); 0771 } 0772 0773 QString createHtmlList(const QStringList &itemList) 0774 { 0775 QString str = QStringLiteral("<ul>"); 0776 for (const QString &itemText : std::as_const(itemList)) { 0777 str += QStringLiteral("<li>%1</li>").arg(itemText.toHtmlEscaped()); 0778 } 0779 str += QStringLiteral("</ul>"); 0780 return str; 0781 } 0782 0783 void FlatpakResource::loadPermissions() 0784 { 0785 QByteArray metaDataBytes = FlatpakRunnables::fetchMetadata(this, nullptr); 0786 0787 QTemporaryFile f; 0788 if (!f.open()) { 0789 return; 0790 } 0791 f.write(metaDataBytes); 0792 f.close(); 0793 0794 KDesktopFile parser(f.fileName()); 0795 0796 QString brief, description; 0797 0798 bool fullSessionBusAccess = false; 0799 bool fullSystemBusAccess = false; 0800 0801 const KConfigGroup contextGroup = parser.group(u"Context"_s); 0802 const QString shared = contextGroup.readEntry("shared", QString()); 0803 if (shared.contains("network"_L1)) { 0804 brief = i18n("Network Access"); 0805 description = i18n("Can access the internet"); 0806 m_permissions.append(FlatpakPermission(brief, description, u"network-wireless-symbolic"_s)); 0807 } 0808 0809 const QString sockets = contextGroup.readEntry("sockets", QString()); 0810 const QString filesystems = contextGroup.readEntry("filesystems", QString()); 0811 const auto dirs = QStringView(filesystems).split(';'_L1, Qt::SkipEmptyParts); 0812 0813 if (sockets.contains("pulseaudio"_L1) || filesystems.contains("xdg-run/pipewire-0"_L1)) { 0814 brief = i18n("Sound system access"); 0815 description = i18n("Can play audio"); 0816 m_permissions.append(FlatpakPermission(brief, description, u"audio-speakers-symbolic"_s)); 0817 } 0818 if (sockets.contains("session-bus"_L1)) { 0819 brief = i18n("Session Bus Access"); 0820 description = i18n("Can communicate with all other applications and processes run in this user account"); 0821 m_permissions.append(FlatpakPermission(brief, description, u"security-medium-symbolic"_s)); 0822 fullSessionBusAccess = true; 0823 } 0824 if (sockets.contains("system-bus"_L1)) { 0825 brief = i18n("System Bus Access"); 0826 description = i18n("Can communicate with all other applications and processes on the system"); 0827 m_permissions.append(FlatpakPermission(brief, description, u"security-medium-symbolic"_s)); 0828 fullSystemBusAccess = true; 0829 } 0830 if (sockets.contains("ssh-auth"_L1)) { 0831 brief = i18n("Remote Login Access"); 0832 description = i18n("Can initiate remote login requests using the SSH protocol"); 0833 m_permissions.append(FlatpakPermission(brief, description, u"x-shape-connection-symbolic"_s)); 0834 } 0835 if (sockets.contains("pcsc"_L1)) { 0836 brief = i18n("Smart Card Access"); 0837 description = i18n("Can integrate and communicate with smart cards"); 0838 m_permissions.append(FlatpakPermission(brief, description, u"auth-sim-symbolic"_s)); 0839 } 0840 if (sockets.contains("cups"_L1)) { 0841 brief = i18n("Printer Access"); 0842 description = i18n("Can integrate and communicate with printers"); 0843 m_permissions.append(FlatpakPermission(brief, description, u"printer-symbolic"_s)); 0844 } 0845 if (sockets.contains("gpg-agent"_L1)) { 0846 brief = i18n("GPG Agent"); 0847 description = i18n("Allows access to the GPG cryptography service, generally used for signing and reading signed documents"); 0848 m_permissions.append(FlatpakPermission(brief, description, u"document-edit-sign-encrypt"_s)); 0849 } 0850 0851 const QString features = contextGroup.readEntry("features", QString()); 0852 if (features.contains("bluetooth"_L1)) { 0853 brief = i18n("Bluetooth Access"); 0854 description = i18n("Can integrate and communicate with Bluetooth devices"); 0855 m_permissions.append(FlatpakPermission(brief, description, u"network-bluetooth-symbolic"_s)); 0856 } 0857 if (features.contains("devel"_L1)) { 0858 brief = i18n("Low-Level System Access"); 0859 description = i18n("Can make low-level system calls (e.g. ptrace)"); 0860 m_permissions.append(FlatpakPermission(brief, description, u"run-build-symbolic"_s)); 0861 } 0862 0863 const QString devices = contextGroup.readEntry("devices", QString()); 0864 if (devices.contains("all"_L1)) { 0865 brief = i18n("Device Access"); 0866 description = i18n("Can communicate with and control built-in or connected hardware devices"); 0867 m_permissions.append(FlatpakPermission(brief, description, u"device-notifier-symbolic"_s)); 0868 } 0869 if (devices.contains("kvm"_L1)) { 0870 brief = i18n("Kernel-based Virtual Machine Access"); 0871 description = i18n("Allows running other operating systems as guests in virtual machines"); 0872 m_permissions.append(FlatpakPermission(brief, description, u"virt-manager-symbolic"_s)); 0873 } 0874 0875 QStringList homeList, systemList; 0876 bool home_ro = false, home_rw = false, home_cr = false, homeAccess = false; 0877 bool system_ro = false, system_rw = false, system_cr = false, systemAccess = false; 0878 0879 bool hasHostRW = false; 0880 0881 for (const QStringView &dir : dirs) { 0882 if (dir == QLatin1String("xdg-config/kdeglobals:ro")) { 0883 // Ignore notifying about the global file being visible, since it's intended by design 0884 continue; 0885 } 0886 0887 if (dir.startsWith("xdg-run/pipewire-0"_L1)) { 0888 // This is already handled as sound system above 0889 continue; 0890 } 0891 0892 int separator = dir.lastIndexOf(':'_L1); 0893 const QStringView postfix = separator > 0 ? dir.mid(separator) : QStringView(); 0894 const QStringView symbolicName = dir.left(separator); 0895 const QString id = translateSymbolicName(symbolicName); 0896 if ((dir.contains(QLatin1String("home")) || dir.contains(QLatin1Char('~')))) { 0897 if (postfix == QLatin1String(":ro")) { 0898 homeList << i18n("%1 (read-only)", id); 0899 home_ro = true; 0900 } else if (postfix == QLatin1String(":create")) { 0901 homeList << i18n("%1 (can create files)", id); 0902 home_cr = true; 0903 } else { 0904 homeList << i18n("%1 (read & write) ", id); 0905 home_rw = true; 0906 } 0907 homeAccess = true; 0908 } else if (!hasHostRW) { 0909 if (postfix == QLatin1String(":ro")) { 0910 systemList << i18n("%1 (read-only)", id); 0911 system_ro = true; 0912 } else if (postfix == QLatin1String(":create")) { 0913 systemList << i18n("%1 (can create files)", id); 0914 system_cr = true; 0915 } else { 0916 // Once we have host in rw, no need to list the rest 0917 if (symbolicName == QLatin1String("host")) { 0918 hasHostRW = true; 0919 systemList.clear(); 0920 } 0921 0922 systemList << i18n("%1 (read & write) ", id); 0923 system_rw = true; 0924 } 0925 systemAccess = true; 0926 } 0927 } 0928 0929 QString appendText = createHtmlList(homeList); 0930 if (homeAccess) { 0931 brief = i18n("Home Folder Access"); 0932 if (home_rw && home_ro && home_cr) { 0933 description = 0934 i18n("Can read, write, and create files in the following locations in your home folder without asking permission first: %1", appendText); 0935 } else if (home_rw && !home_cr) { 0936 description = i18n("Can read and write files in the following locations in your home folder without asking permission first: %1", appendText); 0937 } else if (home_ro && !home_cr && !home_rw) { 0938 description = i18n("Can read files in the following locations in your home folder without asking permission first: %1", appendText); 0939 } else { 0940 description = i18n("Can access files in the following locations in your home folder without asking permission first: %1", appendText); 0941 } 0942 m_permissions.append(FlatpakPermission(brief, description, u"user-home-symbolic"_s)); 0943 } 0944 appendText = createHtmlList(systemList); 0945 if (systemAccess) { 0946 brief = i18n("System Folder Access"); 0947 if (system_rw && system_ro && system_cr) { 0948 description = i18n("Can read, write, and create system files in the following locations without asking permission first: %1", appendText); 0949 } else if (system_rw && !system_cr) { 0950 description = i18n("Can read and write system files in the following locations without asking permission first: %1", appendText); 0951 } else if (system_ro && !system_cr && !system_rw) { 0952 description = i18n("Can read system files in the following locations without asking permission first: %1", appendText); 0953 } else { 0954 description = i18n("Can access system files in the following locations without asking permission first: %1", appendText); 0955 } 0956 m_permissions.append(FlatpakPermission(brief, description, u"drive-harddisk-root-symbolic"_s)); 0957 } 0958 0959 if (!fullSessionBusAccess) { 0960 const KConfigGroup sessionBusGroup = parser.group(u"Session Bus Policy"_s); 0961 if (sessionBusGroup.exists()) { 0962 const QStringList busList = sessionBusGroup.keyList(); 0963 brief = i18n("Session Bus Access"); 0964 description = i18n("Can communicate with other applications and processes in the same desktop session using the following communication protocols: %1", 0965 createHtmlList(busList)); 0966 m_permissions.append(FlatpakPermission(brief, description, "plugins-symbolic"_L1)); 0967 } 0968 } 0969 0970 if (!fullSystemBusAccess) { 0971 const KConfigGroup systemBusGroup = parser.group(u"System Bus Policy"_s); 0972 if (systemBusGroup.exists()) { 0973 const QStringList busList = systemBusGroup.keyList(); 0974 brief = i18n("System Bus Access"); 0975 description = 0976 i18n("Can communicate with all applications and system services using the following communication protocols: %1", createHtmlList(busList)); 0977 m_permissions.append(FlatpakPermission(brief, description, "plugins-symbolic"_L1)); 0978 } 0979 } 0980 } 0981 0982 QString FlatpakResource::dataLocation() const 0983 { 0984 auto id = m_appdata.bundle(AppStream::Bundle::KindFlatpak).id().section('/'_L1, 0, 1); 0985 if (id.isEmpty()) { 0986 return {}; 0987 } 0988 return QDir::homePath() + QLatin1String("/.var/") + id; 0989 } 0990 0991 bool FlatpakResource::hasData() const 0992 { 0993 return !dataLocation().isEmpty() && QDir(dataLocation()).exists(); 0994 } 0995 0996 void FlatpakResource::clearUserData() 0997 { 0998 const auto location = dataLocation(); 0999 if (location.isEmpty()) { 1000 qWarning() << "Failed find location for" << name(); 1001 return; 1002 } 1003 1004 if (!QDir(location).removeRecursively()) { 1005 qWarning() << "Failed to remove location" << location; 1006 } 1007 Q_EMIT hasDataChanged(); 1008 } 1009 1010 int FlatpakResource::versionCompare(FlatpakResource *resource) const 1011 { 1012 const QString other = resource->availableVersion(); 1013 return AppStream::Utils::vercmpSimple(availableVersion(), other); 1014 } 1015 1016 QString FlatpakResource::contentRatingDescription() const 1017 { 1018 return AppStreamUtils::contentRatingDescription(m_appdata); 1019 } 1020 1021 QString FlatpakResource::contentRatingText() const 1022 { 1023 return AppStreamUtils::contentRatingText(m_appdata); 1024 } 1025 1026 AbstractResource::ContentIntensity FlatpakResource::contentRatingIntensity() const 1027 { 1028 return AppStreamUtils::contentRatingIntensity(m_appdata); 1029 } 1030 1031 uint FlatpakResource::contentRatingMinimumAge() const 1032 { 1033 return AppStreamUtils::contentRatingMinimumAge(m_appdata); 1034 } 1035 1036 QStringList FlatpakResource::topObjects() const 1037 { 1038 return s_topObjects; 1039 } 1040 1041 QStringList FlatpakResource::bottomObjects() const 1042 { 1043 return s_bottomObjects; 1044 }