diff --git a/libdiscover/backends/FlatpakBackend/CMakeLists.txt b/libdiscover/backends/FlatpakBackend/CMakeLists.txt index af195c26..46114d1e 100644 --- a/libdiscover/backends/FlatpakBackend/CMakeLists.txt +++ b/libdiscover/backends/FlatpakBackend/CMakeLists.txt @@ -1,26 +1,25 @@ # add_subdirectory(tests) include_directories(${FLATPAK_INCLUDE_DIRS} ${APPSTREAM_INCLUDE_DIRS}) set(flatpak-backend_SRCS FlatpakResource.cpp FlatpakBackend.cpp FlatpakFetchDataJob.cpp - FlatpakFetchUpdatesJob.cpp FlatpakSourcesBackend.cpp FlatpakTransaction.cpp FlatpakTransactionJob.cpp ) add_library(flatpak-backend MODULE ${flatpak-backend_SRCS}) -target_link_libraries(flatpak-backend Qt5::Core Qt5::Widgets KF5::CoreAddons KF5::ConfigCore Discover::Common AppStreamQt ${FLATPAK_LIBRARIES}) +target_link_libraries(flatpak-backend Qt5::Core Qt5::Widgets Qt5::Concurrent KF5::CoreAddons KF5::ConfigCore Discover::Common AppStreamQt ${FLATPAK_LIBRARIES}) install(TARGETS flatpak-backend DESTINATION ${PLUGIN_INSTALL_DIR}/discover) install(FILES flatpak-backend-categories.xml DESTINATION ${DATA_INSTALL_DIR}/libdiscover/categories) -add_library(FlatpakNotifier MODULE FlatpakNotifier.cpp FlatpakFetchUpdatesJob.cpp) -target_link_libraries(FlatpakNotifier Discover::Notifiers ${FLATPAK_LIBRARIES}) +add_library(FlatpakNotifier MODULE FlatpakNotifier.cpp) +target_link_libraries(FlatpakNotifier Discover::Notifiers Qt5::Concurrent ${FLATPAK_LIBRARIES}) set_target_properties(FlatpakNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover) install(TARGETS FlatpakNotifier DESTINATION ${PLUGIN_INSTALL_DIR}/discover-notifier) install(PROGRAMS org.kde.discover-flatpak.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp index 46c9d5ff..f72da7d8 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.cpp @@ -1,1231 +1,1255 @@ /*************************************************************************** * 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 #include #include #include MUON_BACKEND_PLUGIN(FlatpakBackend) FlatpakBackend::FlatpakBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(AppStreamIntegration::global()->reviews()) , m_fetching(false) + , m_threadPool(new QThreadPool(this)) { 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(); m_sources = new FlatpakSourcesBackend(m_installations, this); SourcesModel::global()->addSourcesBackend(m_sources); } connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, &FlatpakBackend::announceRatingsReady); } FlatpakBackend::~FlatpakBackend() { + m_threadPool.clear(); for(auto inst : m_installations) g_object_unref(inst); g_object_unref(m_cancellable); } bool FlatpakBackend::isValid() const { return m_sources && !m_installations.isEmpty(); } void FlatpakBackend::announceRatingsReady() { emitRatingsReady(); const auto ids = m_reviews->appstreamIds().toSet(); foreach(AbstractResource* res, m_resources) { if (ids.contains(res->appstreamId())) { res->ratingFetched(); } } } class FlatpakFetchRemoteResourceJob : public QNetworkAccessManager { Q_OBJECT public: FlatpakFetchRemoteResourceJob(const QUrl &url, FlatpakBackend *backend) : QNetworkAccessManager(backend) , m_backend(backend) , m_url(url) { } void start() { auto replyGet = get(QNetworkRequest(m_url)); connect(replyGet, &QNetworkReply::finished, this, [this, replyGet] { const QUrl originalUrl = replyGet->request().url(); if (replyGet->error() != QNetworkReply::NoError) { qWarning() << "couldn't download" << originalUrl << replyGet->errorString(); Q_EMIT jobFinished(false, nullptr); return; } const QUrl fileUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/') + originalUrl.fileName()); auto replyPut = put(QNetworkRequest(fileUrl), replyGet->readAll()); connect(replyPut, &QNetworkReply::finished, this, [this, originalUrl, fileUrl, replyPut]() { if (replyPut->error() == QNetworkReply::NoError) { auto res = m_backend->resourceForFile(fileUrl); if (res) { FlatpakResource *resource = qobject_cast(res); resource->setResourceFile(originalUrl); Q_EMIT jobFinished(true, resource); } else { qWarning() << "couldn't download" << originalUrl << "into" << fileUrl << replyPut->errorString(); Q_EMIT jobFinished(false, nullptr); } } }); }); } Q_SIGNALS: void jobFinished(bool success, FlatpakResource *resource); private: FlatpakBackend *m_backend; QUrl m_url; }; 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().toUtf8().constData(), resource->arch().toUtf8().constData(), resource->branch().toUtf8().constData(), 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; file = g_file_new_for_path(url.toLocalFile().toUtf8().constData()); 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); AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parse(QString::fromUtf8(appstreamContent), AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return nullptr; } QList components = metadata.components(); if (components.size()) { asComponent = AppStream::Component(components.first()); } else { qWarning() << "Failed to parse appstream metadata"; return nullptr; } } else { asComponent = AppStream::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, preferredInstallation(), this); metadata = flatpak_bundle_ref_get_metadata(bundleRef); QByteArray metadataContent = QByteArray((char *)g_bytes_get_data(metadata, &len)); if (!updateAppMetadata(resource, metadataContent)) { delete resource; 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); } const QString origin = QString::fromUtf8(flatpak_bundle_ref_get_origin(bundleRef)); resource->setDownloadSize(0); resource->setInstalledSize(flatpak_bundle_ref_get_installed_size(bundleRef)); resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::AlreadyKnown); resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::AlreadyKnown); resource->setFlatpakFileType(QStringLiteral("flatpak")); resource->setOrigin(origin.isEmpty() ? i18n("Local bundle") : origin); resource->setResourceFile(url); resource->setState(FlatpakResource::None); resource->setType(FlatpakResource::DesktopApp); addResource(resource); return resource; } FlatpakResource * FlatpakBackend::addAppFromFlatpakRef(const QUrl &url) { 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 (preferredInstallation(), bytes, m_cancellable, &error); if (!remoteRef) { qWarning() << "Failed to install ref file:" << error->message; return nullptr; } } const auto remoteName = flatpak_remote_ref_get_remote_name(remoteRef); auto ref = FLATPAK_REF(remoteRef); AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Ref/Homepage")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Ref/Description")).toString()); asComponent.setName(settings.value(QStringLiteral("Flatpak Ref/Title")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Ref/Comment")).toString()); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Ref/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), this); resource->setFlatpakFileType(QStringLiteral("flatpakref")); resource->setOrigin(QString::fromUtf8(remoteName)); resource->updateFromRef(ref); QUrl runtimeUrl = QUrl(settings.value(QStringLiteral("Flatpak Ref/RuntimeRepo")).toString()); if (!runtimeUrl.isEmpty()) { + auto installation = preferredInstallation(); // We need to fetch metadata to find information about required runtime - FlatpakFetchDataJob *job = new FlatpakFetchDataJob(preferredInstallation(), resource, FlatpakFetchDataJob::FetchMetadata); - connect(job, &FlatpakFetchDataJob::finished, job, &FlatpakFetchDataJob::deleteLater); - connect(job, &FlatpakFetchDataJob::jobFetchMetadataFailed, this, [this, resource] { + auto fw = new QFutureWatcher(this); + fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, installation, resource)); + connect(fw, &QFutureWatcher::finished, this, [this, installation, resource, fw, runtimeUrl]() { + const auto metadata = fw->result(); // Even when we failed to fetch information about runtime we still want to show the application - addResource(resource); - }); - connect(job, &FlatpakFetchDataJob::jobFetchMetadataFinished, this, [this, runtimeUrl] (FlatpakInstallation *installation, FlatpakResource *resource, const QByteArray &metadata) { - Q_UNUSED(installation); - - updateAppMetadata(resource, metadata); - - auto runtime = getRuntimeForApp(resource); - if (!runtime || (runtime && !runtime->isInstalled())) { - FlatpakFetchRemoteResourceJob *fetchRemoteResource = new FlatpakFetchRemoteResourceJob(runtimeUrl, this); - connect(fetchRemoteResource, &FlatpakFetchRemoteResourceJob::jobFinished, this, [this, resource] (bool success, FlatpakResource *repoResource) { - if (success) { - installApplication(repoResource); - } - addResource(resource); - }); - fetchRemoteResource->start(); - return; + if (metadata.isEmpty()) { + onFetchMetadataFinished(installation, resource, metadata); } else { - addResource(resource); + updateAppMetadata(resource, metadata); + + auto runtime = getRuntimeForApp(resource); + if (!runtime || (runtime && !runtime->isInstalled())) { + FlatpakFetchRemoteResourceJob *fetchRemoteResource = new FlatpakFetchRemoteResourceJob(runtimeUrl, this); + connect(fetchRemoteResource, &FlatpakFetchRemoteResourceJob::jobFinished, this, [this, resource] (bool success, FlatpakResource *repoResource) { + if (success) { + installApplication(repoResource); + } + addResource(resource); + }); + fetchRemoteResource->start(); + return; + } else { + addResource(resource); + } } + fw->deleteLater(); }); - job->start(); } else { addResource(resource); } return resource; } FlatpakResource * FlatpakBackend::addSourceFromFlatpakRepo(const QUrl &url) { Q_ASSERT(url.isLocalFile()); 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; } AppStream::Component asComponent; asComponent.addUrl(AppStream::Component::UrlKindHomepage, settings.value(QStringLiteral("Flatpak Repo/Homepage")).toString()); asComponent.setSummary(settings.value(QStringLiteral("Flatpak Repo/Comment")).toString()); asComponent.setDescription(settings.value(QStringLiteral("Flatpak Repo/Description")).toString()); asComponent.setName(title); asComponent.setId(settings.value(QStringLiteral("Flatpak Ref/Name")).toString()); const QString iconUrl = settings.value(QStringLiteral("Flatpak Repo/Icon")).toString(); if (!iconUrl.isEmpty()) { AppStream::Icon icon; icon.setKind(AppStream::Icon::KindRemote); icon.setUrl(QUrl(iconUrl)); asComponent.addIcon(icon); } auto resource = new FlatpakResource(asComponent, preferredInstallation(), 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(preferredInstallation(), resource->flatpakName().toUtf8().constData(), 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->installation(); 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))); const FlatpakResource::ResourceType appType = flatpak_ref_get_kind(FLATPAK_REF(ref)) == FLATPAK_REF_KIND_APP ? FlatpakResource::DesktopApp : FlatpakResource::Runtime; const QString name = QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))); const QString appId = appType == FlatpakResource::DesktopApp ? QLatin1String(flatpak_ref_get_name(FLATPAK_REF(ref))) + QStringLiteral(".desktop") : name; const QString uniqueId = QStringLiteral("%1/%2/%3/%4/%5/%6").arg(FlatpakResource::installationPath(flatpakInstallation)) .arg(QLatin1String("flatpak")) .arg(QString::fromUtf8(flatpak_installed_ref_get_origin(ref))) .arg(FlatpakResource::typeAsString(appType)) .arg(appId) .arg(branch); // 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() == appId || resource->flatpakName() == name); } return resource->flatpakName() == appId || resource->flatpakName() == name; } 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); 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)); g_autoptr(GFile) fileTimestamp = flatpak_remote_get_appstream_timestamp(remote, nullptr); QFileInfo fileInfo = QFileInfo(QString::fromUtf8(g_file_get_path(fileTimestamp))); // Refresh appstream metadata in case they have never been refreshed or the cache is older than 6 hours if (!fileInfo.exists() || fileInfo.lastModified().toUTC().secsTo(QDateTime::currentDateTimeUtc()) > 21600) { refreshAppstreamMetadata(flatpakInstallation, remote); } else { integrateRemote(flatpakInstallation, remote); } } return true; } void FlatpakBackend::integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote) { FlatpakSource source(remote); if (!source.isEnabled() || flatpak_remote_get_noenumerate(remote)) { return; } const QString appstreamDirPath = source.appstreamDir(); const QString appstreamIconsPath = source.appstreamDir() + QLatin1String("/icons/"); const QString appDirFileName = appstreamDirPath + QLatin1String("/appstream.xml.gz"); if (!QFile::exists(appDirFileName)) { qWarning() << "No" << appDirFileName << "appstream metadata found for" << source.name(); return; } AppStream::Metadata metadata; metadata.setFormatStyle(AppStream::Metadata::FormatStyleCollection); AppStream::Metadata::MetadataError error = metadata.parseFile(appDirFileName, AppStream::Metadata::FormatKindXml); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; return; } setFetching(true); QList components = metadata.components(); foreach (const AppStream::Component& component, components) { AppStream::Component appstreamComponent(component); FlatpakResource *resource = new FlatpakResource(appstreamComponent, flatpakInstallation, this); resource->setIconPath(appstreamIconsPath); resource->setOrigin(source.name()); addResource(resource); } setFetching(false); } bool FlatpakBackend::loadInstalledApps(FlatpakInstallation *flatpakInstallation) { Q_ASSERT(flatpakInstallation); // List installed applications from installed desktop files const QString pathExports = FlatpakResource::installationPath(flatpakInstallation) + QLatin1String("/exports/"); const QString pathApps = pathExports + QLatin1String("share/applications/"); const QDir dir(pathApps); if (dir.exists()) { foreach (const auto &file, dir.entryInfoList( QDir::Files)) { if (file.fileName() == QLatin1String("mimeinfo.cache")) { continue; } const QString fnDesktop = file.absoluteFilePath(); AppStream::Metadata metadata; AppStream::Metadata::MetadataError error = metadata.parseFile(fnDesktop, AppStream::Metadata::FormatKindDesktopEntry); if (error != AppStream::Metadata::MetadataErrorNoError) { qWarning() << "Failed to parse appstream metadata: " << error; continue; } AppStream::Component appstreamComponent(metadata.component()); FlatpakResource *resource = new FlatpakResource(appstreamComponent, flatpakInstallation, this); 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() == QStringLiteral("%1.desktop").arg(resource->appstreamId()) && res->name() == resource->name()) { resourceExists = true; 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't get latest commit for" << flatpak_ref_get_name(FLATPAK_REF(ref)); continue; } 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) +void FlatpakBackend::loadRemoteUpdates(FlatpakInstallation* installation) { - FlatpakFetchUpdatesJob *job = new FlatpakFetchUpdatesJob(flatpakInstallation); - connect(job, &FlatpakFetchUpdatesJob::finished, job, &FlatpakFetchUpdatesJob::deleteLater); - connect(job, &FlatpakFetchUpdatesJob::jobFetchUpdatesFinished, this, &FlatpakBackend::onFetchUpdatesFinished); - job->start(); + auto fw = new QFutureWatcher(this); + fw->setFuture(QtConcurrent::run(&m_threadPool, [installation]() -> GPtrArray * { + g_autoptr(GCancellable) cancellable = g_cancellable_new(); + g_autoptr(GError) localError = nullptr; + GPtrArray *refs = flatpak_installation_list_installed_refs_for_update(installation, cancellable, &localError); + if (!refs) { + qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; + } + return refs; + })); + connect(fw, &QFutureWatcher::finished, this, [this, installation, fw](){ + auto refs = fw->result(); + onFetchUpdatesFinished(installation, refs); + fw->deleteLater(); + }); } 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().toUtf8().constData(), &localError); if (!ref) { qWarning() << "Failed to parse" << bundle.id() << localError->message; return false; } else { resource->updateFromRef(ref); } } return true; } class FlatpakRefreshAppstreamMetadataJob : public QThread { Q_OBJECT public: FlatpakRefreshAppstreamMetadataJob(FlatpakInstallation *installation, FlatpakRemote *remote) : QThread() + , m_cancellable(g_cancellable_new()) , m_installation(installation) , m_remote(remote) { - m_cancellable = g_cancellable_new(); + connect(this, &FlatpakRefreshAppstreamMetadataJob::finished, this, &QObject::deleteLater); } ~FlatpakRefreshAppstreamMetadataJob() { g_object_unref(m_cancellable); } void cancel() { g_cancellable_cancel(m_cancellable); } void run() override { g_autoptr(GError) localError = nullptr; #if FLATPAK_CHECK_VERSION(0,9,4) // With Flatpak 0.9.4 we can use flatpak_installation_update_appstream_full_sync() providing progress reporting which we don't use at this moment, but still // better to use newer function in case the previous one gets deprecated if (!flatpak_installation_update_appstream_full_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, nullptr, nullptr, m_cancellable, &localError)) { #else if (!flatpak_installation_update_appstream_sync(m_installation, flatpak_remote_get_name(m_remote), nullptr, nullptr, m_cancellable, &localError)) { #endif qWarning() << "Failed to refresh appstream metadata for " << flatpak_remote_get_name(m_remote) << ": " << (localError ? localError->message : ""); Q_EMIT jobRefreshAppstreamMetadataFailed(); } else { Q_EMIT jobRefreshAppstreamMetadataFinished(m_installation, m_remote); } } Q_SIGNALS: void jobRefreshAppstreamMetadataFailed(); void jobRefreshAppstreamMetadataFinished(FlatpakInstallation *installation, FlatpakRemote *remote); private: GCancellable *m_cancellable; FlatpakInstallation *m_installation; FlatpakRemote *m_remote; }; void FlatpakBackend::refreshAppstreamMetadata(FlatpakInstallation *installation, FlatpakRemote *remote) { FlatpakRefreshAppstreamMetadataJob *job = new FlatpakRefreshAppstreamMetadataJob(installation, remote); - connect(job, &FlatpakRefreshAppstreamMetadataJob::finished, job, &FlatpakFetchDataJob::deleteLater); connect(job, &FlatpakRefreshAppstreamMetadataJob::jobRefreshAppstreamMetadataFinished, this, &FlatpakBackend::integrateRemote); job->start(); } void FlatpakBackend::reloadPackageList() { setFetching(true); for (auto installation : qAsConst(m_installations)) { // Load applications from appstream metadata if (!loadAppsFromAppstreamData(installation)) { qWarning() << "Failed to load packages from appstream data from installation" << installation; } // Load installed applications and update existing resources with info from installed application if (!loadInstalledApps(installation)) { qWarning() << "Failed to load installed packages from installation" << installation; } } setFetching(false); } bool FlatpakBackend::setupFlatpakInstallations(GError **error) { GPtrArray *installations = flatpak_get_system_installations(m_cancellable, error); if (*error) { qWarning() << "Failed to call flatpak_get_system_installations:" << (*error)->message; } for (uint i = 0; installations && i < installations->len; i++) { m_installations << FLATPAK_INSTALLATION(g_ptr_array_index(installations, i)); } auto user = flatpak_installation_new_user(m_cancellable, error); if (user) { m_installations << user; } return !m_installations.isEmpty(); } 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; if (resource->type() != FlatpakResource::DesktopApp) { return true; } installationPath = flatpak_installation_get_path(flatpakInstallation); const QString path = QString::fromUtf8(g_file_get_path(installationPath)) + QStringLiteral("/app/%1/%2/%3/active/metadata").arg(resource->flatpakName()).arg(resource->arch()).arg(resource->branch()); if (QFile::exists(path)) { return updateAppMetadata(resource, path); } 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(); + auto fw = new QFutureWatcher(this); + fw->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchMetadata, flatpakInstallation, resource)); + connect(fw, &QFutureWatcher::finished, this, [this, flatpakInstallation, resource, fw]() { + const auto metadata = fw->result(); + if (!metadata.isEmpty()) + onFetchMetadataFinished(flatpakInstallation, resource, metadata); + fw->deleteLater(); + }); + // 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 QString &path) { // Parse the temporary file QSettings setting(path, 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? return true; } 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(); updateAppMetadata(resource, tempFile.fileName()); 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 if (resource->state() == AbstractResource::None && resource->type() == FlatpakResource::DesktopApp) { auto 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->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::UnknownOrFailed); - resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::UnknownOrFailed); + auto futureWatcher = new QFutureWatcher(this); + futureWatcher->setFuture(QtConcurrent::run(&m_threadPool, &FlatpakRunnables::fetchFlatpakSize, flatpakInstallation, resource)); + connect(futureWatcher, &QFutureWatcher::finished, this, [this, resource, futureWatcher]() { + auto value = futureWatcher->result(); + if (value.valid) { + onFetchSizeFinished(resource, value.downloadSize, value.installedSize); + } else { + resource->setPropertyState(FlatpakResource::DownloadSize, FlatpakResource::UnknownOrFailed); + resource->setPropertyState(FlatpakResource::InstalledSize, FlatpakResource::UnknownOrFailed); + } + futureWatcher->deleteLater(); }); - 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) { if (filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakrepo")) || filter.resourceUrl.fileName().endsWith(QLatin1String(".flatpakref"))) { auto stream = new ResultsStream(QStringLiteral("FlatpakStream-http-")+filter.resourceUrl.fileName()); FlatpakFetchRemoteResourceJob *fetchResourceJob = new FlatpakFetchRemoteResourceJob(filter.resourceUrl, this); connect(fetchResourceJob, &FlatpakFetchRemoteResourceJob::jobFinished, this, [fetchResourceJob, stream] (bool success, FlatpakResource *resource) { if (success) { stream->resourcesFound({resource}); } stream->finish(); fetchResourceJob->deleteLater(); }); return stream; } else if (!filter.resourceUrl.isEmpty() && filter.resourceUrl.scheme() != QLatin1String("appstream")) return new ResultsStream(QStringLiteral("FlatpakStream-void"), {}); QVector ret; foreach(AbstractResource* r, m_resources) { if (r->isTechnical() && filter.state != AbstractResource::Upgradeable) { continue; } if (!filter.resourceUrl.isEmpty() && filter.resourceUrl.host().compare(r->appstreamId(), Qt::CaseInsensitive) != 0) continue; if (r->state() < filter.state) continue; if (filter.search.isEmpty() || 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.data(); } Transaction* 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); // Make sure we update appstream metadata first // FIXME we have to let flatpak to return the remote as the one created by FlatpakSourcesBackend will not have appstream directory refreshAppstreamMetadata(preferredInstallation(), flatpak_installation_get_remote_by_name(preferredInstallation(), flatpak_remote_get_name(remote), nullptr, nullptr)); } return nullptr; } FlatpakTransaction *transaction = nullptr; FlatpakInstallation *installation = resource->installation(); if (resource->propertyState(FlatpakResource::RequiredRuntime) == FlatpakResource::NotKnownYet && resource->type() == FlatpakResource::DesktopApp) { transaction = new FlatpakTransaction(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 { FlatpakResource *runtime = getRuntimeForApp(resource); if (runtime && !runtime->isInstalled()) { transaction = new FlatpakTransaction(resource, runtime, Transaction::InstallRole); } else { transaction = new FlatpakTransaction(resource, Transaction::InstallRole); } } connect(transaction, &FlatpakTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppState(installation, resource); } }); return transaction; } Transaction* FlatpakBackend::installApplication(AbstractResource *app) { return installApplication(app, {}); } Transaction* 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 nullptr; } FlatpakInstallation *installation = resource->installation(); FlatpakTransaction *transaction = new FlatpakTransaction(resource, Transaction::RemoveRole); connect(transaction, &FlatpakTransaction::statusChanged, [this, installation, resource] (Transaction::Status status) { if (status == Transaction::Status::DoneStatus) { updateAppSize(installation, resource); } }); return transaction; } void FlatpakBackend::checkForUpdates() { for (auto installation : qAsConst(m_installations)) { // Load local updates, comparing current and latest commit loadLocalUpdates(installation); // Load updates from remote repositories loadRemoteUpdates(installation); } } AbstractResource * FlatpakBackend::resourceForFile(const QUrl &url) { if (!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 if (url.path().endsWith(QLatin1String(".flatpakrepo"))) { resource = addSourceFromFlatpakRepo(url); } return resource; } QString FlatpakBackend::displayName() const { return QStringLiteral("Flatpak"); } #include "FlatpakBackend.moc" diff --git a/libdiscover/backends/FlatpakBackend/FlatpakBackend.h b/libdiscover/backends/FlatpakBackend/FlatpakBackend.h index 87b34b15..4b00ee5f 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakBackend.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakBackend.h @@ -1,113 +1,115 @@ /*************************************************************************** * 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 FLATPAKBACKEND_H #define FLATPAKBACKEND_H #include "FlatpakResource.h" #include #include #include +#include #include extern "C" { #include } class QAction; class FlatpakSourcesBackend; class StandardBackendUpdater; class OdrsReviewsBackend; class FlatpakBackend : public AbstractResourcesBackend { Q_OBJECT public: explicit FlatpakBackend(QObject *parent = nullptr); ~FlatpakBackend(); int updatesCount() const override; AbstractBackendUpdater * backendUpdater() const override; AbstractReviewsBackend * reviewsBackend() const override; ResultsStream * search(const AbstractResourcesBackend::Filters & search) override; ResultsStream * findResourceByPackageName(const QUrl &search) override; QList resources() const { return m_resources.values(); } bool isValid() const override; QList messageActions() const override { return {}; } Transaction* installApplication(AbstractResource* app) override; Transaction* installApplication(AbstractResource* app, const AddonList& addons) override; Transaction* removeApplication(AbstractResource* app) override; bool isFetching() const override { return m_fetching; } AbstractResource * resourceForFile(const QUrl & ) override; void checkForUpdates() override; QString displayName() const override; bool hasApplications() const override { return true; } FlatpakResource * addSourceFromFlatpakRepo(const QUrl &url); private Q_SLOTS: void onFetchMetadataFinished(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, const QByteArray &metadata); void onFetchSizeFinished(FlatpakResource *resource, guint64 downloadSize, guint64 installedSize); void onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates); private: void announceRatingsReady(); FlatpakInstallation * preferredInstallation() const { return m_installations.constFirst(); } void integrateRemote(FlatpakInstallation *flatpakInstallation, FlatpakRemote *remote); FlatpakRemote * getFlatpakRemoteByUrl(const QString &url, FlatpakInstallation *installation) const; FlatpakInstalledRef * getInstalledRefForApp(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); FlatpakResource * getAppForInstalledRef(FlatpakInstallation *flatpakInstallation, FlatpakInstalledRef *ref); FlatpakResource * getRuntimeForApp(FlatpakResource *resource); FlatpakResource * addAppFromFlatpakBundle(const QUrl &url); FlatpakResource * addAppFromFlatpakRef(const QUrl &url); void addResource(FlatpakResource *resource); bool compareAppFlatpakRef(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource, FlatpakInstalledRef *ref); bool loadAppsFromAppstreamData(FlatpakInstallation *flatpakInstallation); bool loadInstalledApps(FlatpakInstallation *flatpakInstallation); void loadLocalUpdates(FlatpakInstallation *flatpakInstallation); void loadRemoteUpdates(FlatpakInstallation *flatpakInstallation); bool parseMetadataFromAppBundle(FlatpakResource *resource); void refreshAppstreamMetadata(FlatpakInstallation *installation, FlatpakRemote *remote); void reloadPackageList(); bool setupFlatpakInstallations(GError **error); void updateAppInstalledMetadata(FlatpakInstalledRef *installedRef, FlatpakResource *resource); bool updateAppMetadata(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); bool updateAppMetadata(FlatpakResource *resource, const QByteArray &data); bool updateAppMetadata(FlatpakResource *resource, const QString &path); bool updateAppSize(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); bool updateAppSizeFromRemote(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); void updateAppState(FlatpakInstallation *flatpakInstallation, FlatpakResource *resource); void setFetching(bool fetching); QHash m_resources; StandardBackendUpdater *m_updater; FlatpakSourcesBackend *m_sources = nullptr; QSharedPointer m_reviews; bool m_fetching; GCancellable *m_cancellable; QVector m_installations; + QThreadPool m_threadPool; }; #endif // FLATPAKBACKEND_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.cpp b/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.cpp index 03c938b8..3d2a3cf8 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.cpp @@ -1,118 +1,96 @@ /*************************************************************************** * 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 "FlatpakFetchDataJob.h" #include "FlatpakResource.h" #include -FlatpakFetchDataJob::FlatpakFetchDataJob(FlatpakInstallation *installation, FlatpakResource *app, FlatpakFetchDataJob::DataKind kind) - : QThread() - , m_app(app) - , m_installation(installation) - , m_kind(kind) +static FlatpakRef * createFakeRef(FlatpakResource *resource) { - m_cancellable = g_cancellable_new(); -} + FlatpakRef *ref = nullptr; + g_autoptr(GError) localError = nullptr; -FlatpakFetchDataJob::~FlatpakFetchDataJob() -{ - g_object_unref(m_cancellable); -} + const QString id = QStringLiteral("%1/%2/%3/%4").arg(resource->typeAsString(), resource->flatpakName(), resource->arch(), resource->branch()); + ref = flatpak_ref_parse(id.toUtf8().constData(), &localError); -void FlatpakFetchDataJob::cancel() -{ - g_cancellable_cancel(m_cancellable); + if (!ref) { + qWarning() << "Failed to create fake ref: " << localError->message; + } + + return ref; } -void FlatpakFetchDataJob::run() +namespace FlatpakRunnables { +QByteArray fetchMetadata(FlatpakInstallation *installation, FlatpakResource *app) +{ + g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; - if (m_kind == FetchMetadata) { - QByteArray metadataContent; - g_autoptr(GBytes) data = nullptr; - g_autoptr(FlatpakRef) fakeRef = nullptr; - - if (m_app->origin().isEmpty()) { - qWarning() << "Failed to get metadata file because of missing origin"; - Q_EMIT jobFetchMetadataFailed(); - return; - } - - fakeRef = createFakeRef(m_app); - if (!fakeRef) { - Q_EMIT jobFetchMetadataFailed(); - return; - } - - data = flatpak_installation_fetch_remote_metadata_sync(m_installation, m_app->origin().toUtf8().constData(), fakeRef, m_cancellable, &localError); - if (data) { - gsize len = 0; - metadataContent = QByteArray((char *)g_bytes_get_data(data, &len)); - } else { - qWarning() << "Failed to get metadata file: " << localError->message; - Q_EMIT jobFetchMetadataFailed(); - return; - } - - if (metadataContent.isEmpty()) { - qWarning() << "Failed to get metadata file: empty metadata"; - Q_EMIT jobFetchMetadataFailed(); - return; - } - - Q_EMIT jobFetchMetadataFinished(m_installation, m_app, metadataContent); - } else if (m_kind == FetchSize) { - guint64 downloadSize = 0; - guint64 installedSize = 0; - g_autoptr(FlatpakRef) ref = nullptr; - - ref = createFakeRef(m_app); - if (!ref) { - Q_EMIT jobFetchSizeFailed(); - return; - } - - if (!flatpak_installation_fetch_remote_size_sync(m_installation, m_app->origin().toUtf8().constData(), - ref, &downloadSize, &installedSize, m_cancellable, &localError)) { - qWarning() << "Failed to get remote size of " << m_app->name() << ": " << localError->message; - Q_EMIT jobFetchSizeFailed(); - return; - } - - Q_EMIT jobFetchSizeFinished(m_app, downloadSize, installedSize); + if (app->origin().isEmpty()) { + qWarning() << "Failed to get metadata file because of missing origin"; + return {}; + } + + g_autoptr(FlatpakRef) fakeRef = createFakeRef(app); + if (!fakeRef) { + return {}; } + + QByteArray metadataContent; + g_autoptr(GBytes) data = flatpak_installation_fetch_remote_metadata_sync(installation, app->origin().toUtf8().constData(), fakeRef, cancellable, &localError); + if (data) { + gsize len = 0; + metadataContent = QByteArray((char *)g_bytes_get_data(data, &len)); + } else { + qWarning() << "Failed to get metadata file: " << localError->message; + return {}; + } + + if (metadataContent.isEmpty()) { + qWarning() << "Failed to get metadata file: empty metadata"; + return {}; + } + + return metadataContent; } -FlatpakRef * FlatpakFetchDataJob::createFakeRef(FlatpakResource *resource) +SizeInformation fetchFlatpakSize(FlatpakInstallation *installation, FlatpakResource *app) { - FlatpakRef *ref = nullptr; + g_autoptr(GCancellable) cancellable = g_cancellable_new(); g_autoptr(GError) localError = nullptr; - const QString id = QString::fromUtf8("%1/%2/%3/%4").arg(resource->typeAsString()).arg(resource->flatpakName()).arg(resource->arch()).arg(resource->branch()); - ref = flatpak_ref_parse(id.toUtf8().constData(), &localError); - + SizeInformation ret; + g_autoptr(FlatpakRef) ref = createFakeRef(app); if (!ref) { - qWarning() << "Failed to create fake ref: " << localError->message; + return ret; } - return ref; + if (!flatpak_installation_fetch_remote_size_sync(installation, app->origin().toUtf8().constData(), ref, &ret.downloadSize, &ret.installedSize, cancellable, &localError)) { + qWarning() << "Failed to get remote size of " << app->name() << ": " << localError->message; + return ret; + } + + ret.valid = true; + return ret; +} + } diff --git a/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.h b/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.h index 03abbf6c..3998fee1 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.h +++ b/libdiscover/backends/FlatpakBackend/FlatpakFetchDataJob.h @@ -1,65 +1,47 @@ /*************************************************************************** * 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 FLATPAKFETCHDATAJOB_H #define FLATPAKFETCHDATAJOB_H +#include extern "C" { #include -#include #include } -#include - class FlatpakResource; -class FlatpakFetchDataJob : public QThread + +namespace FlatpakRunnables { - Q_OBJECT -public: - enum DataKind { - FetchMetadata = 0, - FetchSize = 1, + struct SizeInformation { + bool valid = false; + guint64 downloadSize; + guint64 installedSize; }; - FlatpakFetchDataJob(FlatpakInstallation *installation, FlatpakResource *app, DataKind kind); - ~FlatpakFetchDataJob(); - - void cancel(); - void run() override; + SizeInformation fetchFlatpakSize(FlatpakInstallation *installation, FlatpakResource *app); -Q_SIGNALS: - void jobFetchMetadataFailed(); - void jobFetchMetadataFinished(FlatpakInstallation *installation, FlatpakResource *resource, const QByteArray &metadata); - void jobFetchSizeFailed(); - void jobFetchSizeFinished(FlatpakResource *resource, int downloadSize, int installedSize); - -private: - FlatpakRef * createFakeRef(FlatpakResource *resource); - - GCancellable *m_cancellable; - FlatpakResource *m_app; - FlatpakInstallation *m_installation; - DataKind m_kind; -}; + QByteArray fetchMetadata(FlatpakInstallation *installation, FlatpakResource *app); +} #endif // FLATPAKFETCHDATAJOB_H diff --git a/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.cpp b/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.cpp deleted file mode 100644 index 4b86254e..00000000 --- a/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************** - * 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 "FlatpakFetchUpdatesJob.h" - -#include - -FlatpakFetchUpdatesJob::FlatpakFetchUpdatesJob(FlatpakInstallation *installation) - : QThread() - , m_installation(installation) -{ - m_cancellable = g_cancellable_new(); -} - -FlatpakFetchUpdatesJob::~FlatpakFetchUpdatesJob() -{ - g_object_unref(m_cancellable); -} - -void FlatpakFetchUpdatesJob::cancel() -{ - g_cancellable_cancel(m_cancellable); -} - -void FlatpakFetchUpdatesJob::run() -{ - g_autoptr(GError) localError = nullptr; - - GPtrArray *refs = nullptr; - refs = flatpak_installation_list_installed_refs_for_update(m_installation, m_cancellable, &localError); - if (!refs) { - qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; - return; - } - - Q_EMIT jobFetchUpdatesFinished(m_installation, refs); -} - diff --git a/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.h b/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.h deleted file mode 100644 index ada185ab..00000000 --- a/libdiscover/backends/FlatpakBackend/FlatpakFetchUpdatesJob.h +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************** - * 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 FLATPAKFETCHUPDATESJOB_H -#define FLATPAKFETCHUPDATESJOB_H - -extern "C" { -#include -#include -#include -} - -#include - -class FlatpakFetchUpdatesJob : public QThread -{ - Q_OBJECT -public: - FlatpakFetchUpdatesJob(FlatpakInstallation *installation); - ~FlatpakFetchUpdatesJob(); - - void cancel(); - void run() override; - -Q_SIGNALS: - void jobFetchUpdatesFinished(FlatpakInstallation *installation, GPtrArray *updates); - -private: - GCancellable *m_cancellable; - FlatpakInstallation *m_installation; -}; - -#endif // FLATPAKFETCHUPDATESJOB_H - - diff --git a/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp b/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp index 6ce7d298..d20dc11c 100644 --- a/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp +++ b/libdiscover/backends/FlatpakBackend/FlatpakNotifier.cpp @@ -1,179 +1,191 @@ /*************************************************************************** * 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 "FlatpakNotifier.h" -#include "FlatpakFetchUpdatesJob.h" #include #include #include +#include +#include static void installationChanged(GFileMonitor *monitor, GFile *child, GFile *other_file, GFileMonitorEvent event_type, gpointer self) { Q_UNUSED(monitor); Q_UNUSED(child); Q_UNUSED(other_file); Q_UNUSED(event_type); FlatpakNotifier *notifier = (FlatpakNotifier*)self; if (!notifier) { return; } notifier->checkUpdates(); } FlatpakNotifier::FlatpakNotifier(QObject* parent) : BackendNotifierModule(parent) , m_userInstallationUpdates(0) , m_systemInstallationUpdates(0) { m_cancellable = g_cancellable_new(); checkUpdates(); QTimer *dailyCheck = new QTimer(this); dailyCheck->setInterval(24 * 60 * 60 * 1000); //refresh at least once every day connect(dailyCheck, &QTimer::timeout, this, &FlatpakNotifier::checkUpdates); } FlatpakNotifier::~FlatpakNotifier() { g_object_unref(m_userInstallationMonitor); g_object_unref(m_systemInstallationMonitor); g_object_unref(m_flatpakInstallationSystem); g_object_unref(m_flatpakInstallationUser); g_object_unref(m_cancellable); } void FlatpakNotifier::recheckSystemUpdateNeeded() { checkUpdates(); } bool FlatpakNotifier::isSystemUpToDate() const { return !m_systemInstallationUpdates && !m_userInstallationUpdates; } uint FlatpakNotifier::securityUpdatesCount() { return 0; } uint FlatpakNotifier::updatesCount() { return m_systemInstallationUpdates + m_userInstallationUpdates; } void FlatpakNotifier::checkUpdates() { g_autoptr(GError) error = nullptr; // Load flatpak installation if (!setupFlatpakInstallations(&error)) { qWarning() << "Failed to setup flatpak installations: " << error->message; } else { // Load updates from remote repositories loadRemoteUpdates(m_flatpakInstallationSystem); loadRemoteUpdates(m_flatpakInstallationUser); } } void FlatpakNotifier::onFetchUpdatesFinished(FlatpakInstallation *flatpakInstallation, GPtrArray *updates) { bool changed = false; uint validUpdates = 0; g_autoptr(GPtrArray) fetchedUpdates = updates; for (uint i = 0; i < fetchedUpdates->len; i++) { FlatpakInstalledRef *ref = FLATPAK_INSTALLED_REF(g_ptr_array_index(fetchedUpdates, i)); const QString refName = QString::fromUtf8(flatpak_ref_get_name(FLATPAK_REF(ref))); // FIXME right now I can't think of any other filter than this, in FlatpakBackend updates are matched // with apps so .Locale/.Debug subrefs are not shown and updated automatically. Also this will show // updates for refs we don't show in Discover if appstream metadata or desktop file for them is not found if (refName.endsWith(QStringLiteral(".Locale")) || refName.endsWith(QStringLiteral(".Debug"))) { continue; } validUpdates++; } if (flatpak_installation_get_is_user(flatpakInstallation)) { changed = m_userInstallationUpdates != validUpdates; m_userInstallationUpdates = validUpdates; } else { changed = m_systemInstallationUpdates != validUpdates; m_systemInstallationUpdates = validUpdates; } if (changed) { Q_EMIT foundUpdates(); } } -void FlatpakNotifier::loadRemoteUpdates(FlatpakInstallation *flatpakInstallation) +void FlatpakNotifier::loadRemoteUpdates(FlatpakInstallation *installation) { - FlatpakFetchUpdatesJob *job = new FlatpakFetchUpdatesJob(flatpakInstallation); - connect(job, &FlatpakFetchUpdatesJob::finished, job, &FlatpakFetchUpdatesJob::deleteLater); - connect(job, &FlatpakFetchUpdatesJob::jobFetchUpdatesFinished, this, &FlatpakNotifier::onFetchUpdatesFinished); - job->start(); + auto fw = new QFutureWatcher(this); + fw->setFuture(QtConcurrent::run( [installation]() -> GPtrArray * { + g_autoptr(GCancellable) cancellable = g_cancellable_new(); + g_autoptr(GError) localError = nullptr; + GPtrArray *refs = flatpak_installation_list_installed_refs_for_update(installation, cancellable, &localError); + if (!refs) { + qWarning() << "Failed to get list of installed refs for listing updates: " << localError->message; + } + return refs; + })); + connect(fw, &QFutureWatcher::finished, this, [this, installation, fw](){ + auto refs = fw->result(); + onFetchUpdatesFinished(installation, refs); + fw->deleteLater(); + }); } bool FlatpakNotifier::setupFlatpakInstallations(GError **error) { if (!m_flatpakInstallationSystem) { m_flatpakInstallationSystem = flatpak_installation_new_system(m_cancellable, error); if (!m_flatpakInstallationSystem) { return false; } } if (!m_flatpakInstallationUser) { m_flatpakInstallationUser = flatpak_installation_new_user(m_cancellable, error); if (!m_flatpakInstallationUser) { return false; } } if (!m_systemInstallationMonitor) { m_systemInstallationMonitor = flatpak_installation_create_monitor(m_flatpakInstallationSystem, m_cancellable, error); if (!m_systemInstallationMonitor) { return false; } g_signal_connect(m_systemInstallationMonitor, "changed", G_CALLBACK(installationChanged), this); } if (!m_userInstallationMonitor) { m_userInstallationMonitor = flatpak_installation_create_monitor(m_flatpakInstallationUser, m_cancellable, error); if (!m_userInstallationMonitor) { return false; } g_signal_connect(m_userInstallationMonitor, "changed", G_CALLBACK(installationChanged), this); } return true; } #include "FlatpakNotifier.moc" diff --git a/libdiscover/backends/SnapBackend/SnapBackend.cpp b/libdiscover/backends/SnapBackend/SnapBackend.cpp index f84ed066..c8858db3 100644 --- a/libdiscover/backends/SnapBackend/SnapBackend.cpp +++ b/libdiscover/backends/SnapBackend/SnapBackend.cpp @@ -1,176 +1,177 @@ /*************************************************************************** * Copyright © 2013 Aleix Pol Gonzalez * * * * 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 "SnapBackend.h" #include "SnapTransaction.h" #include "SnapResource.h" #include "SnapReviewsBackend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" MUON_BACKEND_PLUGIN(SnapBackend) SnapBackend::SnapBackend(QObject* parent) : AbstractResourcesBackend(parent) , m_updater(new StandardBackendUpdater(this)) , m_reviews(new SnapReviewsBackend(this)) { { auto request = m_client.connect(); request->runSync(); m_valid = request->error() == QSnapdRequest::NoError; if (!m_valid) { qWarning() << "snap problem at initialize:" << request->errorString(); + return; } } connect(m_reviews, &SnapReviewsBackend::ratingsReady, this, &AbstractResourcesBackend::emitRatingsReady); //make sure we populate the installed resources first populate(m_client.list(), AbstractResource::Installed); } int SnapBackend::updatesCount() const { return m_updater->updatesCount(); } static ResultsStream* voidStream() { return new ResultsStream(QStringLiteral("Snap-void"), {}); } ResultsStream * SnapBackend::search(const AbstractResourcesBackend::Filters& filters) { if (!filters.resourceUrl.isEmpty()) { return findResourceByPackageName(filters.resourceUrl); } else if (filters.category && filters.category->isAddons()) { return voidStream(); } else if (filters.state >= AbstractResource::Installed) { return populate(m_client.list(), AbstractResource::Installed); } else { return populate(m_client.find(QSnapdClient::FindFlag::None, filters.search), AbstractResource::None); } return voidStream(); } ResultsStream * SnapBackend::findResourceByPackageName(const QUrl& search) { return search.scheme() == QLatin1String("snap") ? populate(m_client.find(QSnapdClient::MatchName, search.host()), AbstractResource::Installed) : voidStream(); } template ResultsStream* SnapBackend::populate(T* job, AbstractResource::State state) { auto stream = new ResultsStream(QStringLiteral("Snap-populate")); connect(job, &QSnapdFindRequest::complete, stream, [stream, this, state, job]() { QSet higher = kFilter>(m_resources, [state](AbstractResource* res){ return res->state()>=state; }); QVector ret; QSet resources; for (int i=0, c=job->snapCount(); i snap(job->snap(i)); const auto snapname = snap->name(); SnapResource* res = m_resources.value(snapname); if (!res) { res = new SnapResource(snap, state, this); Q_ASSERT(res->packageName() == snapname); resources += res; } else { res->setState(state); higher.remove(res); } ret += res; } foreach(SnapResource* res, resources) m_resources[res->packageName()] = res; for(auto res: higher) { res->setState(AbstractResource::None); } if (!ret.isEmpty()) stream->resourcesFound(ret); stream->finish(); }); job->runAsync(); return stream; } void SnapBackend::setFetching(bool fetching) { if (m_fetching != fetching) { m_fetching = fetching; Q_EMIT fetchingChanged(); } else { qWarning() << "fetching already on state" << fetching; } } AbstractBackendUpdater* SnapBackend::backendUpdater() const { return m_updater; } AbstractReviewsBackend* SnapBackend::reviewsBackend() const { return m_reviews; } Transaction* SnapBackend::installApplication(AbstractResource* app, const AddonList& addons) { Q_ASSERT(addons.isEmpty()); return installApplication(app); } Transaction* SnapBackend::installApplication(AbstractResource* _app) { auto app = qobject_cast(_app); return new SnapTransaction(app, m_client.install(app->packageName()), Transaction::InstallRole); } Transaction* SnapBackend::removeApplication(AbstractResource* _app) { auto app = qobject_cast(_app); return new SnapTransaction(app, m_client.remove(app->packageName()), Transaction::RemoveRole); } QString SnapBackend::displayName() const { return QStringLiteral("Snap"); } void SnapBackend::refreshStates() { populate(m_client.list(), AbstractResource::Installed); } #include "SnapBackend.moc"