diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp index 8aad9808..5e3090d8 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp @@ -1,1070 +1,1089 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FlatpakBackend.h" #include "FlatpakFetchDataJob.h" #include "FlatpakFetchUpdatesJob.h" #include "FlatpakResource.h" #include "FlatpakSourcesBackend.h" #include "FlatpakTransaction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MUON_BACKEND_PLUGIN(FlatpakBackend) FlatpakBackend::FlatpakBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(new OdrsReviewsBackend(this)) , m_fetching(false) { g_autoptr(GError) error = nullptr; m_cancellable = g_cancellable_new(); connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FlatpakBackend::updatesCountChanged); // Load flatpak installation if (!setupFlatpakInstallations(&error)) { qWarning() << "Failed to setup flatpak installations: " << error->message; } else { reloadPackageList(); checkForUpdates(); } QAction* updateAction = new QAction(this); updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update"))); updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates")); updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R)); connect(updateAction, &QAction::triggered, this, &FlatpakBackend::checkForUpdates); m_messageActions = QList() << updateAction; m_sources = new FlatpakSourcesBackend(m_flatpakInstallationSystem, m_flatpakInstallationUser, this); SourcesModel::global()->addSourcesBackend(m_sources); connect(m_reviews, &OdrsReviewsBackend::ratingsReady, this, &FlatpakBackend::announceRatingsReady); } FlatpakBackend::~FlatpakBackend() { g_object_unref(m_flatpakInstallationSystem); g_object_unref(m_flatpakInstallationUser); g_object_unref(m_cancellable); } void FlatpakBackend::announceRatingsReady() { emitRatingsReady(); const auto ids = m_reviews->appstreamIds().toSet(); foreach(AbstractResource* res, m_resources) { if (ids.contains(res->appstreamId())) { res->ratingFetched(); } } } FlatpakRemote * FlatpakBackend::getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const { auto remotes = flatpak_installation_list_remotes(installation, m_cancellable, nullptr); if (!remotes) { return nullptr; } const QByteArray comparableUrl = url.toUtf8(); for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); if (comparableUrl == flatpak_remote_get_url(remote)) { return remote; } } return nullptr; } FlatpakInstalledRef * FlatpakBackend::getInstalledRefForApp(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { AppStream::Component *component = resource->appstreamComponent(); AppStream::Component::Kind appKind = component->kind(); FlatpakInstalledRef *ref = nullptr; GPtrArray *installedApps = nullptr; g_autoptr(GError) localError = nullptr; if (!flatpakInstallation) { return ref; } ref = flatpak_installation_get_installed_ref(flatpakInstallation, resource->type() == FlatpakResource::DesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, resource->flatpakName().toStdString().c_str(), resource->arch().toStdString().c_str(), resource->branch().toStdString().c_str(), m_cancellable, &localError); // If we found installed ref this way, we can return it if (ref) { return ref; } // Otherwise go through all installed apps and try to match info we have installedApps = flatpak_installation_list_installed_refs_by_kind(flatpakInstallation, appKind == AppStream::Component::KindDesktopApp ? FLATPAK_REF_KIND_APP : FLATPAK_REF_KIND_RUNTIME, m_cancellable, &localError); if (!installedApps) { return ref; } for (uint i = 0; i < installedApps->len; i++) { FlatpakInstalledRef *installedRef = FLATPAK_INSTALLED_REF(g_ptr_array_index(installedApps, i)); // Check if the installed_reference and app_id are the same and update the app with installed metadata if (compareAppFlatpakRef(flatpakInstallation, resource, installedRef)) { return installedRef; } } // We found nothing, return nullptr return ref; } FlatpakResource * FlatpakBackend::getAppForInstalledRef(FlatpakInstallation *flatpakInstallation, FlatpakInstalledRef *ref) { foreach (FlatpakResource *resource, m_resources) { if (compareAppFlatpakRef(flatpakInstallation, resource, ref)) { return resource; } } return nullptr; } FlatpakResource * FlatpakBackend::getRuntimeForApp(FlatpakResource *resource) { FlatpakResource *runtime = nullptr; const auto runtimeInfo = resource->runtime().split(QLatin1Char('/')); if (runtimeInfo.count() != 3) { return runtime; } const QString runtimeId = QStringLiteral("runtime/") + runtimeInfo.at(0) + QLatin1Char('/') + runtimeInfo.at(2); foreach (const QString &id, m_resources.keys()) { if (id.endsWith(runtimeId)) { runtime = m_resources.value(id); break; } } // TODO if runtime wasn't found, create a new one from available info return runtime; } FlatpakResource * FlatpakBackend::addAppFromFlatpakBundle(const QUrl &url) { g_autoptr(GBytes) appstreamGz = nullptr; g_autoptr(GError) localError = nullptr; g_autoptr(GFile) file = nullptr; g_autoptr(FlatpakBundleRef) bundleRef = nullptr; AppStream::Component *asComponent = nullptr; file = g_file_new_for_path(url.toLocalFile().toStdString().c_str()); bundleRef = flatpak_bundle_ref_new(file, &localError); if (!bundleRef) { qWarning() << "Failed to load bundle: " << localError->message; return nullptr; } appstreamGz = flatpak_bundle_ref_get_appstream(bundleRef); if (appstreamGz) { g_autoptr(GZlibDecompressor) decompressor = nullptr; g_autoptr(GInputStream) streamGz = nullptr; g_autoptr(GInputStream) streamData = nullptr; g_autoptr(GBytes) appstream = nullptr; /* decompress data */ decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); streamGz = g_memory_input_stream_new_from_bytes (appstreamGz); if (!streamGz) { return nullptr; } streamData = g_converter_input_stream_new (streamGz, G_CONVERTER (decompressor)); appstream = g_input_stream_read_bytes (streamData, 0x100000, m_cancellable, &localError); if (!appstream) { qWarning() << "Failed to extract appstream metadata from bundle: " << localError->message; return nullptr; } gsize len = 0; gconstpointer data = g_bytes_get_data(appstream, &len); g_autofree gchar *appstreamContent = g_strndup((char*)data, len); g_autoptr(AsMetadata) metadata = as_metadata_new(); as_metadata_set_format_style(metadata, AS_FORMAT_STYLE_COLLECTION); as_metadata_parse(metadata, appstreamContent, AS_FORMAT_KIND_XML, &localError); if (localError) { qWarning() << "Failed to parse appstream metadata: " << localError->message; return nullptr; } g_autoptr(GPtrArray) components = as_metadata_get_components(metadata); if (g_ptr_array_index(components, 0)) { asComponent = new AppStream::Component(AS_COMPONENT(g_ptr_array_index(components, 0))); } else { qWarning() << "Failed to parse appstream metadata"; return nullptr; } } else { AsComponent *component = as_component_new(); asComponent = new AppStream::Component(component); qWarning() << "No appstream metadata in bundle"; } gsize len = 0; g_autoptr(GBytes) iconData = nullptr; g_autoptr(GBytes) metadata = nullptr; FlatpakResource *resource = new FlatpakResource(asComponent, this); metadata = flatpak_bundle_ref_get_metadata(bundleRef); QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); if (!updateAppMetadata(resource, metadataContent)) { qWarning() << "Failed to update metadata from app bundle"; return nullptr; } iconData = flatpak_bundle_ref_get_icon(bundleRef, 128); if (!iconData) { iconData = flatpak_bundle_ref_get_icon(bundleRef, 64); } if (iconData) { gsize len = 0; QPixmap pixmap; char * data = (char *)g_bytes_get_data(iconData, &len); QByteArray icon = QByteArray(data, len); pixmap.loadFromData(icon, "PNG"); resource->setBundledIcon(pixmap); } resource->setInstalledSize(flatpak_bundle_ref_get_installed_size(bundleRef)); resource->setFlatpakFileType(QStringLiteral("flatpak")); resource->setOrigin(QString::fromUtf8(flatpak_bundle_ref_get_origin(bundleRef))); resource->setResourceFile(url); resource->setState(FlatpakResource::None); resource->setType(FlatpakResource::DesktopApp); addResource(resource); return resource; } FlatpakResource * FlatpakBackend::addAppFromFlatpakRef(const QUrl &url) { auto installation = m_flatpakInstallationSystem; QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString refurl = settings.value(QStringLiteral("Flatpak Ref/Url")).toString(); g_autoptr(GError) error = NULL; g_autoptr(FlatpakRemoteRef) remoteRef = nullptr; { QFile f(url.toLocalFile()); if (!f.open(QFile::ReadOnly | QFile::Text)) { return nullptr; } QByteArray contents = f.readAll(); g_autoptr(GBytes) bytes = g_bytes_new (contents.data(), contents.size()); remoteRef = flatpak_installation_install_ref_file (installation, bytes, m_cancellable, &error); if (!remoteRef) { return nullptr; } } const auto remoteName = flatpak_remote_ref_get_remote_name(remoteRef); auto ref = FLATPAK_REF(remoteRef); AsComponent *component = as_component_new(); as_component_add_url(component, AS_URL_KIND_HOMEPAGE, settings.value(QStringLiteral("Flatpak Ref/Homepage")).toString().toStdString().c_str()); as_component_set_description(component, settings.value(QStringLiteral("Flatpak Ref/Description")).toString().toStdString().c_str(), nullptr); as_component_set_name(component, settings.value(QStringLiteral("Flatpak Ref/Title")).toString().toStdString().c_str(), nullptr); as_component_set_summary(component, settings.value(QStringLiteral("Flatpak Ref/Comment")).toString().toStdString().c_str(), nullptr); const QString iconUrl = settings.value(QStringLiteral("Flatpak Ref/Icon")).toString(); if (!iconUrl.isEmpty()) { AsIcon *icon = as_icon_new(); as_icon_set_kind(icon, AS_ICON_KIND_REMOTE); as_icon_set_url(icon, iconUrl.toStdString().c_str()); as_component_add_icon(component, icon); } AppStream::Component *asComponent = new AppStream::Component(component); auto resource = new FlatpakResource(asComponent, this); resource->setFlatpakFileType(QStringLiteral("flatpakref")); resource->setOrigin(QString::fromUtf8(remoteName)); resource->updateFromRef(ref); addResource(resource); return resource; } FlatpakResource * FlatpakBackend::addSourceFromFlatpakRepo(const QUrl &url) { QSettings settings(url.toLocalFile(), QSettings::NativeFormat); const QString gpgKey = settings.value(QStringLiteral("Flatpak Repo/GPGKey")).toString(); const QString title = settings.value(QStringLiteral("Flatpak Repo/Title")).toString(); const QString repoUrl = settings.value(QStringLiteral("Flatpak Repo/Url")).toString(); if (gpgKey.isEmpty() || title.isEmpty() || repoUrl.isEmpty()) { return nullptr; } if (gpgKey.startsWith(QStringLiteral("http://")) || gpgKey.startsWith(QStringLiteral("https://"))) { return nullptr; } AsComponent *component = as_component_new(); as_component_add_url(component, AS_URL_KIND_HOMEPAGE, settings.value(QStringLiteral("Flatpak Repo/Homepage")).toString().toStdString().c_str()); as_component_set_summary(component, settings.value(QStringLiteral("Flatpak Repo/Comment")).toString().toStdString().c_str(), nullptr); as_component_set_description(component, settings.value(QStringLiteral("Flatpak Repo/Description")).toString().toStdString().c_str(), nullptr); as_component_set_name(component, title.toStdString().c_str(), nullptr); const QString iconUrl = settings.value(QStringLiteral("Flatpak Repo/Icon")).toString(); if (!iconUrl.isEmpty()) { AsIcon *icon = as_icon_new(); as_icon_set_kind(icon, AS_ICON_KIND_REMOTE); as_icon_set_url(icon, iconUrl.toStdString().c_str()); as_component_add_icon(component, icon); } AppStream::Component *asComponent = new AppStream::Component(component); auto resource = new FlatpakResource(asComponent, this); // Use metadata only for stuff which are not common for all resources resource->addMetadata(QStringLiteral("gpg-key"), gpgKey); resource->addMetadata(QStringLiteral("repo-url"), repoUrl); resource->setBranch(settings.value(QStringLiteral("Flatpak Repo/DefaultBranch")).toString()); resource->setFlatpakName(url.fileName().remove(QStringLiteral(".flatpakrepo"))); resource->setType(FlatpakResource::Source); auto repo = flatpak_installation_get_remote_by_name(m_flatpakInstallationSystem, resource->flatpakName().toStdString().c_str(), m_cancellable, nullptr); if (!repo) { resource->setState(AbstractResource::State::None); } else { resource->setState(AbstractResource::State::Installed); } return resource; } void FlatpakBackend::addResource(FlatpakResource *resource) { // Update app with all possible information we have if (!parseMetadataFromAppBundle(resource)) { qWarning() << "Failed to parse metadata from app bundle for " << resource->name(); } auto installation = resource->scope() == FlatpakResource::System ? m_flatpakInstallationSystem : m_flatpakInstallationUser; updateAppState(installation, resource); // This will update also metadata (required runtime) updateAppSize(installation, resource); connect(resource, &FlatpakResource::stateChanged, this, &FlatpakBackend::updatesCountChanged); m_resources.insert(resource->uniqueId(), resource); } bool FlatpakBackend::compareAppFlatpakRef(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, FlatpakInstalledRef *ref) { const QString arch = QString::fromUtf8(flatpak_ref_get_arch(FLATPAK_REF(ref))); const QString branch = QString::fromUtf8(flatpak_ref_get_branch(FLATPAK_REF(ref))); FlatpakResource::ResourceType appType = flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime; FlatpakResource::Scope appScope = flatpak_installation_get_is_user(flatpakInstallation) ? FlatpakResource::User : FlatpakResource::System; g_autofree gchar *appId = nullptr; if (appType == FlatpakResource::DesktopApp) { appId = g_strdup_printf("%s.desktop", flatpak_ref_get_name(FLATPAK_REF(ref))); } else { appId = g_strdup(flatpak_ref_get_name(FLATPAK_REF(ref))); } const QString uniqueId = QString::fromUtf8("%1/%2/%3/%4/%5/%6").arg(FlatpakResource::scopeAsString(appScope)) .arg(QLatin1String("flatpak")) .arg(QString::fromUtf8(flatpak_installed_ref_get_origin(ref))) .arg(FlatpakResource::typeAsString(appType)) .arg(QString::fromUtf8(appId)) .arg(QString::fromUtf8(flatpak_ref_get_branch(FLATPAK_REF(ref)))); // Compare uniqueId first then attempt to compare what we have if (resource->uniqueId() == uniqueId) { return true; } // Check if we have information about architecture and branch, otherwise compare names only // Happens with apps which don't have appstream metadata bug got here thanks to installed desktop file if (!resource->arch().isEmpty() && !resource->branch().isEmpty()) { return resource->arch() == arch && resource->branch() == branch && (resource->flatpakName() == QString::fromUtf8(appId) || resource->flatpakName() == QString::fromUtf8(flatpak_ref_get_name(FLATPAK_REF(ref)))); } return (resource->flatpakName() == QString::fromUtf8(appId) || resource->flatpakName() == QString::fromUtf8(flatpak_ref_get_name(FLATPAK_REF(ref)))); } class FlatpakSource { public: FlatpakSource(FlatpakRemote* remote) : m_remote(remote) {} bool isEnabled() const { return !flatpak_remote_get_disabled(m_remote); } QString appstreamDir() const { g_autoptr(GFile) appstreamDir = flatpak_remote_get_appstream_dir(m_remote, nullptr); if (!appstreamDir) { qWarning() << "No appstream dir for " << flatpak_remote_get_name(m_remote); return {}; } return QString::fromUtf8(g_file_get_path(appstreamDir)); } QString name() const { return QString::fromUtf8(flatpak_remote_get_name(m_remote)); } private: FlatpakRemote* m_remote; }; bool FlatpakBackend::loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); g_autoptr(GPtrArray) remotes = flatpak_installation_list_remotes(flatpakInstallation, m_cancellable, nullptr); if (!remotes) { return false; } for (uint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = FLATPAK_REMOTE(g_ptr_array_index(remotes, i)); integrateRemote(flatpakInstallation, remote); } return true; } void FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote) { g_autoptr(GError) localError = nullptr; FlatpakSource source(remote); if (!source.isEnabled() || flatpak_remote_get_noenumerate(remote)) { return; } const QString appstreamDirPath = source.appstreamDir(); const QString appDirFileName = appstreamDirPath + QLatin1String("/appstream.xml.gz"); if (!QFile::exists(appDirFileName)) { qWarning() << "No " << appDirFileName << " appstream metadata found for " << source.name(); return; } g_autoptr(AsMetadata) metadata = as_metadata_new(); g_autoptr(GFile) file = g_file_new_for_path(appDirFileName.toStdString().c_str()); as_metadata_set_format_style (metadata, AS_FORMAT_STYLE_COLLECTION); as_metadata_parse_file(metadata, file, AS_FORMAT_KIND_XML, &localError); if (localError) { qWarning() << "Failed to parse appstream metadata " << localError->message; return; } g_autoptr(GPtrArray) components = as_metadata_get_components(metadata); const FlatpakResource::Scope scope = flatpak_installation_get_is_user(flatpakInstallation) ? FlatpakResource::User : FlatpakResource::System; for (uint i = 0; i < components->len; i++) { AsComponent *component = AS_COMPONENT(g_ptr_array_index(components, i)); AppStream::Component *appstreamComponent = new AppStream::Component(component); FlatpakResource *resource = new FlatpakResource(appstreamComponent, this); resource->setScope(scope); resource->setIconPath(appstreamDirPath); resource->setOrigin(source.name()); addResource(resource); } } bool FlatpakBackend::loadInstalledApps(FlatpakInstallation *flatpakInstallation) { QDir dir; QString pathExports; QString pathApps; g_autoptr(GFile) path = nullptr; if (!flatpakInstallation) { return false; } // List installed applications from installed desktop files path = flatpak_installation_get_path(flatpakInstallation); pathExports = QString::fromUtf8(g_file_get_path(path)) + QLatin1String("/exports/"); pathApps = pathExports + QLatin1String("share/applications/"); dir = QDir(pathApps); if (dir.exists()) { foreach (const QString &file, dir.entryList(QDir::NoDotAndDotDot | QDir::Files)) { QString fnDesktop; AsComponent *component; g_autoptr(GError) localError = nullptr; g_autoptr(GFile) desktopFile = nullptr; g_autoptr(AsMetadata) metadata = as_metadata_new(); if (file == QLatin1String("mimeinfo.cache")) { continue; } fnDesktop = pathApps + file; desktopFile = g_file_new_for_path(fnDesktop.toStdString().c_str()); if (!desktopFile) { qWarning() << "Couldn't open " << fnDesktop << " :" << localError->message; continue; } as_metadata_parse_file(metadata, desktopFile, AS_FORMAT_KIND_DESKTOP_ENTRY, &localError); if (localError) { qWarning() << "Failed to parse appstream metadata " << localError->message; continue; } component = as_metadata_get_component(metadata); AppStream::Component *appstreamComponent = new AppStream::Component(component); FlatpakResource *resource = new FlatpakResource(appstreamComponent, this); resource->setScope(flatpak_installation_get_is_user(flatpakInstallation) ? FlatpakResource::User : FlatpakResource::System); resource->setIconPath(pathExports); resource->setType(FlatpakResource::DesktopApp); resource->setState(AbstractResource::Installed); // Go through apps we already know about from appstream metadata bool resourceExists = false; foreach (FlatpakResource *res, m_resources) { // Compare the only information we have if (res->appstreamId() == QString::fromUtf8("%1.desktop").arg(resource->appstreamId()) && res->name() == resource->name()) { resourceExists = true; res->setScope(resource->scope()); res->setState(resource->state()); break; } } if (!resourceExists) { addResource(resource); } else { resource->deleteLater(); } } } return true; } void FlatpakBackend::loadLocalUpdates(FlatpakInstallation *flatpakInstallation) { g_autoptr(GError) localError = nullptr; g_autoptr(GPtrArray) refs = nullptr; refs = flatpak_installation_list_installed_refs(flatpakInstallation, m_cancellable, &localError); if (!refs) { qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; return; } for (uint i = 0; i < refs->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(refs, i)); const gchar *latestCommit = flatpak_installed_ref_get_latest_commit(ref); if (!latestCommit) { qWarning() << "Couldn'g get latest commit for " << flatpak_ref_get_name(FLATPAK_REF(ref)); } const gchar *commit = flatpak_ref_get_commit(FLATPAK_REF(ref)); if (g_strcmp0(commit, latestCommit) == 0) { continue; } FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } void FlatpakBackend::loadRemoteUpdates(FlatpakInstallation *flatpakInstallation) { FlatpakFetchUpdatesJob *job = new FlatpakFetchUpdatesJob(flatpakInstallation); connect(job, &FlatpakFetchUpdatesJob::finished, job, &FlatpakFetchUpdatesJob::deleteLater); connect(job, &FlatpakFetchUpdatesJob::jobFetchUpdatesFinished, this, &FlatpakBackend::onFetchUpdatesFinished); job->start(); } void FlatpakBackend::onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates) { g_autoptr(GPtrArray) fetchedUpdates = updates; for (uint i = 0; i < fetchedUpdates->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i)); FlatpakResource *resource = getAppForInstalledRef(flatpakInstallation, ref); if (resource) { resource->setState(AbstractResource::Upgradeable); updateAppSize(flatpakInstallation, resource); } } } bool FlatpakBackend::parseMetadataFromAppBundle(FlatpakResource *resource) { g_autoptr(FlatpakRef) ref = nullptr; g_autoptr(GError) localError = nullptr; AppStream::Bundle bundle = resource->appstreamComponent()->bundle(AppStream::Bundle::KindFlatpak); // Get arch/branch/commit/name from FlatpakRef if (!bundle.isEmpty()) { ref = flatpak_ref_parse(bundle.id().toStdString().c_str(), &localError); if (!ref) { qWarning() << "Failed to parse " << bundle.id() << localError->message; return false; } else { resource->updateFromRef(ref); } } return true; } void FlatpakBackend::reloadPackageList() { setFetching(true); // Load applications from appstream metadata if (!loadAppsFromAppstreamData(m_flatpakInstallationSystem)) { qWarning() << "Failed to load packages from appstream data from system installation"; } if (!loadAppsFromAppstreamData(m_flatpakInstallationUser)) { qWarning() << "Failed to load packages from appstream data from user installation"; } // Load installed applications and update existing resources with info from installed application if (!loadInstalledApps(m_flatpakInstallationSystem)) { qWarning() << "Failed to load installed packages from system installation"; } if (!loadInstalledApps(m_flatpakInstallationUser)) { qWarning() << "Failed to load installed packages from user installation"; } setFetching(false); } bool FlatpakBackend::setupFlatpakInstallations(GError **error) { m_flatpakInstallationSystem = flatpak_installation_new_system(m_cancellable, error); if (!m_flatpakInstallationSystem) { return false; } m_flatpakInstallationUser = flatpak_installation_new_user(m_cancellable, error); if (!m_flatpakInstallationUser) { return false; } return true; } void FlatpakBackend::updateAppInstalledMetadata(FlatpakInstalledRef *installedRef, FlatpakResource *resource) { // Update the rest resource->updateFromRef(FLATPAK_REF(installedRef)); resource->setInstalledSize(flatpak_installed_ref_get_installed_size(installedRef)); resource->setOrigin(QString::fromUtf8(flatpak_installed_ref_get_origin(installedRef))); resource->setState(AbstractResource::Installed); } bool FlatpakBackend::updateAppMetadata(FlatpakInstallation* flatpakInstallation, FlatpakResource *resource) { QByteArray metadataContent; g_autoptr(GFile) installationPath = nullptr; g_autoptr(GError) localError = nullptr; if (resource->type() != FlatpakResource::DesktopApp) { return true; } installationPath = flatpak_installation_get_path(flatpakInstallation); const QString path = QString::fromUtf8(g_file_get_path(installationPath)) + QString::fromUtf8("/app/%1/%2/%3/active/metadata").arg(resource->flatpakName()).arg(resource->arch()).arg(resource->branch()); if (QFile::exists(path)) { QFile file(path); if (file.open(QFile::ReadOnly | QFile::Text)) { metadataContent = file.readAll(); } if (metadataContent.isEmpty()) { qWarning() << "Failed to get metadata file"; return false; } return updateAppMetadata(resource, metadataContent); } else { FlatpakFetchDataJob *job = new FlatpakFetchDataJob(flatpakInstallation, resource, FlatpakFetchDataJob::FetchMetadata); connect(job, &FlatpakFetchDataJob::finished, job, &FlatpakFetchDataJob::deleteLater); connect(job, &FlatpakFetchDataJob::jobFetchMetadataFinished, this, &FlatpakBackend::onFetchMetadataFinished); job->start(); // Return false to indicate we cannot continue (right now used only in updateAppSize()) return false; } } void FlatpakBackend::onFetchMetadataFinished(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, const QByteArray &metadata) { updateAppMetadata(resource, metadata); // Right now we attempt to update metadata for calculating the size so call updateSizeFromRemote() // as it's what we want. In future if there are other reason to update metadata we will need to somehow // distinguish betwen these calls updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppMetadata(FlatpakResource *resource, const QByteArray &data) { // Save the content to temporary file QTemporaryFile tempFile; tempFile.setAutoRemove(false); if (!tempFile.open()) { qWarning() << "Failed to get metadata file"; return false; } tempFile.write(data); tempFile.close(); // Parse the temporary file QSettings setting(tempFile.fileName(), QSettings::NativeFormat); setting.beginGroup(QLatin1String("Application")); // Set the runtime in form of name/arch/version which can be later easily parsed resource->setRuntime(setting.value(QLatin1String("runtime")).toString()); // TODO get more information? tempFile.remove(); return true; } bool FlatpakBackend::updateAppSize(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Check if the size is already set, we should also distiguish between download and installed size, // right now it doesn't matter whether we get size for installed or not installed app, but if we // start making difference then for not installed app check download and install size separately if (resource->state() == AbstractResource::Installed) { // The size appears to be already set (from updateAppInstalledMetadata() apparently) if (resource->installedSize() > 0) { return true; } } else { if (resource->installedSize() > 0 && resource->downloadSize() > 0) { return true; } } // Check if we know the needed runtime which is needed for calculating the size if (resource->runtime().isEmpty()) { if (!updateAppMetadata(flatpakInstallation, resource)) { return false; } } return updateAppSizeFromRemote(flatpakInstallation, resource); } bool FlatpakBackend::updateAppSizeFromRemote(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { // Calculate the runtime size FlatpakResource *runtime = nullptr; if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { runtime = getRuntimeForApp(resource); if (runtime) { // Re-check runtime state if case a new one was created updateAppState(flatpakInstallation, runtime); if (!runtime->isInstalled()) { if (!updateAppSize(flatpakInstallation, runtime)) { qWarning() << "Failed to get runtime size needed for total size of " << resource->name(); return false; } // Set required download size to include runtime size even now, in case we fail to // get the app size (e.g. when installing bundles where download size is 0) resource->setDownloadSize(runtime->downloadSize()); } } } if (resource->state() == AbstractResource::Installed) { g_autoptr(FlatpakInstalledRef) ref = nullptr; ref = getInstalledRefForApp(flatpakInstallation, resource); if (!ref) { qWarning() << "Failed to get installed size of " << resource->name(); return false; } resource->setInstalledSize(flatpak_installed_ref_get_installed_size(ref)); } else { if (resource->origin().isEmpty()) { qWarning() << "Failed to get size of " << resource->name() << " because of missing origin"; return false; } FlatpakFetchDataJob *job = new FlatpakFetchDataJob(flatpakInstallation, resource, FlatpakFetchDataJob::FetchSize); connect(job, &FlatpakFetchDataJob::finished, job, &FlatpakFetchDataJob::deleteLater); connect(job, &FlatpakFetchDataJob::jobFetchSizeFinished, this, &FlatpakBackend::onFetchSizeFinished); connect(job, &FlatpakFetchDataJob::jobFetchSizeFailed, [resource] () { - resource->setDownloadSize(-2); - resource->setInstalledSize(-2); + resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::UnknownOrFailed); + resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::UnknownOrFailed); }); job->start(); } return true; } void FlatpakBackend::onFetchSizeFinished(FlatpakResource *resource, guint64 downloadSize, guint64 installedSize) { FlatpakResource *runtime = nullptr; if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { runtime = getRuntimeForApp(resource); } if (runtime && !runtime->isInstalled()) { resource->setDownloadSize(runtime->downloadSize() + downloadSize); resource->setInstalledSize(installedSize); } else { resource->setDownloadSize(downloadSize); resource->setInstalledSize(installedSize); } } void FlatpakBackend::updateAppState(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource) { FlatpakInstalledRef *ref = getInstalledRefForApp(flatpakInstallation, resource); if (ref) { // If the app is installed, we can set information about commit, arch etc. updateAppInstalledMetadata(ref, resource); } else { // TODO check if the app is actuall still available resource->setState(AbstractResource::None); } } void FlatpakBackend::setFetching(bool fetching) { if (m_fetching != fetching) { m_fetching = fetching; emit fetchingChanged(); } } int FlatpakBackend::updatesCount() const { return m_updater->updatesCount(); } ResultsStream * FlatpakBackend::search(const AbstractResourcesBackend::Filters &filter) { QVector ret; foreach(AbstractResource* r, m_resources) { if (qobject_cast(r)->type() == FlatpakResource::Runtime && filter.state != AbstractResource::Upgradeable) { continue; } if (r->name().contains(filter.search, Qt::CaseInsensitive) || r->comment().contains(filter.search, Qt::CaseInsensitive)) { ret += r; } } return new ResultsStream(QStringLiteral("FlatpakStream"), ret); } ResultsStream * FlatpakBackend::findResourceByPackageName(const QUrl &url) { QVector resources; if (url.scheme() == QLatin1String("appstream")) { if (url.host().isEmpty()) passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString())); else { foreach(FlatpakResource* res, m_resources) { if (QString::compare(res->appstreamId(), url.host(), Qt::CaseInsensitive)==0) resources << res; } } } return new ResultsStream(QStringLiteral("FlatpakStream"), resources); } AbstractBackendUpdater * FlatpakBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend * FlatpakBackend::reviewsBackend() const { return m_reviews; } FlatpakInstallation * FlatpakBackend::flatpakInstallationForAppScope(FlatpakResource::Scope appScope) const { if (appScope == FlatpakResource::Scope::System) { return m_flatpakInstallationSystem; } else { return m_flatpakInstallationUser; } } void FlatpakBackend::installApplication(AbstractResource *app, const AddonList &addons) { + Q_UNUSED(addons); + FlatpakResource *resource = qobject_cast(app); if (resource->type() == FlatpakResource::Source) { // Let source backend handle this FlatpakRemote *remote = m_sources->installSource(resource); if (remote) { resource->setState(AbstractResource::Installed); integrateRemote(m_flatpakInstallationSystem, remote); } return; } FlatpakTransaction *transaction = nullptr; FlatpakInstallation *installation = resource->scope() == FlatpakResource::System ? m_flatpakInstallationSystem : m_flatpakInstallationUser; - FlatpakResource *runtime = getRuntimeForApp(resource); - if (runtime && !runtime->isInstalled()) { - transaction = new FlatpakTransaction(installation, resource, runtime, addons, Transaction::InstallRole); + if (resource->propertyState(FlatpakResource::RequiredRuntime) == FlatpakResource::NotKnownYet) { + transaction = new FlatpakTransaction(installation, resource, Transaction::InstallRole, true); + connect(resource, &FlatpakResource::propertyStateChanged, [resource, transaction, this] (FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState state) { + if (kind != FlatpakResource::RequiredRuntime) { + return; + } + + if (state == FlatpakResource::AlreadyKnown) { + FlatpakResource *runtime = getRuntimeForApp(resource); + if (runtime && !runtime->isInstalled()) { + transaction->setRuntime(runtime); + } + } + transaction->start(); + }); } else { - transaction = new FlatpakTransaction(installation, resource, addons, Transaction::InstallRole); + FlatpakResource *runtime = getRuntimeForApp(resource); + if (runtime && !runtime->isInstalled()) { + transaction = new FlatpakTransaction(installation, resource, runtime, Transaction::InstallRole); + } else { + transaction = new FlatpakTransaction(installation, resource, Transaction::InstallRole); + } } connect(transaction, &FlatpakTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppState(installation, resource); } }); } void FlatpakBackend::installApplication(AbstractResource *app) { installApplication(app, {}); } void FlatpakBackend::removeApplication(AbstractResource *app) { FlatpakResource *resource = qobject_cast(app); if (resource->type() == FlatpakResource::Source) { // Let source backend handle this if (m_sources->removeSource(resource->flatpakName())) { resource->setState(AbstractResource::None); } return; } FlatpakInstallation *installation = resource->scope() == FlatpakResource::System ? m_flatpakInstallationSystem : m_flatpakInstallationUser; FlatpakTransaction *transaction = new FlatpakTransaction(installation, resource, Transaction::RemoveRole); connect(transaction, &FlatpakTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppSize(installation, resource); } }); } void FlatpakBackend::checkForUpdates() { // Load local updates, comparing current and latest commit loadLocalUpdates(m_flatpakInstallationSystem); loadLocalUpdates(m_flatpakInstallationUser); // Load updates from remote repositories loadRemoteUpdates(m_flatpakInstallationSystem); loadRemoteUpdates(m_flatpakInstallationUser); } AbstractResource * FlatpakBackend::resourceForFile(const QUrl &url) { if ((!url.path().endsWith(QLatin1String(".flatpakref")) && !url.path().endsWith(QLatin1String(".flatpak")) && !url.path().endsWith(QLatin1String(".flatpakrepo"))) || !url.isLocalFile()) { return nullptr; } FlatpakResource *resource = nullptr; if (url.path().endsWith(QLatin1String(".flatpak"))) { resource = addAppFromFlatpakBundle(url); } else if (url.path().endsWith(QLatin1String(".flatpakref"))) { resource = addAppFromFlatpakRef(url); } else { resource = addSourceFromFlatpakRepo(url); } return resource; } #include "FlatpakBackend.moc" diff --git a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp index 3bd6680f..cb748962 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakResource.cpp @@ -1,555 +1,576 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FlatpakResource.h" #include "FlatpakBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FlatpakResource::FlatpakResource(AppStream::Component *component, FlatpakBackend *parent) : AbstractResource(parent) , m_appdata(component) - , m_downloadSize(-1) - , m_installedSize(-1) + , m_downloadSize(0) + , m_installedSize(0) + , m_propertyStates({{DownloadSize, NotKnownYet}, {InstalledSize, NotKnownYet},{RequiredRuntime, NotKnownYet}}) , m_scope(FlatpakResource::System) , m_state(AbstractResource::None) , m_type(FlatpakResource::DesktopApp) { // Start fetching remote icons during initialization const auto icons = m_appdata->icons(); if (!icons.isEmpty()) { foreach (const AppStream::Icon &icon, icons) { if (icon.kind() == AppStream::Icon::KindRemote) { const QString fileName = QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) .arg(icon.url().fileName()); if (!QFileInfo::exists(fileName)) { const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); // Create $HOME/.cache/discover/icons folder cacheDir.mkdir(QStringLiteral("icons")); QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, [this, icon, fileName, manager] (QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray iconData = reply->readAll(); QFile file(fileName); if (file.open(QIODevice::WriteOnly)) { file.write(iconData); } file.close(); Q_EMIT iconChanged(); } manager->deleteLater(); }); manager->get(QNetworkRequest(icon.url())); } } } } } AppStream::Component *FlatpakResource::appstreamComponent() const { return m_appdata; } QList FlatpakResource::addonsInformation() { return m_addons; } QString FlatpakResource::availableVersion() const { // TODO check if there is actually version available QString version = branch(); if (version.isEmpty()) { version = i18n("Unknown"); } return version; } QString FlatpakResource::appstreamId() const { return m_appdata->id(); } QString FlatpakResource::arch() const { return m_arch; } QString FlatpakResource::branch() const { return m_branch; } bool FlatpakResource::canExecute() const { return (m_type == DesktopApp && (m_state == AbstractResource::Installed || m_state == AbstractResource::Upgradeable)); } void FlatpakResource::updateFromRef(FlatpakRef* ref) { setArch(QString::fromUtf8(flatpak_ref_get_arch(ref))); setBranch(QString::fromUtf8(flatpak_ref_get_branch(ref))); setCommit(QString::fromUtf8(flatpak_ref_get_commit(ref))); setFlatpakName(QString::fromUtf8(flatpak_ref_get_name(ref))); setType(flatpak_ref_get_kind(ref) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime); } QStringList FlatpakResource::categories() { auto cats = m_appdata->categories(); if (m_appdata->kind() != AppStream::Component::KindAddon) cats.append(QStringLiteral("Application")); return cats; } QString FlatpakResource::comment() { const auto summary = m_appdata->summary(); if (!summary.isEmpty()) { return summary; } return QString(); } QString FlatpakResource::commit() const { return m_commit; } int FlatpakResource::downloadSize() const { return m_downloadSize; } QStringList FlatpakResource::executables() const { // return m_appdata->provided(AppStream::Provided::KindBinary).items(); return QStringList(); } QVariant FlatpakResource::icon() const { QIcon ret; const auto icons = m_appdata->icons(); if (!m_bundledIcon.isNull()) { ret = QIcon(m_bundledIcon); } else if (icons.isEmpty()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } else foreach(const AppStream::Icon &icon, icons) { QStringList stock; QString url = QString::fromUtf8("%1/icons/").arg(m_iconPath); switch (icon.kind()) { case AppStream::Icon::KindLocal: case AppStream::Icon::KindCached: url += icon.url().toLocalFile(); if (QFileInfo::exists(url)) { ret.addFile(url); } else { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } break; case AppStream::Icon::KindStock: stock += icon.name(); break; case AppStream::Icon::KindRemote: const QString fileName = QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)) .arg(icon.url().fileName()); if (QFileInfo::exists(fileName)) { ret.addFile(fileName); } else { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); break; } } if (ret.isNull() && !stock.isEmpty()) { ret = QIcon::fromTheme(stock.first(), QIcon::fromTheme(QStringLiteral("package-x-generic"))); } } return ret; } QString FlatpakResource::installedVersion() const { // TODO check if there is actually version available QString version = branch(); if (version.isEmpty()) { version = i18n("Unknown"); } return version; } int FlatpakResource::installedSize() const { return m_installedSize; } bool FlatpakResource::isTechnical() const { return m_type == FlatpakResource::Runtime; } QUrl FlatpakResource::homepage() { return m_appdata->url(AppStream::Component::UrlKindHomepage); } QString FlatpakResource::flatpakFileType() const { return m_flatpakFileType; } QString FlatpakResource::flatpakName() const { // If the flatpak name is not known (known only for installed apps), then use // appstream id instead; if (m_flatpakName.isEmpty()) { return m_appdata->id(); } return m_flatpakName; } QString FlatpakResource::license() { return m_appdata->projectLicense(); } QString FlatpakResource::longDescription() { return m_appdata->description(); } QString FlatpakResource::name() { QString name = m_appdata->name(); if (name.isEmpty()) { name = m_appdata->id(); } if (name.startsWith(QLatin1String("(Nightly) "))) { return name.mid(10); } return name; } QString FlatpakResource::origin() const { return m_origin; } QString FlatpakResource::packageName() const { return flatpakName(); } +FlatpakResource::PropertyState FlatpakResource::propertyState(FlatpakResource::PropertyKind kind) const +{ + return m_propertyStates[kind]; +} + QUrl FlatpakResource::resourceFile() const { return m_resourceFile; } QString FlatpakResource::runtime() const { return m_runtime; } static QUrl imageOfKind(const QList &images, AppStream::Image::Kind kind) { QUrl ret; Q_FOREACH (const AppStream::Image &i, images) { if (i.kind() == kind) { ret = i.url(); break; } } return ret; } static QUrl screenshot(AppStream::Component *comp, AppStream::Image::Kind kind) { QUrl ret; Q_FOREACH (const AppStream::Screenshot &s, comp->screenshots()) { ret = imageOfKind(s.images(), kind); if (s.isDefault() && !ret.isEmpty()) break; } return ret; } FlatpakResource::Scope FlatpakResource::scope() const { return m_scope; } QString FlatpakResource::scopeAsString() const { return m_scope == System ? QLatin1String("system") : QLatin1String("user"); } QUrl FlatpakResource::screenshotUrl() { return screenshot(m_appdata, AppStream::Image::KindSource); } QString FlatpakResource::section() { return QString(); } int FlatpakResource::size() { if (m_state == Installed) { return m_installedSize; } else { return m_downloadSize; } } QString FlatpakResource::sizeDescription() { KFormat f; if (!isInstalled() || canUpgrade()) { - if (downloadSize() == -1 || installedSize() == -1) { + if (propertyState(DownloadSize) == NotKnownYet || propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); - } else if (downloadSize() == -2 || installedSize() == -2) { + } else if (propertyState(DownloadSize) == UnknownOrFailed || propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 to download, %2 on disk", f.formatByteSize(downloadSize()), f.formatByteSize(installedSize())); } } else { - if (installedSize() == -1) { + if (propertyState(InstalledSize) == NotKnownYet) { return i18n("Retrieving size information"); - } else if (installedSize() == -2) { + } else if (propertyState(InstalledSize) == UnknownOrFailed) { return i18n("Unknown size"); } else { return i18nc("@info app size", "%1 on disk", f.formatByteSize(installedSize())); } } } AbstractResource::State FlatpakResource::state() { return m_state; } QUrl FlatpakResource::thumbnailUrl() { return screenshot(m_appdata, AppStream::Image::KindThumbnail); } FlatpakResource::ResourceType FlatpakResource::type() const { return m_type; } QString FlatpakResource::typeAsString() const { switch (m_type) { case FlatpakResource::DesktopApp: return QLatin1String("app"); break; case FlatpakResource::Runtime: return QLatin1String("runtime"); break; } return QLatin1String("app"); } QString FlatpakResource::uniqueId() const { // Build uniqueId const QString scope = m_scope == System ? QLatin1String("system") : QLatin1String("user"); return QString::fromUtf8("%1/%2/%3/%4/%5/%6").arg(scope) .arg(QLatin1String("flatpak")) .arg(origin()) .arg(typeAsString()) .arg(m_appdata->id()) .arg(branch()); } void FlatpakResource::invokeApplication() const { g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; const FlatpakBackend *p = static_cast(parent()); if (!flatpak_installation_launch(p->flatpakInstallationForAppScope(scope()), flatpakName().toStdString().c_str(), arch().toStdString().c_str(), branch().toStdString().c_str(), nullptr, cancellable, &localError)) { qWarning() << "Failed to launch " << m_appdata->name() << ": " << localError->message; } } void FlatpakResource::fetchChangelog() { QString log = longDescription(); log.replace(QLatin1Char('\n'), QLatin1String("
")); emit changelogFetched(log); } void FlatpakResource::fetchScreenshots() { QList thumbnails, screenshots; Q_FOREACH (const AppStream::Screenshot &s, m_appdata->screenshots()) { const QUrl thumbnail = imageOfKind(s.images(), AppStream::Image::KindThumbnail); const QUrl plain = imageOfKind(s.images(), AppStream::Image::KindSource); if (plain.isEmpty()) qWarning() << "invalid screenshot for" << name(); screenshots << plain; thumbnails << (thumbnail.isEmpty() ? plain : thumbnail); } Q_EMIT screenshotsFetched(thumbnails, screenshots); } void FlatpakResource::setArch(const QString &arch) { m_arch = arch; } void FlatpakResource::setBranch(const QString &branch) { m_branch = branch; } void FlatpakResource::setBundledIcon(const QPixmap &pixmap) { m_bundledIcon = pixmap; } void FlatpakResource::setCommit(const QString &commit) { m_commit = commit; } void FlatpakResource::setDownloadSize(int size) { m_downloadSize = size; + + setPropertyState(DownloadSize, AlreadyKnown); + Q_EMIT sizeChanged(); } void FlatpakResource::setFlatpakFileType(const QString &fileType) { m_flatpakFileType = fileType; } void FlatpakResource::setFlatpakName(const QString &name) { m_flatpakName = name; } void FlatpakResource::setIconPath(const QString &path) { m_iconPath = path; } void FlatpakResource::setInstalledSize(int size) { m_installedSize = size; + + setPropertyState(InstalledSize, AlreadyKnown); + Q_EMIT sizeChanged(); } void FlatpakResource::setOrigin(const QString &origin) { m_origin = origin; } +void FlatpakResource::setPropertyState(FlatpakResource::PropertyKind kind, FlatpakResource::PropertyState state) +{ + m_propertyStates[kind] = state; + + Q_EMIT propertyStateChanged(kind, state); +} + void FlatpakResource::setResourceFile(const QUrl &url) { m_resourceFile = url; } void FlatpakResource::setRuntime(const QString &runtime) { m_runtime = runtime; + + setPropertyState(RequiredRuntime, AlreadyKnown); } void FlatpakResource::setScope(FlatpakResource::Scope scope) { m_scope = scope; } void FlatpakResource::setState(AbstractResource::State state) { if (m_state != state) { m_state = state; Q_EMIT stateChanged(); } } void FlatpakResource::setType(FlatpakResource::ResourceType type) { m_type = type; } // void FlatpakResource::setAddons(const AddonList& addons) // { // Q_FOREACH (const QString& toInstall, addons.addonsToInstall()) { // setAddonInstalled(toInstall, true); // } // Q_FOREACH (const QString& toRemove, addons.addonsToRemove()) { // setAddonInstalled(toRemove, false); // } // } // void FlatpakResource::setAddonInstalled(const QString& addon, bool installed) // { // for(auto & elem : m_addons) { // if(elem.name() == addon) { // elem.setInstalled(installed); // } // } // } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakResource.h b/libdiscover/backends/FlatpakBackend/FlatpakResource.h index b2be7494..1c3ba8e7 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakResource.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakResource.h @@ -1,153 +1,171 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef FLATPAKRESOURCE_H #define FLATPAKRESOURCE_H #include extern "C" { #include } #include #include class AddonList; class FlatpakBackend; class FlatpakResource : public AbstractResource { Q_OBJECT public: explicit FlatpakResource(AppStream::Component *component, FlatpakBackend *parent); + enum PropertyKind { + DownloadSize = 0, + InstalledSize, + RequiredRuntime + }; + + enum PropertyState { + NotKnownYet = 0, + AlreadyKnown, + UnknownOrFailed, + }; + enum ResourceType { DesktopApp = 0, Runtime, Source }; enum Scope { System = 0, User }; Q_ENUM(Scope) static QString typeAsString(ResourceType type) { if (type == DesktopApp) { return QLatin1String("app"); } return QLatin1String("runtime"); } static QString scopeAsString(Scope scope) { if (scope == System) { return QLatin1String("system"); } return QLatin1String("user"); } AppStream::Component *appstreamComponent() const; QList addonsInformation() override; QString availableVersion() const override; QString appstreamId() const override; QString arch() const; QString branch() const; bool canExecute() const override; QStringList categories() override; QString comment() override; QString commit() const; int downloadSize() const; QStringList executables() const override; QVariant icon() const override; QString installedVersion() const override; int installedSize() const; bool isTechnical() const override; QUrl homepage() override; QString flatpakFileType() const; QString flatpakName() const; QString license() override; QString longDescription() override; QString name() override; QString origin() const override; QString packageName() const override; + PropertyState propertyState(PropertyKind kind) const; QUrl resourceFile() const; QString runtime() const; QUrl screenshotUrl() override; Scope scope() const; QString scopeAsString() const; QString section() override; int size() override; QString sizeDescription() override; AbstractResource::State state() override; QUrl thumbnailUrl() override; ResourceType type() const; QString typeAsString() const; QString uniqueId() const; void invokeApplication() const override; void fetchChangelog() override; void fetchScreenshots() override; void setArch(const QString &arch); void setBranch(const QString &branch); void setBundledIcon(const QPixmap &pixmap); void setCommit(const QString &commit); void setDownloadSize(int size); void setIconPath(const QString &path); void setInstalledSize(int size); void setFlatpakFileType(const QString &fileType); void setFlatpakName(const QString &name); void setOrigin(const QString &origin); + void setPropertyState(PropertyKind kind, PropertyState state); void setResourceFile(const QUrl &url); void setRuntime(const QString &runtime); void setScope(Scope scope); void setState(State state); void setType(ResourceType type); // void setAddons(const AddonList& addons); // void setAddonInstalled(const QString& addon, bool installed); void updateFromRef(FlatpakRef* ref); +Q_SIGNALS: + void propertyStateChanged(PropertyKind kind, PropertyState state); + public: QList m_addons; AppStream::Component *m_appdata; FlatpakRefKind m_flatpakRefKind; QString m_arch; QString m_branch; QPixmap m_bundledIcon; QString m_commit; int m_downloadSize; QString m_flatpakFileType; QString m_flatpakName; QString m_iconPath; int m_installedSize; QString m_origin; + QHash m_propertyStates; QUrl m_resourceFile; QString m_runtime; Scope m_scope; AbstractResource::State m_state; ResourceType m_type; }; #endif // FLATPAKRESOURCE_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp index 7ec25db3..3a0e1a1f 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.cpp @@ -1,167 +1,164 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "FlatpakTransaction.h" #include "FlatpakBackend.h" #include "FlatpakResource.h" #include "FlatpakTransactionJob.h" #include #include #include -FlatpakTransaction::FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, Role role) - : FlatpakTransaction(installation, app, nullptr, {}, role) +FlatpakTransaction::FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, Role role, bool delayStart) + : FlatpakTransaction(installation, app, nullptr, role, delayStart) { } -FlatpakTransaction::FlatpakTransaction(FlatpakInstallation* installation, FlatpakResource *app, FlatpakResource *runtime, Transaction::Role role) - : FlatpakTransaction(installation, app, runtime, {}, role) -{ -} - -FlatpakTransaction::FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, const AddonList &addons, Transaction::Role role) - : FlatpakTransaction(installation, app, nullptr, addons, role) -{ -} - -FlatpakTransaction::FlatpakTransaction(FlatpakInstallation* installation, FlatpakResource *app, FlatpakResource *runtime, const AddonList &list, Transaction::Role role) - : Transaction(app->backend(), app, role, list) +FlatpakTransaction::FlatpakTransaction(FlatpakInstallation* installation, FlatpakResource *app, FlatpakResource *runtime, Transaction::Role role, bool delayStart) + : Transaction(app->backend(), app, role, {}) , m_appJobFinished(false) , m_runtimeJobFinished(false) , m_appJobProgress(0) , m_runtimeJobProgress(0) , m_app(app) , m_runtime(runtime) , m_installation(installation) { setCancellable(true); TransactionModel::global()->addTransaction(this); - QTimer::singleShot(0, this, &FlatpakTransaction::start); + if (!delayStart) { + QTimer::singleShot(0, this, &FlatpakTransaction::start); + } } FlatpakTransaction::~FlatpakTransaction() { } void FlatpakTransaction::cancel() { m_appJob->cancel(); if (m_runtime) { m_runtimeJob->cancel(); } TransactionModel::global()->cancelTransaction(this); } +void FlatpakTransaction::setRuntime(FlatpakResource *runtime) +{ + m_runtime = runtime; +} + void FlatpakTransaction::start() { if (m_runtime) { m_runtimeJob = new FlatpakTransactionJob(m_installation, m_runtime, role()); connect(m_runtimeJob, &FlatpakTransactionJob::finished, m_runtimeJob, &FlatpakTransactionJob::deleteLater); connect(m_runtimeJob, &FlatpakTransactionJob::jobFinished, this, &FlatpakTransaction::onRuntimeJobFinished); connect(m_runtimeJob, &FlatpakTransactionJob::progressChanged, this, &FlatpakTransaction::onRuntimeJobProgressChanged); m_runtimeJob->start(); } else { // We can mark runtime job as finished as we don't need to start it m_runtimeJobFinished = true; } // App job will be started everytime m_appJob = new FlatpakTransactionJob(m_installation, m_app, role()); connect(m_appJob, &FlatpakTransactionJob::finished, m_appJob, &FlatpakTransactionJob::deleteLater); connect(m_appJob, &FlatpakTransactionJob::jobFinished, this, &FlatpakTransaction::onAppJobFinished); connect(m_appJob, &FlatpakTransactionJob::progressChanged, this, &FlatpakTransaction::onAppJobProgressChanged); m_appJob->start(); } void FlatpakTransaction::onAppJobFinished(bool success) { m_appJobFinished = true; m_appJobProgress = 100; updateProgress(); if (m_runtimeJobFinished) { finishTransaction(success); } } void FlatpakTransaction::onAppJobProgressChanged(int progress) { m_appJobProgress = progress; updateProgress(); } void FlatpakTransaction::onRuntimeJobFinished(bool success) { m_runtimeJobFinished = true; m_runtimeJobProgress = 100; updateProgress(); if (m_appJobFinished) { finishTransaction(success); } } void FlatpakTransaction::onRuntimeJobProgressChanged(int progress) { m_runtimeJobProgress = progress; updateProgress(); } void FlatpakTransaction::updateProgress() { if (m_runtime) { setProgress((m_appJobProgress + m_runtimeJobProgress) / 2); } else { setProgress(m_appJobProgress); } } void FlatpakTransaction::finishTransaction(bool success) { if (success) { AbstractResource::State newState = AbstractResource::None; switch(role()) { case InstallRole: case ChangeAddonsRole: newState = AbstractResource::Installed; break; case RemoveRole: newState = AbstractResource::None; break; } m_app->setState(newState); if (m_runtime && role() == InstallRole) { m_runtime->setState(newState); } } setStatus(DoneStatus); TransactionModel::global()->removeTransaction(this); } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.h b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.h index 7f7d6a54..c193ab06 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakTransaction.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakTransaction.h @@ -1,71 +1,71 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * Copyright © 2017 Jan Grulich * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef FLATPAKTRANSACTION_H #define FLATPAKTRANSACTION_H #include #include extern "C" { #include #include #include } class FlatpakResource; class FlatpakTransactionJob; class FlatpakTransaction : public Transaction { Q_OBJECT public: - FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, Role role); - FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, FlatpakResource *runtime, Role role); - // TODO will be these two ever needed? - FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, const AddonList &list, Role role); - FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, FlatpakResource *runtime, const AddonList &list, Role role); + FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, Role role, bool delayStart = false); + FlatpakTransaction(FlatpakInstallation *installation, FlatpakResource *app, FlatpakResource *runtime, Role role, bool delayStart = false); + // FIXME ignore addons, they are not used in flatpak world (yet) + ~FlatpakTransaction(); void cancel() override; + void setRuntime(FlatpakResource *runtime); public Q_SLOTS: void onAppJobFinished(bool success); void onAppJobProgressChanged(int progress); void onRuntimeJobFinished(bool success); void onRuntimeJobProgressChanged(int progress); void finishTransaction(bool success); void start(); private: void updateProgress(); bool m_appJobFinished; bool m_runtimeJobFinished; int m_appJobProgress; int m_runtimeJobProgress; QPointer m_app; QPointer m_runtime; FlatpakInstallation *m_installation; QPointer m_appJob; QPointer m_runtimeJob; }; #endif // FLATPAKTRANSACTION_H