summaryrefslogtreecommitdiffstats
path: root/plugins/flatpak/gs-flatpak.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/flatpak/gs-flatpak.c4624
1 files changed, 4624 insertions, 0 deletions
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
new file mode 100644
index 0000000..aac55b1
--- /dev/null
+++ b/plugins/flatpak/gs-flatpak.c
@@ -0,0 +1,4624 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2016 Joaquim Rocha <jrocha@endlessm.com>
+ * Copyright (C) 2016-2018 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2016-2019 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/* Notes:
+ *
+ * All GsApp's created have management-plugin set to flatpak
+ * The GsApp:origin is the remote name, e.g. test-repo
+ *
+ * Two #FlatpakInstallation objects are kept: `installation_noninteractive` and
+ * `installation_interactive`. One has flatpak_installation_set_no_interaction()
+ * set to %TRUE, the other to %FALSE.
+ *
+ * This is because multiple #GsFlatpak operations can be ongoing with different
+ * interactive states (for example, a background refresh operation while the
+ * user is refining an app in the foreground), but the #FlatpakInstallation
+ * methods don’t support per-operation interactive state.
+ *
+ * Internally, each #FlatpakInstallation will use a separate #FlatpakDir
+ * pointing to the same repository. Those #FlatpakDirs will lock the repository
+ * when using it, so parallel operations won’t race.
+ */
+
+#include <config.h>
+
+#include <glib/gi18n.h>
+#include <xmlb.h>
+
+#include "gs-appstream.h"
+#include "gs-flatpak-app.h"
+#include "gs-flatpak.h"
+#include "gs-flatpak-utils.h"
+
+struct _GsFlatpak {
+ GObject parent_instance;
+ GsFlatpakFlags flags;
+ FlatpakInstallation *installation_noninteractive; /* (owned) */
+ FlatpakInstallation *installation_interactive; /* (owned) */
+ GPtrArray *installed_refs; /* must be entirely replaced rather than updated internally */
+ GMutex installed_refs_mutex;
+ GHashTable *broken_remotes;
+ GMutex broken_remotes_mutex;
+ GFileMonitor *monitor;
+ AsComponentScope scope;
+ GsPlugin *plugin;
+ XbSilo *silo;
+ GRWLock silo_lock;
+ gchar *id;
+ guint changed_id;
+ GHashTable *app_silos;
+ GMutex app_silos_mutex;
+ GHashTable *remote_title; /* gchar *remote name ~> gchar *remote title */
+ GMutex remote_title_mutex;
+ gboolean requires_full_rescan;
+ gint busy; /* (atomic) */
+ gboolean changed_while_busy;
+};
+
+G_DEFINE_TYPE (GsFlatpak, gs_flatpak, G_TYPE_OBJECT)
+
+static void
+gs_plugin_refine_item_scope (GsFlatpak *self, GsApp *app)
+{
+ if (gs_app_get_scope (app) == AS_COMPONENT_SCOPE_UNKNOWN) {
+ gboolean is_user = flatpak_installation_get_is_user (self->installation_noninteractive);
+ gs_app_set_scope (app, is_user ? AS_COMPONENT_SCOPE_USER : AS_COMPONENT_SCOPE_SYSTEM);
+ }
+}
+
+static void
+gs_flatpak_claim_app (GsFlatpak *self, GsApp *app)
+{
+ if (!gs_app_has_management_plugin (app, NULL))
+ return;
+
+ gs_app_set_management_plugin (app, self->plugin);
+ gs_flatpak_app_set_packaging_info (app);
+
+ /* only when we have a non-temp object */
+ if ((self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY) == 0) {
+ gs_app_set_scope (app, self->scope);
+ gs_flatpak_app_set_object_id (app, gs_flatpak_get_id (self));
+ }
+}
+
+static void
+gs_flatpak_ensure_remote_title (GsFlatpak *self,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->remote_title_mutex);
+ g_autoptr(GPtrArray) xremotes = NULL;
+
+ if (g_hash_table_size (self->remote_title))
+ return;
+
+ xremotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive), cancellable, NULL);
+ if (xremotes) {
+ guint ii;
+
+ for (ii = 0; ii < xremotes->len; ii++) {
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, ii);
+
+ if (flatpak_remote_get_disabled (xremote) ||
+ !flatpak_remote_get_name (xremote))
+ continue;
+
+ g_hash_table_insert (self->remote_title, g_strdup (flatpak_remote_get_name (xremote)), flatpak_remote_get_title (xremote));
+ }
+ }
+}
+
+static void
+gs_flatpak_set_app_origin (GsFlatpak *self,
+ GsApp *app,
+ const gchar *origin,
+ FlatpakRemote *xremote,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autoptr(GMutexLocker) locker = NULL;
+ g_autofree gchar *tmp = NULL;
+ const gchar *title = NULL;
+
+ g_return_if_fail (GS_IS_APP (app));
+ g_return_if_fail (origin != NULL);
+
+ if (xremote) {
+ tmp = flatpak_remote_get_title (xremote);
+ title = tmp;
+ } else {
+ locker = g_mutex_locker_new (&self->remote_title_mutex);
+ title = g_hash_table_lookup (self->remote_title, origin);
+ }
+
+ if (!title) {
+ g_autoptr(GPtrArray) xremotes = NULL;
+
+ xremotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive), cancellable, NULL);
+
+ if (xremotes) {
+ guint ii;
+
+ for (ii = 0; ii < xremotes->len; ii++) {
+ FlatpakRemote *yremote = g_ptr_array_index (xremotes, ii);
+
+ if (flatpak_remote_get_disabled (yremote))
+ continue;
+
+ if (g_strcmp0 (flatpak_remote_get_name (yremote), origin) == 0) {
+ title = flatpak_remote_get_title (yremote);
+
+ if (!locker)
+ locker = g_mutex_locker_new (&self->remote_title_mutex);
+
+ /* Takes ownership of the 'title' */
+ g_hash_table_insert (self->remote_title, g_strdup (origin), (gpointer) title);
+ break;
+ }
+ }
+ }
+ }
+
+ if (g_strcmp0 (origin, "flathub-beta") == 0 ||
+ g_strcmp0 (gs_app_get_branch (app), "devel") == 0 ||
+ g_strcmp0 (gs_app_get_branch (app), "master") == 0 ||
+ (gs_app_get_branch (app) && g_str_has_suffix (gs_app_get_branch (app), "beta")))
+ gs_app_add_quirk (app, GS_APP_QUIRK_DEVELOPMENT_SOURCE);
+
+ gs_app_set_origin (app, origin);
+ gs_app_set_origin_ui (app, title);
+}
+
+static void
+gs_flatpak_claim_app_list (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive)
+{
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+
+ /* Do not claim ownership of a wildcard app */
+ if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD))
+ continue;
+
+ if (gs_app_get_origin (app))
+ gs_flatpak_set_app_origin (self, app, gs_app_get_origin (app), NULL, interactive, NULL);
+
+ gs_flatpak_claim_app (self, app);
+ }
+}
+
+static void
+gs_flatpak_set_kind_from_flatpak (GsApp *app, FlatpakRef *xref)
+{
+ if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_APP) {
+ gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
+ } else if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_RUNTIME) {
+ const gchar *id = gs_app_get_id (app);
+ /* this is anything that's not an app, including locales
+ * sources and debuginfo */
+ if (g_str_has_suffix (id, ".Locale")) {
+ gs_app_set_kind (app, AS_COMPONENT_KIND_LOCALIZATION);
+ } else if (g_str_has_suffix (id, ".Debug") ||
+ g_str_has_suffix (id, ".Sources") ||
+ g_str_has_prefix (id, "org.freedesktop.Platform.Icontheme.") ||
+ g_str_has_prefix (id, "org.gtk.Gtk3theme.")) {
+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC);
+ } else {
+ gs_app_set_kind (app, AS_COMPONENT_KIND_RUNTIME);
+ }
+ }
+}
+
+static guint
+gs_get_strv_index (const gchar * const *strv,
+ const gchar *value)
+{
+ guint ii;
+
+ for (ii = 0; strv[ii]; ii++) {
+ if (g_str_equal (strv[ii], value))
+ break;
+ }
+
+ return ii;
+}
+
+static GsAppPermissions *
+perms_from_metadata (GKeyFile *keyfile)
+{
+ char **strv;
+ char *str;
+ GsAppPermissions *permissions = gs_app_permissions_new ();
+ GsAppPermissionsFlags flags = GS_APP_PERMISSIONS_FLAGS_UNKNOWN;
+
+ strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL);
+ if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "system-bus"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_SYSTEM_BUS;
+ if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "session-bus"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_SESSION_BUS;
+ if (strv != NULL &&
+ !g_strv_contains ((const gchar * const*)strv, "fallback-x11") &&
+ g_strv_contains ((const gchar * const*)strv, "x11"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_X11;
+ g_strfreev (strv);
+
+ strv = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL);
+ if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "all"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_DEVICES;
+ g_strfreev (strv);
+
+ strv = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL);
+ if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "network"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_NETWORK;
+ g_strfreev (strv);
+
+ strv = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL);
+ if (strv != NULL) {
+ const struct {
+ const gchar *key;
+ GsAppPermissionsFlags perm;
+ } filesystems_access[] = {
+ /* Reference: https://docs.flatpak.org/en/latest/flatpak-command-reference.html#idm45858571325264 */
+ { "home", GS_APP_PERMISSIONS_FLAGS_HOME_FULL },
+ { "home:rw", GS_APP_PERMISSIONS_FLAGS_HOME_FULL },
+ { "home:ro", GS_APP_PERMISSIONS_FLAGS_HOME_READ },
+ { "~", GS_APP_PERMISSIONS_FLAGS_HOME_FULL },
+ { "~:rw", GS_APP_PERMISSIONS_FLAGS_HOME_FULL },
+ { "~:ro", GS_APP_PERMISSIONS_FLAGS_HOME_READ },
+ { "host", GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL },
+ { "host:rw", GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL },
+ { "host:ro", GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ },
+ { "xdg-download", GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL },
+ { "xdg-download:rw", GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL },
+ { "xdg-download:ro", GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ },
+ { "xdg-data/flatpak/overrides:create", GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX }
+ };
+ guint filesystems_hits = 0;
+ guint strv_len = g_strv_length (strv);
+
+ for (guint i = 0; i < G_N_ELEMENTS (filesystems_access); i++) {
+ guint index = gs_get_strv_index ((const gchar * const *) strv, filesystems_access[i].key);
+ if (index < strv_len) {
+ flags |= filesystems_access[i].perm;
+ filesystems_hits++;
+ /* Mark it as used */
+ strv[index][0] = '\0';
+ }
+ }
+
+ if ((flags & GS_APP_PERMISSIONS_FLAGS_HOME_FULL) != 0)
+ flags = flags & ~GS_APP_PERMISSIONS_FLAGS_HOME_READ;
+ if ((flags & GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) != 0)
+ flags = flags & ~GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ;
+ if ((flags & GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL) != 0)
+ flags = flags & ~GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ;
+
+ if (strv_len > filesystems_hits) {
+ /* Cover those not being part of the above filesystem_access array */
+ const struct {
+ const gchar *prefix;
+ const gchar *title;
+ const gchar *title_subdir;
+ } filesystems_other[] = {
+ /* Reference: https://docs.flatpak.org/en/latest/flatpak-command-reference.html#idm45858571325264 */
+ { "/", NULL, N_("System folder %s") },
+ { "home/", NULL, N_("Home subfolder %s") },
+ { "~/", NULL, N_("Home subfolder %s") },
+ { "host-os", N_("Host system folders"), NULL },
+ { "host-etc", N_("Host system configuration from /etc"), NULL },
+ { "xdg-desktop", N_("Desktop folder"), N_("Desktop subfolder %s") },
+ { "xdg-documents", N_("Documents folder"), N_("Documents subfolder %s") },
+ { "xdg-music", N_("Music folder"), N_("Music subfolder %s") },
+ { "xdg-pictures", N_("Pictures folder"), N_("Pictures subfolder %s") },
+ { "xdg-public-share", N_("Public Share folder"), N_("Public Share subfolder %s") },
+ { "xdg-videos", N_("Videos folder"), N_("Videos subfolder %s") },
+ { "xdg-templates", N_("Templates folder"), N_("Templates subfolder %s") },
+ { "xdg-cache", N_("User cache folder"), N_("User cache subfolder %s") },
+ { "xdg-config", N_("User configuration folder"), N_("User configuration subfolder %s") },
+ { "xdg-data", N_("User data folder"), N_("User data subfolder %s") },
+ { "xdg-run", N_("User runtime folder"), N_("User runtime subfolder %s") }
+ };
+
+ flags |= GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER;
+
+ for (guint j = 0; strv[j]; j++) {
+ gchar *perm = strv[j];
+ gboolean is_readonly;
+ gchar *colon;
+ guint i;
+
+ /* Already handled by the flags */
+ if (!perm[0])
+ continue;
+
+ is_readonly = g_str_has_suffix (perm, ":ro");
+ colon = strrchr (perm, ':');
+ /* modifiers are ":ro", ":rw", ":create", where ":create" is ":rw" + create
+ and ":rw" is default; treat ":create" as ":rw" */
+ if (colon) {
+ /* Completeness check */
+ if (!g_str_equal (colon, ":ro") &&
+ !g_str_equal (colon, ":rw") &&
+ !g_str_equal (colon, ":create"))
+ g_debug ("Unknown filesystem permission modifier '%s' from '%s'", colon, perm);
+ /* cut it off */
+ *colon = '\0';
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (filesystems_other); i++) {
+ if (g_str_has_prefix (perm, filesystems_other[i].prefix)) {
+ g_autofree gchar *title_tmp = NULL;
+ const gchar *slash, *title = NULL;
+ slash = strchr (perm, '/');
+ /* Catch and ignore invalid permission definitions */
+ if (slash && filesystems_other[i].title_subdir != NULL) {
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ title_tmp = g_strdup_printf (
+ _(filesystems_other[i].title_subdir),
+ slash + (slash == perm ? 0 : 1));
+ #pragma GCC diagnostic pop
+ title = title_tmp;
+ } else if (!slash && filesystems_other[i].title != NULL) {
+ title = _(filesystems_other[i].title);
+ }
+ if (title != NULL) {
+ if (is_readonly)
+ gs_app_permissions_add_filesystem_read (permissions, title);
+ else
+ gs_app_permissions_add_filesystem_full (permissions, title);
+ }
+ break;
+ }
+ }
+
+ /* Nothing matched, use a generic entry */
+ if (i == G_N_ELEMENTS (filesystems_other)) {
+ g_autofree gchar *title = g_strdup_printf (_("Filesystem access to %s"), perm);
+ if (is_readonly)
+ gs_app_permissions_add_filesystem_read (permissions, title);
+ else
+ gs_app_permissions_add_filesystem_full (permissions, title);
+ }
+ }
+ }
+ }
+ g_strfreev (strv);
+
+ str = g_key_file_get_string (keyfile, "Session Bus Policy", "ca.desrt.dconf", NULL);
+ if (str != NULL && g_str_equal (str, "talk"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_SETTINGS;
+ g_free (str);
+
+ if (!(flags & GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX)) {
+ str = g_key_file_get_string (keyfile, "Session Bus Policy", "org.freedesktop.Flatpak", NULL);
+ if (str != NULL && g_str_equal (str, "talk"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX;
+ g_free (str);
+ }
+
+ if (!(flags & GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX)) {
+ str = g_key_file_get_string (keyfile, "Session Bus Policy", "org.freedesktop.impl.portal.PermissionStore", NULL);
+ if (str != NULL && g_str_equal (str, "talk"))
+ flags |= GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX;
+ g_free (str);
+ }
+
+ /* no permissions set */
+ if (flags == GS_APP_PERMISSIONS_FLAGS_UNKNOWN)
+ flags = GS_APP_PERMISSIONS_FLAGS_NONE;
+
+ gs_app_permissions_set_flags (permissions, flags);
+ gs_app_permissions_seal (permissions);
+
+ return permissions;
+}
+
+static void
+gs_flatpak_set_update_permissions (GsFlatpak *self,
+ GsApp *app,
+ FlatpakInstalledRef *xref,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autoptr(GBytes) old_bytes = NULL;
+ g_autoptr(GKeyFile) old_keyfile = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+ g_autoptr(GsAppPermissions) additional_permissions = gs_app_permissions_new ();
+ g_autoptr(GError) error_local = NULL;
+
+ old_bytes = flatpak_installed_ref_load_metadata (FLATPAK_INSTALLED_REF (xref), NULL, NULL);
+ old_keyfile = g_key_file_new ();
+ g_key_file_load_from_data (old_keyfile,
+ g_bytes_get_data (old_bytes, NULL),
+ g_bytes_get_size (old_bytes),
+ 0, NULL);
+
+ bytes = flatpak_installation_fetch_remote_metadata_sync (gs_flatpak_get_installation (self, interactive),
+ gs_app_get_origin (app),
+ FLATPAK_REF (xref),
+ cancellable,
+ &error_local);
+ if (bytes == NULL) {
+ g_debug ("Failed to get metadata for remote ‘%s’: %s",
+ gs_app_get_origin (app), error_local->message);
+ g_clear_error (&error_local);
+ gs_app_permissions_set_flags (additional_permissions, GS_APP_PERMISSIONS_FLAGS_UNKNOWN);
+ } else {
+ g_autoptr(GsAppPermissions) old_permissions = NULL;
+ g_autoptr(GsAppPermissions) new_permissions = NULL;
+ const GPtrArray *new_paths;
+
+ keyfile = g_key_file_new ();
+ g_key_file_load_from_data (keyfile,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes),
+ 0, NULL);
+
+ old_permissions = perms_from_metadata (old_keyfile);
+ new_permissions = perms_from_metadata (keyfile);
+
+ gs_app_permissions_set_flags (additional_permissions,
+ gs_app_permissions_get_flags (new_permissions) &
+ ~gs_app_permissions_get_flags (old_permissions));
+
+ new_paths = gs_app_permissions_get_filesystem_read (new_permissions);
+ for (guint i = 0; new_paths && i < new_paths->len; i++) {
+ const gchar *new_path = g_ptr_array_index (new_paths, i);
+ if (!gs_app_permissions_contains_filesystem_read (old_permissions, new_path))
+ gs_app_permissions_add_filesystem_read (additional_permissions, new_path);
+ }
+
+ new_paths = gs_app_permissions_get_filesystem_full (new_permissions);
+ for (guint i = 0; new_paths && i < new_paths->len; i++) {
+ const gchar *new_path = g_ptr_array_index (new_paths, i);
+ if (!gs_app_permissions_contains_filesystem_full (old_permissions, new_path))
+ gs_app_permissions_add_filesystem_full (additional_permissions, new_path);
+ }
+ }
+
+ /* no new permissions set */
+ if (gs_app_permissions_get_flags (additional_permissions) == GS_APP_PERMISSIONS_FLAGS_UNKNOWN)
+ gs_app_permissions_set_flags (additional_permissions, GS_APP_PERMISSIONS_FLAGS_NONE);
+
+ gs_app_permissions_seal (additional_permissions);
+ gs_app_set_update_permissions (app, additional_permissions);
+
+ if (gs_app_permissions_get_flags (additional_permissions) != GS_APP_PERMISSIONS_FLAGS_NONE)
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEW_PERMISSIONS);
+ else
+ gs_app_remove_quirk (app, GS_APP_QUIRK_NEW_PERMISSIONS);
+}
+
+static void
+gs_flatpak_set_metadata (GsFlatpak *self, GsApp *app, FlatpakRef *xref)
+{
+ g_autofree gchar *ref_tmp = flatpak_ref_format_ref (FLATPAK_REF (xref));
+ guint64 installed_size = 0, download_size = 0;
+
+ /* core */
+ gs_flatpak_claim_app (self, app);
+ gs_app_set_branch (app, flatpak_ref_get_branch (xref));
+ gs_app_add_source (app, ref_tmp);
+ gs_plugin_refine_item_scope (self, app);
+
+ /* flatpak specific */
+ gs_flatpak_app_set_ref_kind (app, flatpak_ref_get_kind (xref));
+ gs_flatpak_app_set_ref_name (app, flatpak_ref_get_name (xref));
+ gs_flatpak_app_set_ref_arch (app, flatpak_ref_get_arch (xref));
+ gs_flatpak_app_set_commit (app, flatpak_ref_get_commit (xref));
+
+ /* map the flatpak kind to the gnome-software kind */
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_UNKNOWN ||
+ gs_app_get_kind (app) == AS_COMPONENT_KIND_GENERIC) {
+ gs_flatpak_set_kind_from_flatpak (app, xref);
+ }
+
+ if (FLATPAK_IS_REMOTE_REF (xref) && flatpak_remote_ref_get_eol (FLATPAK_REMOTE_REF (xref)) != NULL)
+ gs_app_set_metadata (app, "GnomeSoftware::EolReason", flatpak_remote_ref_get_eol (FLATPAK_REMOTE_REF (xref)));
+ else if (FLATPAK_IS_INSTALLED_REF (xref) && flatpak_installed_ref_get_eol (FLATPAK_INSTALLED_REF (xref)) != NULL)
+ gs_app_set_metadata (app, "GnomeSoftware::EolReason", flatpak_installed_ref_get_eol (FLATPAK_INSTALLED_REF (xref)));
+
+ if (FLATPAK_IS_REMOTE_REF (xref)) {
+ installed_size = flatpak_remote_ref_get_installed_size (FLATPAK_REMOTE_REF (xref));
+ download_size = flatpak_remote_ref_get_download_size (FLATPAK_REMOTE_REF (xref));
+ } else if (FLATPAK_IS_INSTALLED_REF (xref)) {
+ installed_size = flatpak_installed_ref_get_installed_size (FLATPAK_INSTALLED_REF (xref));
+ }
+
+ gs_app_set_size_installed (app, (installed_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, installed_size);
+ gs_app_set_size_download (app, (download_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, download_size);
+}
+
+static GsApp *
+gs_flatpak_create_app (GsFlatpak *self,
+ const gchar *origin,
+ FlatpakRef *xref,
+ FlatpakRemote *xremote,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ GsApp *app_cached;
+ g_autoptr(GsApp) app = NULL;
+
+ /* create a temp GsApp */
+ app = gs_app_new (flatpak_ref_get_name (xref));
+ gs_flatpak_set_metadata (self, app, xref);
+ if (origin != NULL) {
+ gs_flatpak_set_app_origin (self, app, origin, xremote, interactive, cancellable);
+
+ if (!(self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY)) {
+ /* return the ref'd cached copy, only if the origin is known */
+ app_cached = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
+ if (app_cached != NULL)
+ return app_cached;
+ }
+ }
+
+ /* fallback values */
+ if (gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME) {
+ g_autoptr(GIcon) icon = NULL;
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
+ flatpak_ref_get_name (FLATPAK_REF (xref)));
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
+ "Framework for applications");
+ gs_app_set_version (app, flatpak_ref_get_branch (FLATPAK_REF (xref)));
+ icon = g_themed_icon_new ("system-component-runtime");
+ gs_app_add_icon (app, icon);
+ }
+
+ /* Don't add NULL origin apps to the cache. If the app is later set to
+ * origin x the cache may return it as a match for origin y since the cache
+ * hash table uses as_utils_data_id_equal() as the equal func and a NULL
+ * origin becomes a "*" in gs_utils_build_unique_id().
+ */
+ if (origin != NULL && !(self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY))
+ gs_plugin_cache_add (self->plugin, NULL, app);
+
+ /* no existing match, just steal the temp object */
+ return g_steal_pointer (&app);
+}
+
+static GsApp *
+gs_flatpak_create_source (GsFlatpak *self, FlatpakRemote *xremote)
+{
+ GsApp *app_cached;
+ g_autoptr(GsApp) app = NULL;
+
+ /* create a temp GsApp */
+ app = gs_flatpak_app_new_from_remote (self->plugin, xremote,
+ flatpak_installation_get_is_user (self->installation_noninteractive));
+ gs_flatpak_claim_app (self, app);
+
+ /* we already have one, returned the ref'd cached copy */
+ app_cached = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
+ if (app_cached != NULL)
+ return app_cached;
+
+ /* no existing match, just steal the temp object */
+ gs_plugin_cache_add (self->plugin, NULL, app);
+ return g_steal_pointer (&app);
+}
+
+static void
+gs_flatpak_invalidate_silo (GsFlatpak *self)
+{
+ g_rw_lock_writer_lock (&self->silo_lock);
+ if (self->silo != NULL)
+ xb_silo_invalidate (self->silo);
+ g_rw_lock_writer_unlock (&self->silo_lock);
+}
+
+static void
+gs_flatpak_internal_data_changed (GsFlatpak *self)
+{
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ /* drop the installed refs cache */
+ locker = g_mutex_locker_new (&self->installed_refs_mutex);
+ g_clear_pointer (&self->installed_refs, g_ptr_array_unref);
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ /* drop the remote title cache */
+ locker = g_mutex_locker_new (&self->remote_title_mutex);
+ g_hash_table_remove_all (self->remote_title);
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ /* give all the repos a second chance */
+ locker = g_mutex_locker_new (&self->broken_remotes_mutex);
+ g_hash_table_remove_all (self->broken_remotes);
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ gs_flatpak_invalidate_silo (self);
+
+ self->requires_full_rescan = TRUE;
+}
+
+static gboolean
+gs_flatpak_claim_changed_idle_cb (gpointer user_data)
+{
+ GsFlatpak *self = user_data;
+
+ gs_flatpak_internal_data_changed (self);
+ gs_plugin_cache_invalidate (self->plugin);
+ gs_plugin_reload (self->plugin);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gs_plugin_flatpak_changed_cb (GFileMonitor *monitor,
+ GFile *child,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GsFlatpak *self)
+{
+ if (gs_flatpak_get_busy (self)) {
+ self->changed_while_busy = TRUE;
+ } else {
+ gs_flatpak_claim_changed_idle_cb (self);
+ }
+}
+
+static gboolean
+gs_flatpak_add_flatpak_keyword_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0)
+ gs_appstream_component_add_keyword (bn, "flatpak");
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_fix_id_desktop_suffix_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+ g_auto(GStrv) split = NULL;
+ g_autoptr(XbBuilderNode) id = xb_builder_node_get_child (bn, "id", NULL);
+ g_autoptr(XbBuilderNode) bundle = xb_builder_node_get_child (bn, "bundle", NULL);
+ if (id == NULL || bundle == NULL)
+ return TRUE;
+ split = g_strsplit (xb_builder_node_get_text (bundle), "/", -1);
+ if (g_strv_length (split) != 4)
+ return TRUE;
+ if (g_strcmp0 (xb_builder_node_get_text (id), split[1]) != 0) {
+ g_debug ("fixing up <id>%s</id> to %s",
+ xb_builder_node_get_text (id), split[1]);
+ gs_appstream_component_add_provide (bn, xb_builder_node_get_text (id));
+ xb_builder_node_set_text (id, split[1], -1);
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_add_bundle_tag_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ const char *app_ref = (char *)user_data;
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+ g_autoptr(XbBuilderNode) id = xb_builder_node_get_child (bn, "id", NULL);
+ g_autoptr(XbBuilderNode) bundle = xb_builder_node_get_child (bn, "bundle", NULL);
+ if (id == NULL || bundle != NULL)
+ return TRUE;
+ g_debug ("adding <bundle> tag for %s", app_ref);
+ xb_builder_node_insert_text (bn, "bundle", app_ref, "type", "flatpak", NULL);
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_fix_metadata_tag_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+ g_autoptr(XbBuilderNode) metadata = xb_builder_node_get_child (bn, "metadata", NULL);
+ if (metadata != NULL)
+ xb_builder_node_set_element (metadata, "custom");
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_set_origin_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ const char *remote_name = (char *)user_data;
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "components") == 0) {
+ xb_builder_node_set_attr (bn, "origin",
+ remote_name);
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_filter_default_branch_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ const gchar *default_branch = (const gchar *) user_data;
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+ g_autoptr(XbBuilderNode) bc = xb_builder_node_get_child (bn, "bundle", NULL);
+ g_auto(GStrv) split = NULL;
+ if (bc == NULL) {
+ g_debug ("no bundle for component");
+ return TRUE;
+ }
+ split = g_strsplit (xb_builder_node_get_text (bc), "/", -1);
+ if (split == NULL || g_strv_length (split) != 4)
+ return TRUE;
+ if (g_strcmp0 (split[3], default_branch) != 0) {
+ g_debug ("not adding app with branch %s as filtering to %s",
+ split[3], default_branch);
+ xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE);
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_filter_noenumerate_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ const gchar *main_ref = (const gchar *) user_data;
+
+ if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+ g_autoptr(XbBuilderNode) bc = xb_builder_node_get_child (bn, "bundle", NULL);
+ if (bc == NULL) {
+ g_debug ("no bundle for component");
+ return TRUE;
+ }
+ if (g_strcmp0 (xb_builder_node_get_text (bc), main_ref) != 0) {
+ g_debug ("not adding app %s as filtering to %s",
+ xb_builder_node_get_text (bc), main_ref);
+ xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE);
+ }
+ }
+ return TRUE;
+}
+
+#if LIBXMLB_CHECK_VERSION(0,3,0)
+static gboolean
+gs_flatpak_tokenize_cb (XbBuilderFixup *self,
+ XbBuilderNode *bn,
+ gpointer user_data,
+ GError **error)
+{
+ const gchar * const elements_to_tokenize[] = {
+ "id",
+ "keyword",
+ "launchable",
+ "mimetype",
+ "name",
+ "summary",
+ NULL };
+ if (xb_builder_node_get_element (bn) != NULL &&
+ g_strv_contains (elements_to_tokenize, xb_builder_node_get_element (bn)))
+ xb_builder_node_tokenize_text (bn);
+ return TRUE;
+}
+#endif
+
+static void
+fixup_flatpak_appstream_xml (XbBuilderSource *source,
+ const char *origin)
+{
+ g_autoptr(XbBuilderFixup) fixup1 = NULL;
+ g_autoptr(XbBuilderFixup) fixup2 = NULL;
+ g_autoptr(XbBuilderFixup) fixup3 = NULL;
+#if LIBXMLB_CHECK_VERSION(0,3,0)
+ g_autoptr(XbBuilderFixup) fixup5 = NULL;
+#endif
+
+ /* add the flatpak search keyword */
+ fixup1 = xb_builder_fixup_new ("AddKeywordFlatpak",
+ gs_flatpak_add_flatpak_keyword_cb,
+ NULL, NULL);
+ xb_builder_fixup_set_max_depth (fixup1, 2);
+ xb_builder_source_add_fixup (source, fixup1);
+
+ /* ensure the <id> matches the flatpak ref ID */
+ fixup2 = xb_builder_fixup_new ("FixIdDesktopSuffix",
+ gs_flatpak_fix_id_desktop_suffix_cb,
+ NULL, NULL);
+ xb_builder_fixup_set_max_depth (fixup2, 2);
+ xb_builder_source_add_fixup (source, fixup2);
+
+ /* Fixup <metadata> to <custom> for appstream versions >= 0.9 */
+ fixup3 = xb_builder_fixup_new ("FixMetadataTag",
+ gs_flatpak_fix_metadata_tag_cb,
+ NULL, NULL);
+ xb_builder_fixup_set_max_depth (fixup3, 2);
+ xb_builder_source_add_fixup (source, fixup3);
+
+#if LIBXMLB_CHECK_VERSION(0,3,0)
+ fixup5 = xb_builder_fixup_new ("TextTokenize",
+ gs_flatpak_tokenize_cb,
+ NULL, NULL);
+ xb_builder_fixup_set_max_depth (fixup5, 2);
+ xb_builder_source_add_fixup (source, fixup5);
+#endif
+
+ if (origin != NULL) {
+ g_autoptr(XbBuilderFixup) fixup4 = NULL;
+
+ /* override the *AppStream* origin */
+ fixup4 = xb_builder_fixup_new ("SetOrigin",
+ gs_flatpak_set_origin_cb,
+ g_strdup (origin), g_free);
+ xb_builder_fixup_set_max_depth (fixup4, 1);
+ xb_builder_source_add_fixup (source, fixup4);
+ }
+}
+
+static gboolean
+gs_flatpak_refresh_appstream_remote (GsFlatpak *self,
+ const gchar *remote_name,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error);
+
+static gboolean
+gs_flatpak_add_apps_from_xremote (GsFlatpak *self,
+ XbBuilder *builder,
+ FlatpakRemote *xremote,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *appstream_dir_fn = NULL;
+ g_autofree gchar *appstream_fn = NULL;
+ g_autofree gchar *icon_prefix = NULL;
+ g_autofree gchar *default_branch = NULL;
+ g_autoptr(GFile) appstream_dir = NULL;
+ g_autoptr(GFile) file_xml = NULL;
+ g_autoptr(GSettings) settings = NULL;
+ g_autoptr(XbBuilderNode) info = NULL;
+ g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+ const gchar *remote_name = flatpak_remote_get_name (xremote);
+ gboolean did_refresh = FALSE;
+
+ /* get the AppStream data location */
+ appstream_dir = flatpak_remote_get_appstream_dir (xremote, NULL);
+ if (appstream_dir == NULL) {
+ g_autoptr(GError) error_local = NULL;
+ g_debug ("no appstream dir for %s, trying refresh...",
+ remote_name);
+
+ if (!gs_flatpak_refresh_appstream_remote (self, remote_name, interactive, cancellable, &error_local)) {
+ g_debug ("Failed to refresh appstream data for '%s': %s", remote_name, error_local->message);
+ if (g_error_matches (error_local, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED)) {
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ locker = g_mutex_locker_new (&self->broken_remotes_mutex);
+
+ /* don't try to fetch this again until refresh() */
+ g_hash_table_insert (self->broken_remotes,
+ g_strdup (remote_name),
+ GUINT_TO_POINTER (1));
+ }
+ return TRUE;
+ }
+
+ appstream_dir = flatpak_remote_get_appstream_dir (xremote, NULL);
+ if (appstream_dir == NULL) {
+ g_debug ("no appstream dir for %s even after refresh, skipping",
+ remote_name);
+ return TRUE;
+ }
+
+ did_refresh = TRUE;
+ }
+
+ /* load the file into a temp silo */
+ appstream_dir_fn = g_file_get_path (appstream_dir);
+ appstream_fn = g_build_filename (appstream_dir_fn, "appstream.xml.gz", NULL);
+ if (!g_file_test (appstream_fn, G_FILE_TEST_EXISTS)) {
+ g_autoptr(GError) error_local = NULL;
+ g_debug ("no appstream metadata found for '%s' (file: %s), %s",
+ remote_name,
+ appstream_fn,
+ did_refresh ? "skipping" : "trying refresh...");
+ if (did_refresh)
+ return TRUE;
+
+ if (!gs_flatpak_refresh_appstream_remote (self, remote_name, interactive, cancellable, &error_local)) {
+ g_debug ("Failed to refresh appstream data for '%s': %s", remote_name, error_local->message);
+ if (g_error_matches (error_local, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED)) {
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ locker = g_mutex_locker_new (&self->broken_remotes_mutex);
+
+ /* don't try to fetch this again until refresh() */
+ g_hash_table_insert (self->broken_remotes,
+ g_strdup (remote_name),
+ GUINT_TO_POINTER (1));
+ }
+ return TRUE;
+ }
+
+ if (!g_file_test (appstream_fn, G_FILE_TEST_EXISTS)) {
+ g_debug ("no appstream metadata found for '%s', even after refresh (file: %s), skipping",
+ remote_name,
+ appstream_fn);
+ return TRUE;
+ }
+ }
+
+ /* add source */
+ file_xml = g_file_new_for_path (appstream_fn);
+ if (!xb_builder_source_load_file (source, file_xml,
+ XB_BUILDER_SOURCE_FLAG_WATCH_FILE |
+ XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
+ cancellable,
+ error))
+ return FALSE;
+
+ fixup_flatpak_appstream_xml (source, remote_name);
+
+ /* add metadata */
+ icon_prefix = g_build_filename (appstream_dir_fn, "icons", NULL);
+ info = xb_builder_node_insert (NULL, "info", NULL);
+ xb_builder_node_insert_text (info, "scope", as_component_scope_to_string (self->scope), NULL);
+ xb_builder_node_insert_text (info, "icon-prefix", icon_prefix, NULL);
+ xb_builder_source_set_info (source, info);
+
+ /* only add the specific app for noenumerate=true */
+ if (flatpak_remote_get_noenumerate (xremote)) {
+ g_autofree gchar *main_ref = NULL;
+
+ main_ref = flatpak_remote_get_main_ref (xremote);
+
+ if (main_ref != NULL) {
+ g_autoptr(XbBuilderFixup) fixup = NULL;
+ fixup = xb_builder_fixup_new ("FilterNoEnumerate",
+ gs_flatpak_filter_noenumerate_cb,
+ g_strdup (main_ref),
+ g_free);
+ xb_builder_fixup_set_max_depth (fixup, 2);
+ xb_builder_source_add_fixup (source, fixup);
+ }
+ }
+
+ /* do we want to filter to the default branch */
+ settings = g_settings_new ("org.gnome.software");
+ default_branch = flatpak_remote_get_default_branch (xremote);
+ if (g_settings_get_boolean (settings, "filter-default-branch") &&
+ default_branch != NULL) {
+ g_autoptr(XbBuilderFixup) fixup = NULL;
+ fixup = xb_builder_fixup_new ("FilterDefaultbranch",
+ gs_flatpak_filter_default_branch_cb,
+ flatpak_remote_get_default_branch (xremote),
+ g_free);
+ xb_builder_fixup_set_max_depth (fixup, 2);
+ xb_builder_source_add_fixup (source, fixup);
+ }
+
+ /* success */
+ xb_builder_import_source (builder, source);
+ return TRUE;
+}
+
+static GInputStream *
+gs_plugin_appstream_load_desktop_cb (XbBuilderSource *self,
+ XbBuilderSourceCtx *ctx,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *xml = NULL;
+ g_autoptr(AsComponent) cpt = as_component_new ();
+ g_autoptr(AsContext) actx = as_context_new ();
+ g_autoptr(GBytes) bytes = NULL;
+ gboolean ret;
+
+ bytes = xb_builder_source_ctx_get_bytes (ctx, cancellable, error);
+ if (bytes == NULL)
+ return NULL;
+
+ as_component_set_id (cpt, xb_builder_source_ctx_get_filename (ctx));
+ ret = as_component_load_from_bytes (cpt,
+ actx,
+ AS_FORMAT_KIND_DESKTOP_ENTRY,
+ bytes,
+ error);
+ if (!ret)
+ return NULL;
+ xml = as_component_to_xml_data (cpt, actx, error);
+ if (xml == NULL)
+ return NULL;
+
+ return g_memory_input_stream_new_from_data (g_steal_pointer (&xml), (gssize) -1, g_free);
+}
+
+static gboolean
+gs_flatpak_load_desktop_fn (GsFlatpak *self,
+ XbBuilder *builder,
+ const gchar *filename,
+ const gchar *icon_prefix,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GFile) file = g_file_new_for_path (filename);
+ g_autoptr(XbBuilderNode) info = NULL;
+ g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+ g_autoptr(XbBuilderFixup) fixup = NULL;
+
+ /* add support for desktop files */
+ xb_builder_source_add_adapter (source, "application/x-desktop",
+ gs_plugin_appstream_load_desktop_cb, NULL, NULL);
+
+ /* add the flatpak search keyword */
+ fixup = xb_builder_fixup_new ("AddKeywordFlatpak",
+ gs_flatpak_add_flatpak_keyword_cb,
+ self, NULL);
+ xb_builder_fixup_set_max_depth (fixup, 2);
+ xb_builder_source_add_fixup (source, fixup);
+
+ /* set the component metadata */
+ info = xb_builder_node_insert (NULL, "info", NULL);
+ xb_builder_node_insert_text (info, "scope", as_component_scope_to_string (self->scope), NULL);
+ xb_builder_node_insert_text (info, "icon-prefix", icon_prefix, NULL);
+ xb_builder_source_set_info (source, info);
+
+ /* add source */
+ if (!xb_builder_source_load_file (source, file,
+#if LIBXMLB_CHECK_VERSION(0, 2, 0)
+ XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY,
+#else
+ XB_BUILDER_SOURCE_FLAG_WATCH_FILE,
+#endif
+ cancellable,
+ error)) {
+ return FALSE;
+ }
+
+ /* success */
+ xb_builder_import_source (builder, source);
+ return TRUE;
+}
+
+static void
+gs_flatpak_rescan_installed (GsFlatpak *self,
+ XbBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *fn;
+ g_autoptr(GFile) path = NULL;
+ g_autoptr(GDir) dir = NULL;
+ g_autofree gchar *path_str = NULL;
+ g_autofree gchar *path_exports = NULL;
+ g_autofree gchar *path_apps = NULL;
+
+ /* add all installed desktop files */
+ path = flatpak_installation_get_path (self->installation_noninteractive);
+ path_str = g_file_get_path (path);
+ path_exports = g_build_filename (path_str, "exports", NULL);
+ path_apps = g_build_filename (path_exports, "share", "applications", NULL);
+ dir = g_dir_open (path_apps, 0, NULL);
+ if (dir == NULL)
+ return;
+ while ((fn = g_dir_read_name (dir)) != NULL) {
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* ignore */
+ if (g_strcmp0 (fn, "mimeinfo.cache") == 0)
+ continue;
+
+ /* parse desktop files */
+ filename = g_build_filename (path_apps, fn, NULL);
+ if (!gs_flatpak_load_desktop_fn (self,
+ builder,
+ filename,
+ path_exports,
+ cancellable,
+ &error_local)) {
+ g_debug ("ignoring %s: %s", filename, error_local->message);
+ continue;
+ }
+ }
+}
+
+static gboolean
+gs_flatpak_rescan_appstream_store (GsFlatpak *self,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *const *locales = g_get_language_names ();
+ g_autofree gchar *blobfn = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GPtrArray) xremotes = NULL;
+ g_autoptr(GRWLockReaderLocker) reader_locker = NULL;
+ g_autoptr(GRWLockWriterLocker) writer_locker = NULL;
+ g_autoptr(XbBuilder) builder = NULL;
+ g_autoptr(GMainContext) old_thread_default = NULL;
+
+ reader_locker = g_rw_lock_reader_locker_new (&self->silo_lock);
+ /* everything is okay */
+ if (self->silo != NULL && xb_silo_is_valid (self->silo))
+ return TRUE;
+ g_clear_pointer (&reader_locker, g_rw_lock_reader_locker_free);
+
+ /* drat! silo needs regenerating */
+ writer_locker = g_rw_lock_writer_locker_new (&self->silo_lock);
+ g_clear_object (&self->silo);
+
+ /* FIXME: https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1422 */
+ old_thread_default = g_main_context_ref_thread_default ();
+ if (old_thread_default == g_main_context_default ())
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+ if (old_thread_default != NULL)
+ g_main_context_pop_thread_default (old_thread_default);
+ builder = xb_builder_new ();
+ if (old_thread_default != NULL)
+ g_main_context_push_thread_default (old_thread_default);
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+
+ /* verbose profiling */
+ if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+ xb_builder_set_profile_flags (builder,
+ XB_SILO_PROFILE_FLAG_XPATH |
+ XB_SILO_PROFILE_FLAG_DEBUG);
+ }
+
+ /* add current locales */
+ for (guint i = 0; locales[i] != NULL; i++)
+ xb_builder_add_locale (builder, locales[i]);
+
+ /* go through each remote adding metadata */
+ xremotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive),
+ cancellable,
+ error);
+ if (xremotes == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < xremotes->len; i++) {
+ g_autoptr(GError) error_local = NULL;
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ if (flatpak_remote_get_disabled (xremote))
+ continue;
+ g_debug ("found remote %s",
+ flatpak_remote_get_name (xremote));
+ if (!gs_flatpak_add_apps_from_xremote (self, builder, xremote, interactive, cancellable, &error_local)) {
+ g_debug ("Failed to add apps from remote ‘%s’; skipping: %s",
+ flatpak_remote_get_name (xremote), error_local->message);
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ }
+ }
+
+ /* add any installed files without AppStream info */
+ gs_flatpak_rescan_installed (self, builder, cancellable, error);
+
+ /* create per-user cache */
+ blobfn = gs_utils_get_cache_filename (gs_flatpak_get_id (self),
+ "components.xmlb",
+ GS_UTILS_CACHE_FLAG_WRITEABLE |
+ GS_UTILS_CACHE_FLAG_CREATE_DIRECTORY,
+ error);
+ if (blobfn == NULL)
+ return FALSE;
+ file = g_file_new_for_path (blobfn);
+ g_debug ("ensuring %s", blobfn);
+
+ /* FIXME: https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1422 */
+ old_thread_default = g_main_context_ref_thread_default ();
+ if (old_thread_default == g_main_context_default ())
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+ if (old_thread_default != NULL)
+ g_main_context_pop_thread_default (old_thread_default);
+
+ self->silo = xb_builder_ensure (builder, file,
+ XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID |
+ XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+ cancellable, error);
+
+ if (old_thread_default != NULL)
+ g_main_context_push_thread_default (old_thread_default);
+
+ if (self->silo == NULL)
+ return FALSE;
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_rescan_app_data (GsFlatpak *self,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (self->requires_full_rescan) {
+ gboolean res = gs_flatpak_refresh (self, 60, interactive, cancellable, error);
+ if (res)
+ self->requires_full_rescan = FALSE;
+ else
+ gs_flatpak_internal_data_changed (self);
+ return res;
+ }
+
+ if (!gs_flatpak_rescan_appstream_store (self, interactive, cancellable, error)) {
+ gs_flatpak_internal_data_changed (self);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Returns with a read lock held on @self->silo_lock on success.
+ The *locker should be NULL when being called. */
+static gboolean
+ensure_flatpak_silo_with_locker (GsFlatpak *self,
+ GRWLockReaderLocker **locker,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* should not hold the lock when called */
+ g_return_val_if_fail (*locker == NULL, FALSE);
+
+ /* ensure valid */
+ if (!gs_flatpak_rescan_app_data (self, interactive, cancellable, error))
+ return FALSE;
+
+ *locker = g_rw_lock_reader_locker_new (&self->silo_lock);
+
+ while (self->silo == NULL) {
+ g_clear_pointer (locker, g_rw_lock_reader_locker_free);
+
+ if (!gs_flatpak_rescan_appstream_store (self, interactive, cancellable, error)) {
+ gs_flatpak_internal_data_changed (self);
+ return FALSE;
+ }
+
+ /* At this point either rescan_appstream_store() returned an error or it successfully
+ * initialised self->silo. There is the possibility that another thread will invalidate
+ * the silo before we regain the lock. If so, we’ll have to rescan again. */
+ *locker = g_rw_lock_reader_locker_new (&self->silo_lock);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_setup (GsFlatpak *self, GCancellable *cancellable, GError **error)
+{
+ /* watch for changes */
+ self->monitor = flatpak_installation_create_monitor (self->installation_noninteractive,
+ cancellable,
+ error);
+ if (self->monitor == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ self->changed_id =
+ g_signal_connect (self->monitor, "changed",
+ G_CALLBACK (gs_plugin_flatpak_changed_cb), self);
+
+ /* success */
+ return TRUE;
+}
+
+typedef struct {
+ GsPlugin *plugin;
+ GsApp *app;
+} GsFlatpakProgressHelper;
+
+static void
+gs_flatpak_progress_helper_free (GsFlatpakProgressHelper *phelper)
+{
+ g_object_unref (phelper->plugin);
+ if (phelper->app != NULL)
+ g_object_unref (phelper->app);
+ g_slice_free (GsFlatpakProgressHelper, phelper);
+}
+
+static GsFlatpakProgressHelper *
+gs_flatpak_progress_helper_new (GsPlugin *plugin, GsApp *app)
+{
+ GsFlatpakProgressHelper *phelper;
+ phelper = g_slice_new0 (GsFlatpakProgressHelper);
+ phelper->plugin = g_object_ref (plugin);
+ if (app != NULL)
+ phelper->app = g_object_ref (app);
+ return phelper;
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsFlatpakProgressHelper, gs_flatpak_progress_helper_free)
+
+static void
+gs_flatpak_progress_cb (const gchar *status,
+ guint progress,
+ gboolean estimating,
+ gpointer user_data)
+{
+ GsFlatpakProgressHelper *phelper = (GsFlatpakProgressHelper *) user_data;
+ GsPluginStatus plugin_status = GS_PLUGIN_STATUS_DOWNLOADING;
+
+ if (phelper->app != NULL) {
+ if (estimating)
+ gs_app_set_progress (phelper->app, GS_APP_PROGRESS_UNKNOWN);
+ else
+ gs_app_set_progress (phelper->app, progress);
+
+ switch (gs_app_get_state (phelper->app)) {
+ case GS_APP_STATE_INSTALLING:
+ plugin_status = GS_PLUGIN_STATUS_INSTALLING;
+ break;
+ case GS_APP_STATE_REMOVING:
+ plugin_status = GS_PLUGIN_STATUS_REMOVING;
+ break;
+ default:
+ break;
+ }
+ }
+ gs_plugin_status_update (phelper->plugin, phelper->app, plugin_status);
+}
+
+static gboolean
+gs_flatpak_refresh_appstream_remote (GsFlatpak *self,
+ const gchar *remote_name,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *str = NULL;
+ g_autoptr(GsApp) app_dl = gs_app_new (gs_plugin_get_name (self->plugin));
+ g_autoptr(GsFlatpakProgressHelper) phelper = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+ g_autoptr(GError) error_local = NULL;
+
+ /* TRANSLATORS: status text when downloading new metadata */
+ str = g_strdup_printf (_("Getting flatpak metadata for %s…"), remote_name);
+ gs_app_set_summary_missing (app_dl, str);
+ gs_plugin_status_update (self->plugin, app_dl, GS_PLUGIN_STATUS_DOWNLOADING);
+
+ if (!flatpak_installation_update_remote_sync (installation,
+ remote_name,
+ cancellable,
+ &error_local)) {
+ g_debug ("Failed to update metadata for remote %s: %s",
+ remote_name, error_local->message);
+ gs_flatpak_error_convert (&error_local);
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+ phelper = gs_flatpak_progress_helper_new (self->plugin, app_dl);
+ if (!flatpak_installation_update_appstream_full_sync (installation,
+ remote_name,
+ NULL, /* arch */
+ gs_flatpak_progress_cb,
+ phelper,
+ NULL, /* out_changed */
+ cancellable,
+ error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ /* success */
+ gs_app_set_progress (app_dl, 100);
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_refresh_appstream (GsFlatpak *self,
+ guint64 cache_age_secs,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ g_autoptr(GPtrArray) xremotes = NULL;
+
+ /* get remotes */
+ xremotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive),
+ cancellable,
+ error);
+ if (xremotes == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < xremotes->len; i++) {
+ const gchar *remote_name;
+ guint64 tmp;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFile) file_timestamp = NULL;
+ g_autofree gchar *appstream_fn = NULL;
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ /* not enabled */
+ if (flatpak_remote_get_disabled (xremote))
+ continue;
+
+ remote_name = flatpak_remote_get_name (xremote);
+ locker = g_mutex_locker_new (&self->broken_remotes_mutex);
+
+ /* skip known-broken repos */
+ if (g_hash_table_lookup (self->broken_remotes, remote_name) != NULL) {
+ g_debug ("skipping known broken remote: %s", remote_name);
+ continue;
+ }
+
+ g_clear_pointer (&locker, g_mutex_locker_free);
+
+ /* is the timestamp new enough */
+ file_timestamp = flatpak_remote_get_appstream_timestamp (xremote, NULL);
+ tmp = gs_utils_get_file_age (file_timestamp);
+ if (tmp < cache_age_secs) {
+ g_autofree gchar *fn = g_file_get_path (file_timestamp);
+ g_debug ("%s is only %" G_GUINT64_FORMAT " seconds old, so ignoring refresh",
+ fn, tmp);
+ continue;
+ }
+
+ /* download new data */
+ g_debug ("%s is %" G_GUINT64_FORMAT " seconds old, so downloading new data",
+ remote_name, tmp);
+ ret = gs_flatpak_refresh_appstream_remote (self,
+ remote_name,
+ interactive,
+ cancellable,
+ &error_local);
+ if (!ret) {
+ g_autoptr(GsPluginEvent) event = NULL;
+ if (g_error_matches (error_local,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED)) {
+ g_debug ("Failed to get AppStream metadata: %s",
+ error_local->message);
+
+ locker = g_mutex_locker_new (&self->broken_remotes_mutex);
+
+ /* don't try to fetch this again until refresh() */
+ g_hash_table_insert (self->broken_remotes,
+ g_strdup (remote_name),
+ GUINT_TO_POINTER (1));
+ continue;
+ }
+
+ /* allow the plugin loader to decide if this should be
+ * shown the user, possibly only for interactive jobs */
+ gs_flatpak_error_convert (&error_local);
+ event = gs_plugin_event_new ("error", error_local,
+ NULL);
+ gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+ gs_plugin_report_event (self->plugin, event);
+ continue;
+ }
+
+ /* add the new AppStream repo to the shared silo */
+ file = flatpak_remote_get_appstream_dir (xremote, NULL);
+ appstream_fn = g_file_get_path (file);
+ g_debug ("using AppStream metadata found at: %s", appstream_fn);
+ }
+
+ /* ensure the AppStream silo is up to date */
+ if (!gs_flatpak_rescan_appstream_store (self, interactive, cancellable, error)) {
+ gs_flatpak_internal_data_changed (self);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gs_flatpak_set_metadata_installed (GsFlatpak *self,
+ GsApp *app,
+ FlatpakInstalledRef *xref,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ const gchar *appdata_version;
+ guint64 mtime;
+ guint64 size_installed;
+ g_autofree gchar *metadata_fn = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFileInfo) info = NULL;
+
+ /* for all types */
+ gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref));
+ if (gs_app_get_metadata_item (app, "GnomeSoftware::Creator") == NULL) {
+ gs_app_set_metadata (app, "GnomeSoftware::Creator",
+ gs_plugin_get_name (self->plugin));
+ }
+
+ /* get the last time the app was updated */
+ metadata_fn = g_build_filename (flatpak_installed_ref_get_deploy_dir (xref),
+ "..",
+ "active",
+ "metadata",
+ NULL);
+ file = g_file_new_for_path (metadata_fn);
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, NULL);
+ if (info != NULL) {
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ gs_app_set_install_date (app, mtime);
+ }
+
+ /* If it's a runtime, check if the main-app info should be set. Note that
+ * checking the app for AS_COMPONENT_KIND_RUNTIME is not good enough because it
+ * could be e.g. AS_COMPONENT_KIND_LOCALIZATION and still be a runtime from
+ * Flatpak's perspective.
+ */
+ if (gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME &&
+ gs_flatpak_app_get_main_app_ref_name (app) == NULL) {
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GKeyFile) metadata_file = NULL;
+ metadata_file = g_key_file_new ();
+ if (g_key_file_load_from_file (metadata_file, metadata_fn,
+ G_KEY_FILE_NONE, &error)) {
+ g_autofree gchar *main_app = g_key_file_get_string (metadata_file,
+ "ExtensionOf",
+ "ref", NULL);
+ if (main_app != NULL)
+ gs_flatpak_app_set_main_app_ref_name (app, main_app);
+ } else {
+ g_warning ("Error loading the metadata file for '%s': %s",
+ gs_app_get_unique_id (app), error->message);
+ }
+ }
+
+ /* this is faster than resolving */
+ if (gs_app_get_origin (app) == NULL)
+ gs_flatpak_set_app_origin (self, app, flatpak_installed_ref_get_origin (xref), NULL, interactive, cancellable);
+
+ /* this is faster than flatpak_installation_fetch_remote_size_sync() */
+ size_installed = flatpak_installed_ref_get_installed_size (xref);
+ gs_app_set_size_installed (app, (size_installed != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, size_installed);
+
+ appdata_version = flatpak_installed_ref_get_appdata_version (xref);
+ if (appdata_version != NULL)
+ gs_app_set_version (app, appdata_version);
+}
+
+static GsApp *
+gs_flatpak_create_installed (GsFlatpak *self,
+ FlatpakInstalledRef *xref,
+ FlatpakRemote *xremote,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autoptr(GsApp) app = NULL;
+ const gchar *origin;
+
+ g_return_val_if_fail (xref != NULL, NULL);
+
+ /* create new object */
+ origin = flatpak_installed_ref_get_origin (xref);
+ app = gs_flatpak_create_app (self, origin, FLATPAK_REF (xref), xremote, interactive, cancellable);
+
+ gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+
+ gs_flatpak_set_metadata_installed (self, app, xref, interactive, cancellable);
+ return g_steal_pointer (&app);
+}
+
+gboolean
+gs_flatpak_add_installed (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) xrefs = NULL;
+
+ /* get apps and runtimes */
+ xrefs = flatpak_installation_list_installed_refs (gs_flatpak_get_installation (self, interactive),
+ cancellable, error);
+ if (xrefs == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ gs_flatpak_ensure_remote_title (self, interactive, cancellable);
+
+ for (guint i = 0; i < xrefs->len; i++) {
+ FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+ g_autoptr(GsApp) app = gs_flatpak_create_installed (self, xref, NULL, interactive, cancellable);
+ gs_app_list_add (list, app);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_add_sources (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) xrefs = NULL;
+ g_autoptr(GPtrArray) xremotes = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* refresh */
+ if (!gs_flatpak_rescan_app_data (self, interactive, cancellable, error))
+ return FALSE;
+
+ /* get installed apps and runtimes */
+ xrefs = flatpak_installation_list_installed_refs (installation,
+ cancellable,
+ error);
+ if (xrefs == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ /* get available remotes */
+ xremotes = flatpak_installation_list_remotes (installation,
+ cancellable,
+ error);
+ if (xremotes == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < xremotes->len; i++) {
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ g_autoptr(GsApp) app = NULL;
+
+ /* apps installed from bundles add their own remote that only
+ * can be used for updating that app only -- so hide them */
+ if (flatpak_remote_get_noenumerate (xremote))
+ continue;
+
+ /* create app */
+ app = gs_flatpak_create_source (self, xremote);
+ gs_app_list_add (list, app);
+
+ /* add related apps, i.e. what was installed from there */
+ for (guint j = 0; j < xrefs->len; j++) {
+ FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, j);
+ g_autoptr(GsApp) related = NULL;
+
+ /* only apps */
+ if (flatpak_ref_get_kind (FLATPAK_REF (xref)) != FLATPAK_REF_KIND_APP)
+ continue;
+ if (g_strcmp0 (flatpak_installed_ref_get_origin (xref),
+ flatpak_remote_get_name (xremote)) != 0)
+ continue;
+ related = gs_flatpak_create_installed (self, xref, xremote, interactive, cancellable);
+ gs_app_add_related (app, related);
+ }
+ }
+ return TRUE;
+}
+
+GsApp *
+gs_flatpak_find_source_by_url (GsFlatpak *self,
+ const gchar *url,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) xremotes = NULL;
+
+ g_return_val_if_fail (url != NULL, NULL);
+
+ xremotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive), cancellable, error);
+ if (xremotes == NULL)
+ return NULL;
+ for (guint i = 0; i < xremotes->len; i++) {
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ g_autofree gchar *url_tmp = flatpak_remote_get_url (xremote);
+ if (g_strcmp0 (url, url_tmp) == 0)
+ return gs_flatpak_create_source (self, xremote);
+ }
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "cannot find %s", url);
+ return NULL;
+}
+
+/* transfer full */
+GsApp *
+gs_flatpak_ref_to_app (GsFlatpak *self,
+ const gchar *ref,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) xremotes = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ g_return_val_if_fail (ref != NULL, NULL);
+
+ g_mutex_lock (&self->installed_refs_mutex);
+
+ if (self->installed_refs == NULL) {
+ self->installed_refs = flatpak_installation_list_installed_refs (installation,
+ cancellable, error);
+
+ if (self->installed_refs == NULL) {
+ g_mutex_unlock (&self->installed_refs_mutex);
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ }
+
+ for (guint i = 0; i < self->installed_refs->len; i++) {
+ g_autoptr(FlatpakInstalledRef) xref = g_object_ref (g_ptr_array_index (self->installed_refs, i));
+ g_autofree gchar *ref_tmp = flatpak_ref_format_ref (FLATPAK_REF (xref));
+ if (g_strcmp0 (ref, ref_tmp) == 0) {
+ g_mutex_unlock (&self->installed_refs_mutex);
+ return gs_flatpak_create_installed (self, xref, NULL, interactive, cancellable);
+ }
+ }
+
+ g_mutex_unlock (&self->installed_refs_mutex);
+
+ /* look at each remote xref */
+ xremotes = flatpak_installation_list_remotes (installation,
+ cancellable, error);
+ if (xremotes == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ for (guint i = 0; i < xremotes->len; i++) {
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GPtrArray) refs_remote = NULL;
+
+ /* disabled */
+ if (flatpak_remote_get_disabled (xremote))
+ continue;
+ refs_remote = flatpak_installation_list_remote_refs_sync (installation,
+ flatpak_remote_get_name (xremote),
+ cancellable,
+ &error_local);
+ if (refs_remote == NULL) {
+ g_debug ("failed to list refs in '%s': %s",
+ flatpak_remote_get_name (xremote),
+ error_local->message);
+ continue;
+ }
+ for (guint j = 0; j < refs_remote->len; j++) {
+ FlatpakRef *xref = g_ptr_array_index (refs_remote, j);
+ g_autofree gchar *ref_tmp = flatpak_ref_format_ref (xref);
+ if (g_strcmp0 (ref, ref_tmp) == 0) {
+ const gchar *origin = flatpak_remote_get_name (xremote);
+ return gs_flatpak_create_app (self, origin, xref, xremote, interactive, cancellable);
+ }
+ }
+ }
+
+ /* nothing found */
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "cannot find %s", ref);
+ return NULL;
+}
+
+/* This is essentially the inverse of gs_flatpak_app_new_from_repo_file() */
+static void
+gs_flatpak_update_remote_from_app (GsFlatpak *self,
+ FlatpakRemote *xremote,
+ GsApp *app)
+{
+ const gchar *gpg_key;
+ const gchar *branch;
+ const gchar *title, *homepage, *comment, *description;
+ const gchar *filter;
+ GPtrArray *icons;
+
+ flatpak_remote_set_disabled (xremote, FALSE);
+
+ flatpak_remote_set_url (xremote, gs_flatpak_app_get_repo_url (app));
+ flatpak_remote_set_noenumerate (xremote, FALSE);
+
+ title = gs_app_get_name (app);
+ if (title != NULL)
+ flatpak_remote_set_title (xremote, title);
+
+ /* decode GPG key if set */
+ gpg_key = gs_flatpak_app_get_repo_gpgkey (app);
+ if (gpg_key != NULL) {
+ gsize data_len = 0;
+ g_autofree guchar *data = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ data = g_base64_decode (gpg_key, &data_len);
+ bytes = g_bytes_new (data, data_len);
+ flatpak_remote_set_gpg_verify (xremote, TRUE);
+ flatpak_remote_set_gpg_key (xremote, bytes);
+ } else {
+ flatpak_remote_set_gpg_verify (xremote, FALSE);
+ }
+
+ /* default branch */
+ branch = gs_app_get_branch (app);
+ if (branch != NULL)
+ flatpak_remote_set_default_branch (xremote, branch);
+
+ /* optional data */
+ homepage = gs_app_get_url (app, AS_URL_KIND_HOMEPAGE);
+ if (homepage != NULL)
+ flatpak_remote_set_homepage (xremote, homepage);
+
+ comment = gs_app_get_summary (app);
+ if (comment != NULL)
+ flatpak_remote_set_comment (xremote, comment);
+
+ description = gs_app_get_description (app);
+ if (description != NULL)
+ flatpak_remote_set_description (xremote, description);
+
+ icons = gs_app_get_icons (app);
+ for (guint i = 0; icons != NULL && i < icons->len; i++) {
+ GIcon *icon = g_ptr_array_index (icons, i);
+
+ if (GS_IS_REMOTE_ICON (icon)) {
+ flatpak_remote_set_icon (xremote,
+ gs_remote_icon_get_uri (GS_REMOTE_ICON (icon)));
+ break;
+ }
+ }
+
+ /* With the other fields, we always want to add as much information as
+ * we can to the @xremote. With the filter, though, we want to drop it
+ * if no filter is set on the @app. Importing an updated flatpakrepo
+ * file is one of the methods for switching from (for example) filtered
+ * flathub to unfiltered flathub. So if @app doesn’t have a filter set,
+ * clear it on the @xremote (i.e. don’t check for NULL). */
+ filter = gs_flatpak_app_get_repo_filter (app);
+ flatpak_remote_set_filter (xremote, filter);
+}
+
+static FlatpakRemote *
+gs_flatpak_create_new_remote (GsFlatpak *self,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakRemote) xremote = NULL;
+
+ /* create a new remote */
+ xremote = flatpak_remote_new (gs_app_get_id (app));
+ gs_flatpak_update_remote_from_app (self, xremote, app);
+
+ return g_steal_pointer (&xremote);
+}
+
+/* @is_install is %TRUE if the repo is being installed, or %FALSE if it’s being
+ * enabled. If it’s being enabled, no properties apart from enabled/disabled
+ * should be modified. */
+gboolean
+gs_flatpak_app_install_source (GsFlatpak *self,
+ GsApp *app,
+ gboolean is_install,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakRemote) xremote = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ xremote = flatpak_installation_get_remote_by_name (installation,
+ gs_app_get_id (app),
+ cancellable, NULL);
+ if (xremote != NULL) {
+ /* if the remote already exists, just enable it and update it */
+ g_debug ("modifying existing remote %s", flatpak_remote_get_name (xremote));
+ flatpak_remote_set_disabled (xremote, FALSE);
+
+ if (is_install &&
+ gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_REPO) {
+ gs_flatpak_update_remote_from_app (self, xremote, app);
+ }
+ } else if (!is_install) {
+ g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Cannot enable flatpak remote '%s', remote not found", gs_app_get_id (app));
+ } else {
+ /* create a new remote */
+ xremote = gs_flatpak_create_new_remote (self, app, cancellable, error);
+ }
+
+ /* install it */
+ gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+ if (!flatpak_installation_modify_remote (installation,
+ xremote,
+ cancellable,
+ error)) {
+ gs_flatpak_error_convert (error);
+ g_prefix_error (error, "cannot modify remote: ");
+ gs_app_set_state_recover (app);
+ gs_flatpak_internal_data_changed (self);
+ return FALSE;
+ }
+
+ /* Mark the internal cache as obsolete. */
+ gs_flatpak_internal_data_changed (self);
+
+ /* success */
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+
+ gs_plugin_repository_changed (self->plugin, app);
+
+ return TRUE;
+}
+
+static GsApp *
+get_main_app_of_related (GsFlatpak *self,
+ GsApp *related_app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakInstalledRef) ref = NULL;
+ const gchar *ref_name;
+ g_auto(GStrv) app_tokens = NULL;
+ FlatpakRefKind ref_kind = FLATPAK_REF_KIND_RUNTIME;
+
+ ref_name = gs_flatpak_app_get_main_app_ref_name (related_app);
+ if (ref_name == NULL) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "%s doesn't have a main app set to it.",
+ gs_app_get_unique_id (related_app));
+ return NULL;
+ }
+
+ app_tokens = g_strsplit (ref_name, "/", -1);
+ if (g_strv_length (app_tokens) != 4) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ "The main app of %s has an invalid name: %s",
+ gs_app_get_unique_id (related_app), ref_name);
+ return NULL;
+ }
+
+ /* get the right ref kind for the main app */
+ if (g_strcmp0 (app_tokens[0], "app") == 0)
+ ref_kind = FLATPAK_REF_KIND_APP;
+
+ /* this function only returns G_IO_ERROR_NOT_FOUND when the metadata file
+ * is missing, but if that's the case then things should have broken before
+ * this point */
+ ref = flatpak_installation_get_installed_ref (gs_flatpak_get_installation (self, interactive),
+ ref_kind,
+ app_tokens[1],
+ app_tokens[2],
+ app_tokens[3],
+ cancellable,
+ error);
+ if (ref == NULL)
+ return NULL;
+
+ return gs_flatpak_create_installed (self, ref, NULL, interactive, cancellable);
+}
+
+static GsApp *
+get_real_app_for_update (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsApp *main_app = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ if (gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME)
+ main_app = get_main_app_of_related (self, app, interactive, cancellable, &error_local);
+
+ if (main_app == NULL) {
+ /* not all runtimes are extensions, and in that case we get the
+ * not-found error, so we only report other types of errors */
+ if (error_local != NULL &&
+ !g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+
+ main_app = g_object_ref (app);
+ } else {
+ g_debug ("Related extension app %s of main app %s is updatable, so "
+ "setting the latter's state instead.", gs_app_get_unique_id (app),
+ gs_app_get_unique_id (main_app));
+ gs_app_set_state (main_app, GS_APP_STATE_UPDATABLE_LIVE);
+ /* Make sure the 'app' is not forgotten, it'll be added into the transaction later */
+ gs_app_add_related (main_app, app);
+ }
+
+ return main_app;
+}
+
+gboolean
+gs_flatpak_add_updates (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GPtrArray) xrefs = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* ensure valid */
+ if (!gs_flatpak_rescan_app_data (self, interactive, cancellable, error))
+ return FALSE;
+
+ /* get all the updatable apps and runtimes */
+ xrefs = flatpak_installation_list_installed_refs_for_update (installation,
+ cancellable,
+ error);
+ if (xrefs == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ gs_flatpak_ensure_remote_title (self, interactive, cancellable);
+
+ /* look at each installed xref */
+ for (guint i = 0; i < xrefs->len; i++) {
+ FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+ const gchar *commit;
+ const gchar *latest_commit;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GsApp) main_app = NULL;
+
+ /* check the application has already been downloaded */
+ commit = flatpak_ref_get_commit (FLATPAK_REF (xref));
+ latest_commit = flatpak_installed_ref_get_latest_commit (xref);
+ app = gs_flatpak_create_installed (self, xref, NULL, interactive, cancellable);
+ main_app = get_real_app_for_update (self, app, interactive, cancellable, &error_local);
+ if (main_app == NULL) {
+ g_debug ("Couldn't get the main app for updatable app extension %s: "
+ "%s; adding the app itself to the updates list...",
+ gs_app_get_unique_id (app), error_local->message);
+ g_clear_error (&error_local);
+ main_app = g_object_ref (app);
+ }
+
+ /* if for some reason the app is already getting updated, then
+ * don't change its state */
+ if (gs_app_get_state (main_app) != GS_APP_STATE_INSTALLING)
+ gs_app_set_state (main_app, GS_APP_STATE_UPDATABLE_LIVE);
+
+ /* set updatable state on the extension too, as it will have
+ * its state updated to installing then installed later on */
+ if (gs_app_get_state (app) != GS_APP_STATE_INSTALLING)
+ gs_app_set_state (app, GS_APP_STATE_UPDATABLE_LIVE);
+
+ /* already downloaded */
+ if (latest_commit && g_strcmp0 (commit, latest_commit) != 0) {
+ g_debug ("%s has a downloaded update %s->%s",
+ flatpak_ref_get_name (FLATPAK_REF (xref)),
+ commit, latest_commit);
+ gs_app_set_update_details_markup (main_app, NULL);
+ gs_app_set_update_version (main_app, NULL);
+ gs_app_set_update_urgency (main_app, AS_URGENCY_KIND_UNKNOWN);
+ gs_app_set_size_download (main_app, GS_SIZE_TYPE_VALID, 0);
+
+ /* needs download */
+ } else {
+ guint64 download_size = 0;
+ g_debug ("%s needs update",
+ flatpak_ref_get_name (FLATPAK_REF (xref)));
+
+ /* get the current download size */
+ if (gs_app_get_size_download (main_app, NULL) != GS_SIZE_TYPE_VALID) {
+ if (!flatpak_installation_fetch_remote_size_sync (installation,
+ gs_app_get_origin (app),
+ FLATPAK_REF (xref),
+ &download_size,
+ NULL,
+ cancellable,
+ &error_local)) {
+ g_warning ("failed to get download size: %s",
+ error_local->message);
+ g_clear_error (&error_local);
+ gs_app_set_size_download (main_app, GS_SIZE_TYPE_UNKNOWABLE, 0);
+ } else {
+ gs_app_set_size_download (main_app, GS_SIZE_TYPE_VALID, download_size);
+ }
+ }
+ }
+ gs_flatpak_set_update_permissions (self, main_app, xref, interactive, cancellable);
+ gs_app_list_add (list, main_app);
+ }
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_refresh (GsFlatpak *self,
+ guint64 cache_age_secs,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* give all the repos a second chance */
+ g_mutex_lock (&self->broken_remotes_mutex);
+ g_hash_table_remove_all (self->broken_remotes);
+ g_mutex_unlock (&self->broken_remotes_mutex);
+
+ /* manually drop the cache in both installation instances;
+ * it's needed to have them both agree on the content. */
+ if (!flatpak_installation_drop_caches (gs_flatpak_get_installation (self, FALSE),
+ cancellable,
+ error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ if (!flatpak_installation_drop_caches (gs_flatpak_get_installation (self, TRUE),
+ cancellable,
+ error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ /* drop the installed refs cache */
+ g_mutex_lock (&self->installed_refs_mutex);
+ g_clear_pointer (&self->installed_refs, g_ptr_array_unref);
+ g_mutex_unlock (&self->installed_refs_mutex);
+
+ /* manually do this in case we created the first appstream file */
+ gs_flatpak_invalidate_silo (self);
+
+ /* update AppStream metadata */
+ if (!gs_flatpak_refresh_appstream (self, cache_age_secs, interactive, cancellable, error))
+ return FALSE;
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+gs_plugin_refine_item_origin_hostname (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakRemote) xremote = NULL;
+ g_autofree gchar *url = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* already set */
+ if (gs_app_get_origin_hostname (app) != NULL)
+ return TRUE;
+
+ /* no origin */
+ if (gs_app_get_origin (app) == NULL)
+ return TRUE;
+
+ /* get the remote */
+ xremote = flatpak_installation_get_remote_by_name (gs_flatpak_get_installation (self, interactive),
+ gs_app_get_origin (app),
+ cancellable,
+ &error_local);
+ if (xremote == NULL) {
+ if (g_error_matches (error_local,
+ FLATPAK_ERROR,
+ FLATPAK_ERROR_REMOTE_NOT_FOUND)) {
+ /* if the user deletes the -origin remote for a locally
+ * installed flatpakref file then we should just show
+ * 'localhost' and not return an error */
+ gs_app_set_origin_hostname (app, "");
+ return TRUE;
+ }
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ url = flatpak_remote_get_url (xremote);
+ if (url == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no URL for remote %s",
+ flatpak_remote_get_name (xremote));
+ return FALSE;
+ }
+ gs_app_set_origin_hostname (app, url);
+ return TRUE;
+}
+
+static gboolean
+gs_refine_item_metadata (GsFlatpak *self,
+ GsApp *app,
+ GError **error)
+{
+ g_autoptr(FlatpakRef) xref = NULL;
+
+ /* already set */
+ if (gs_flatpak_app_get_ref_name (app) != NULL)
+ return TRUE;
+
+ /* not a valid type */
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY)
+ return TRUE;
+
+ /* AppStream sets the source to appname/arch/branch, if this isn't set
+ * we can't break out the fields */
+ if (gs_app_get_source_default (app) == NULL) {
+ g_autofree gchar *tmp = gs_app_to_string (app);
+ g_warning ("no source set by appstream for %s: %s",
+ gs_plugin_get_name (self->plugin), tmp);
+ return TRUE;
+ }
+
+ /* parse the ref */
+ xref = flatpak_ref_parse (gs_app_get_source_default (app), error);
+ if (xref == NULL) {
+ gs_flatpak_error_convert (error);
+ g_prefix_error (error, "failed to parse '%s': ",
+ gs_app_get_source_default (app));
+ return FALSE;
+ }
+ gs_flatpak_set_metadata (self, app, xref);
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+gs_plugin_refine_item_origin (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree gchar *ref_display = NULL;
+ g_autoptr(GPtrArray) xremotes = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* already set */
+ if (gs_app_get_origin (app) != NULL)
+ return TRUE;
+
+ /* not applicable */
+ if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE_LOCAL)
+ return TRUE;
+
+ /* ensure metadata exists */
+ if (!gs_refine_item_metadata (self, app, error))
+ return FALSE;
+
+ /* find list of remotes */
+ ref_display = gs_flatpak_app_get_ref_display (app);
+ g_debug ("looking for a remote for %s", ref_display);
+ xremotes = flatpak_installation_list_remotes (installation,
+ cancellable, error);
+ if (xremotes == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < xremotes->len; i++) {
+ const gchar *remote_name;
+ FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+ g_autoptr(FlatpakRemoteRef) xref = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* not enabled */
+ if (flatpak_remote_get_disabled (xremote))
+ continue;
+
+ /* sync */
+ remote_name = flatpak_remote_get_name (xremote);
+ g_debug ("looking at remote %s", remote_name);
+ xref = flatpak_installation_fetch_remote_ref_sync (installation,
+ remote_name,
+ gs_flatpak_app_get_ref_kind (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ cancellable,
+ &error_local);
+ if (xref != NULL) {
+ g_debug ("found remote %s", remote_name);
+ gs_flatpak_set_app_origin (self, app, remote_name, xremote, interactive, cancellable);
+ gs_flatpak_app_set_commit (app, flatpak_ref_get_commit (FLATPAK_REF (xref)));
+ gs_plugin_refine_item_scope (self, app);
+ return TRUE;
+ }
+ g_debug ("%s failed to find remote %s: %s",
+ ref_display, remote_name, error_local->message);
+ }
+
+ /* not found */
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "%s not found in any remote",
+ ref_display);
+ return FALSE;
+}
+
+static FlatpakRef *
+gs_flatpak_create_fake_ref (GsApp *app, GError **error)
+{
+ FlatpakRef *xref;
+ g_autofree gchar *id = NULL;
+ id = g_strdup_printf ("%s/%s/%s/%s",
+ gs_flatpak_app_get_ref_kind_as_str (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app));
+ xref = flatpak_ref_parse (id, error);
+ if (xref == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ return xref;
+}
+
+/* the _unlocked() version doesn't call gs_flatpak_rescan_app_data,
+ * in order to avoid taking the writer lock on self->silo_lock */
+static gboolean
+gs_flatpak_refine_app_state_unlocked (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakInstalledRef) ref = NULL;
+ g_autoptr(GPtrArray) installed_refs = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* already found */
+ if (gs_app_get_state (app) != GS_APP_STATE_UNKNOWN)
+ return TRUE;
+
+ /* need broken out metadata */
+ if (!gs_refine_item_metadata (self, app, error))
+ return FALSE;
+
+ /* ensure origin set */
+ if (!gs_plugin_refine_item_origin (self, app, interactive, cancellable, error))
+ return FALSE;
+
+ /* find the app using the origin and the ID */
+ g_mutex_lock (&self->installed_refs_mutex);
+
+ if (self->installed_refs == NULL) {
+ self->installed_refs = flatpak_installation_list_installed_refs (installation,
+ cancellable, error);
+
+ if (self->installed_refs == NULL) {
+ g_mutex_unlock (&self->installed_refs_mutex);
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ }
+
+ installed_refs = g_ptr_array_ref (self->installed_refs);
+
+ for (guint i = 0; i < installed_refs->len; i++) {
+ FlatpakInstalledRef *ref_tmp = g_ptr_array_index (installed_refs, i);
+ const gchar *origin = flatpak_installed_ref_get_origin (ref_tmp);
+ const gchar *name = flatpak_ref_get_name (FLATPAK_REF (ref_tmp));
+ const gchar *arch = flatpak_ref_get_arch (FLATPAK_REF (ref_tmp));
+ const gchar *branch = flatpak_ref_get_branch (FLATPAK_REF (ref_tmp));
+ if (g_strcmp0 (origin, gs_app_get_origin (app)) == 0 &&
+ g_strcmp0 (name, gs_flatpak_app_get_ref_name (app)) == 0 &&
+ g_strcmp0 (arch, gs_flatpak_app_get_ref_arch (app)) == 0 &&
+ g_strcmp0 (branch, gs_app_get_branch (app)) == 0) {
+ ref = g_object_ref (ref_tmp);
+ break;
+ }
+ }
+ g_mutex_unlock (&self->installed_refs_mutex);
+ if (ref != NULL) {
+ g_debug ("marking %s as installed with flatpak",
+ gs_app_get_unique_id (app));
+ gs_flatpak_set_metadata_installed (self, app, ref, interactive, cancellable);
+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+ gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+
+ /* flatpak only allows one installed app to be launchable */
+ if (flatpak_installed_ref_get_is_current (ref)) {
+ gs_app_remove_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+ } else {
+ g_debug ("%s is not current, and therefore not launchable",
+ gs_app_get_unique_id (app));
+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+ }
+ return TRUE;
+ }
+
+ /* anything not installed just check the remote is still present */
+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN &&
+ gs_app_get_origin (app) != NULL) {
+ g_autoptr(FlatpakRemote) xremote = NULL;
+ xremote = flatpak_installation_get_remote_by_name (installation,
+ gs_app_get_origin (app),
+ cancellable, NULL);
+ if (xremote != NULL) {
+ if (flatpak_remote_get_disabled (xremote)) {
+ g_debug ("%s is available with flatpak "
+ "but %s is disabled",
+ gs_app_get_unique_id (app),
+ flatpak_remote_get_name (xremote));
+ gs_app_set_state (app, GS_APP_STATE_UNAVAILABLE);
+ } else {
+ g_debug ("marking %s as available with flatpak",
+ gs_app_get_unique_id (app));
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+ }
+ } else {
+ gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
+ g_debug ("failed to find %s remote %s for %s",
+ self->id,
+ gs_app_get_origin (app),
+ gs_app_get_unique_id (app));
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_refine_app_state (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* ensure valid */
+ if (!gs_flatpak_rescan_app_data (self, interactive, cancellable, error))
+ return FALSE;
+
+ return gs_flatpak_refine_app_state_unlocked (self, app, interactive, cancellable, error);
+}
+
+static GsApp *
+gs_flatpak_create_runtime (GsFlatpak *self,
+ GsApp *parent,
+ const gchar *runtime,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autofree gchar *source = NULL;
+ g_auto(GStrv) split = NULL;
+ g_autoptr(GsApp) app_cache = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GError) local_error = NULL;
+ const gchar *origin;
+
+ /* get the name/arch/branch */
+ split = g_strsplit (runtime, "/", -1);
+ if (g_strv_length (split) != 3)
+ return NULL;
+
+ /* create the complete GsApp from the single string */
+ app = gs_app_new (split[0]);
+ gs_flatpak_claim_app (self, app);
+ source = g_strdup_printf ("runtime/%s", runtime);
+ gs_app_add_source (app, source);
+ gs_app_set_kind (app, AS_COMPONENT_KIND_RUNTIME);
+ gs_app_set_branch (app, split[2]);
+
+ origin = gs_app_get_origin (parent);
+ if (origin != NULL) {
+ g_autoptr(FlatpakRemoteRef) xref = NULL;
+
+ xref = flatpak_installation_fetch_remote_ref_sync (gs_flatpak_get_installation (self, interactive),
+ origin,
+ FLATPAK_REF_KIND_RUNTIME,
+ gs_app_get_id (app),
+ gs_flatpak_app_get_ref_arch (parent),
+ gs_app_get_branch (app),
+ cancellable,
+ NULL);
+
+ /* Prefer runtime from the same origin as the parent application */
+ if (xref)
+ gs_app_set_origin (app, origin);
+ }
+
+ /* search in the cache */
+ app_cache = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
+ if (app_cache != NULL &&
+ g_strcmp0 (gs_flatpak_app_get_ref_name (app_cache), split[0]) == 0 &&
+ g_strcmp0 (gs_flatpak_app_get_ref_arch (app_cache), split[1]) == 0 &&
+ g_strcmp0 (gs_app_get_branch (app_cache), split[2]) == 0) {
+ /* since the cached runtime can have been created somewhere else
+ * (we're using a global cache), we need to make sure that a
+ * source is set */
+ if (gs_app_get_source_default (app_cache) == NULL)
+ gs_app_add_source (app_cache, source);
+ return g_steal_pointer (&app_cache);
+ } else {
+ g_clear_object (&app_cache);
+ }
+
+ /* if the app is per-user we can also use the installed system runtime */
+ if (gs_app_get_scope (parent) == AS_COMPONENT_SCOPE_USER) {
+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_UNKNOWN);
+ app_cache = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
+ if (app_cache != NULL &&
+ g_strcmp0 (gs_flatpak_app_get_ref_name (app_cache), split[0]) == 0 &&
+ g_strcmp0 (gs_flatpak_app_get_ref_arch (app_cache), split[1]) == 0 &&
+ g_strcmp0 (gs_app_get_branch (app_cache), split[2]) == 0) {
+ return g_steal_pointer (&app_cache);
+ } else {
+ g_clear_object (&app_cache);
+ }
+ }
+
+ /* set superclassed app properties */
+ gs_flatpak_app_set_ref_kind (app, FLATPAK_REF_KIND_RUNTIME);
+ gs_flatpak_app_set_ref_name (app, split[0]);
+ gs_flatpak_app_set_ref_arch (app, split[1]);
+
+ if (!gs_flatpak_refine_app_state_unlocked (self, app, interactive, NULL, &local_error))
+ g_debug ("Failed to refine state for runtime '%s': %s", gs_app_get_unique_id (app), local_error->message);
+
+ /* save in the cache */
+ gs_plugin_cache_add (self->plugin, NULL, app);
+ return g_steal_pointer (&app);
+}
+
+static gboolean
+gs_flatpak_set_app_metadata (GsFlatpak *self,
+ GsApp *app,
+ const gchar *data,
+ gsize length,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean secure = TRUE;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *runtime = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+ g_autoptr(GsApp) app_runtime = NULL;
+ g_autoptr(GsAppPermissions) permissions = NULL;
+ g_auto(GStrv) shared = NULL;
+ g_auto(GStrv) sockets = NULL;
+ g_auto(GStrv) filesystems = NULL;
+
+ kf = g_key_file_new ();
+ if (!g_key_file_load_from_data (kf, data, length, G_KEY_FILE_NONE, error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ name = g_key_file_get_string (kf, "Application", "name", error);
+ if (name == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ gs_flatpak_app_set_ref_name (app, name);
+ runtime = g_key_file_get_string (kf, "Application", "runtime", error);
+ if (runtime == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ shared = g_key_file_get_string_list (kf, "Context", "shared", NULL, NULL);
+ if (shared != NULL) {
+ /* SHM isn't secure enough */
+ if (g_strv_contains ((const gchar * const *) shared, "ipc"))
+ secure = FALSE;
+ }
+ sockets = g_key_file_get_string_list (kf, "Context", "sockets", NULL, NULL);
+ if (sockets != NULL) {
+ /* X11 isn't secure enough */
+ if (g_strv_contains ((const gchar * const *) sockets, "x11"))
+ secure = FALSE;
+ }
+ filesystems = g_key_file_get_string_list (kf, "Context", "filesystems", NULL, NULL);
+ if (filesystems != NULL) {
+ /* secure apps should be using portals */
+ if (g_strv_contains ((const gchar * const *) filesystems, "home"))
+ secure = FALSE;
+ }
+
+ permissions = perms_from_metadata (kf);
+ gs_app_set_permissions (app, permissions);
+ /* this is actually quite hard to achieve */
+ if (secure)
+ gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED_SECURE);
+
+ /* create runtime */
+ app_runtime = gs_flatpak_create_runtime (self, app, runtime, interactive, cancellable);
+ if (app_runtime != NULL) {
+ gs_plugin_refine_item_scope (self, app_runtime);
+ gs_app_set_runtime (app, app_runtime);
+ }
+
+ /* we always get this, but it's a low bar... */
+ gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED);
+
+ return TRUE;
+}
+
+static GBytes *
+gs_flatpak_fetch_remote_metadata (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GBytes) data = NULL;
+ g_autoptr(FlatpakRef) xref = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ /* no origin */
+ if (gs_app_get_origin (app) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "no origin set when getting metadata for %s",
+ gs_app_get_unique_id (app));
+ return NULL;
+ }
+
+ /* fetch from the server */
+ xref = gs_flatpak_create_fake_ref (app, error);
+ if (xref == NULL)
+ return NULL;
+ data = flatpak_installation_fetch_remote_metadata_sync (gs_flatpak_get_installation (self, interactive),
+ gs_app_get_origin (app),
+ xref,
+ cancellable,
+ &local_error);
+ if (data == NULL) {
+ if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_REF_NOT_FOUND) &&
+ !gs_plugin_get_network_available (self->plugin)) {
+ local_error->code = GS_PLUGIN_ERROR_NO_NETWORK;
+ local_error->domain = GS_PLUGIN_ERROR;
+ } else {
+ gs_flatpak_error_convert (&local_error);
+ }
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return NULL;
+ }
+ return g_steal_pointer (&data);
+}
+
+static gboolean
+gs_plugin_refine_item_metadata (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *str;
+ gsize len = 0;
+ g_autofree gchar *contents = NULL;
+ g_autofree gchar *installation_path_str = NULL;
+ g_autofree gchar *install_path = NULL;
+ g_autoptr(GBytes) data = NULL;
+ g_autoptr(GFile) installation_path = NULL;
+
+ /* not applicable */
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY)
+ return TRUE;
+ if (gs_flatpak_app_get_ref_kind (app) != FLATPAK_REF_KIND_APP)
+ return TRUE;
+
+ /* already done */
+ if (gs_app_has_kudo (app, GS_APP_KUDO_SANDBOXED))
+ return TRUE;
+
+ /* this is quicker than doing network IO */
+ installation_path = flatpak_installation_get_path (self->installation_noninteractive);
+ installation_path_str = g_file_get_path (installation_path);
+ install_path = g_build_filename (installation_path_str,
+ gs_flatpak_app_get_ref_kind_as_str (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ "active",
+ "metadata",
+ NULL);
+ if (g_file_test (install_path, G_FILE_TEST_EXISTS)) {
+ if (!g_file_get_contents (install_path, &contents, &len, error))
+ return FALSE;
+ str = contents;
+ } else {
+ data = gs_flatpak_fetch_remote_metadata (self, app, interactive,
+ cancellable,
+ error);
+ if (data == NULL)
+ return FALSE;
+ str = g_bytes_get_data (data, &len);
+ }
+
+ /* parse key file */
+ if (!gs_flatpak_set_app_metadata (self, app, str, len, interactive, cancellable, error))
+ return FALSE;
+ return TRUE;
+}
+
+static FlatpakInstalledRef *
+gs_flatpak_get_installed_ref (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ FlatpakInstalledRef *ref;
+ ref = flatpak_installation_get_installed_ref (gs_flatpak_get_installation (self, interactive),
+ gs_flatpak_app_get_ref_kind (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ cancellable,
+ error);
+ if (ref == NULL)
+ gs_flatpak_error_convert (error);
+ return ref;
+}
+
+static gboolean
+gs_flatpak_prune_addons_list (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) addons_list = NULL;
+ g_autoptr(GPtrArray) installed_related_refs = NULL;
+ g_autoptr(GPtrArray) remote_related_refs = NULL;
+ g_autofree gchar *ref = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+ g_autoptr(GError) error_local = NULL;
+
+ addons_list = gs_app_dup_addons (app);
+ if (addons_list == NULL || gs_app_list_length (addons_list) == 0)
+ return TRUE;
+
+ if (gs_app_get_origin (app) == NULL)
+ return TRUE;
+
+ /* return early if the addons haven't been refined */
+ for (guint i = 0; i < gs_app_list_length (addons_list); i++) {
+ GsApp *app_addon = gs_app_list_index (addons_list, i);
+
+ if (gs_flatpak_app_get_ref_name (app_addon) == NULL ||
+ gs_flatpak_app_get_ref_arch (app_addon) == NULL ||
+ gs_app_get_branch (app_addon) == NULL)
+ return TRUE;
+ }
+
+ /* return early if the API we need isn't available */
+#if !FLATPAK_CHECK_VERSION(1,11,1)
+ if (gs_app_get_state (app) == GS_APP_STATE_INSTALLED)
+ return TRUE;
+#endif
+
+ ref = g_strdup_printf ("%s/%s/%s/%s",
+ gs_flatpak_app_get_ref_kind_as_str (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app));
+
+ /* Find installed related refs in case the app is installed */
+ installed_related_refs = flatpak_installation_list_installed_related_refs_sync (installation,
+ gs_app_get_origin (app),
+ ref,
+ cancellable,
+ &error_local);
+ if (installed_related_refs == NULL &&
+ !g_error_matches (error_local,
+ FLATPAK_ERROR,
+ FLATPAK_ERROR_NOT_INSTALLED)) {
+ gs_flatpak_error_convert (&error_local);
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+
+ g_clear_error (&error_local);
+
+#if FLATPAK_CHECK_VERSION(1,11,1)
+ /* Find remote related refs that match the installed version in case the app is installed */
+ remote_related_refs = flatpak_installation_list_remote_related_refs_for_installed_sync (installation,
+ gs_app_get_origin (app),
+ ref,
+ cancellable,
+ &error_local);
+ if (remote_related_refs == NULL &&
+ !g_error_matches (error_local,
+ FLATPAK_ERROR,
+ FLATPAK_ERROR_NOT_INSTALLED)) {
+ gs_flatpak_error_convert (&error_local);
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+
+ g_clear_error (&error_local);
+#endif
+
+ /* Find remote related refs in case the app is not installed */
+ if (remote_related_refs == NULL) {
+ remote_related_refs = flatpak_installation_list_remote_related_refs_sync (installation,
+ gs_app_get_origin (app),
+ ref,
+ cancellable,
+ &error_local);
+ /* don't make the error fatal in case we're offline */
+ if (error_local != NULL)
+ g_debug ("failed to list remote related refs of %s: %s",
+ gs_app_get_unique_id (app), error_local->message);
+ }
+
+ g_clear_error (&error_local);
+
+ /* For each addon, if it is neither installed nor available, hide it
+ * since it may be intended for a different version of the app. We
+ * don't want to show both org.videolan.VLC.Plugin.bdj//3-19.08 and
+ * org.videolan.VLC.Plugin.bdj//3-20.08 in the UI; only one will work
+ * for the installed app
+ */
+ for (guint i = 0; i < gs_app_list_length (addons_list); i++) {
+ GsApp *app_addon = gs_app_list_index (addons_list, i);
+ gboolean found = FALSE;
+ g_autofree char *addon_ref = NULL;
+
+ addon_ref = g_strdup_printf ("%s/%s/%s/%s",
+ gs_flatpak_app_get_ref_kind_as_str (app_addon),
+ gs_flatpak_app_get_ref_name (app_addon),
+ gs_flatpak_app_get_ref_arch (app_addon),
+ gs_app_get_branch (app_addon));
+ for (guint j = 0; installed_related_refs && j < installed_related_refs->len; j++) {
+ FlatpakRelatedRef *rel = g_ptr_array_index (installed_related_refs, j);
+ g_autofree char *rel_ref = flatpak_ref_format_ref (FLATPAK_REF (rel));
+ if (g_strcmp0 (addon_ref, rel_ref) == 0)
+ found = TRUE;
+ }
+ for (guint j = 0; remote_related_refs && j < remote_related_refs->len; j++) {
+ FlatpakRelatedRef *rel = g_ptr_array_index (remote_related_refs, j);
+ g_autofree char *rel_ref = flatpak_ref_format_ref (FLATPAK_REF (rel));
+ if (g_strcmp0 (addon_ref, rel_ref) == 0)
+ found = TRUE;
+ }
+
+ if (!found) {
+ gs_app_add_quirk (app_addon, GS_APP_QUIRK_HIDE_EVERYWHERE);
+ g_debug ("hiding %s since it's not related to %s",
+ addon_ref, gs_app_get_unique_id (app));
+ } else {
+ gs_app_remove_quirk (app_addon, GS_APP_QUIRK_HIDE_EVERYWHERE);
+ g_debug ("unhiding %s since it's related to %s",
+ addon_ref, gs_app_get_unique_id (app));
+ }
+ }
+ return TRUE;
+}
+
+static guint64
+gs_flatpak_get_app_directory_size (GsApp *app,
+ const gchar *subdir_name,
+ GCancellable *cancellable)
+{
+ g_autofree gchar *filename = NULL;
+ filename = g_build_filename (g_get_home_dir (), ".var", "app", gs_app_get_id (app), subdir_name, NULL);
+ return gs_utils_get_file_size (filename, NULL, NULL, cancellable);
+}
+
+static gboolean
+gs_plugin_refine_item_size (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+ guint64 download_size = 0;
+ guint64 installed_size = 0;
+ GsSizeType size_type = GS_SIZE_TYPE_UNKNOWABLE;
+
+ /* not applicable */
+ if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE_LOCAL)
+ return TRUE;
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY)
+ return TRUE;
+
+ /* already set */
+ if (gs_app_is_installed (app)) {
+ /* only care about the installed size if the app is installed */
+ if (gs_app_get_size_installed (app, NULL) == GS_SIZE_TYPE_VALID)
+ return TRUE;
+ } else {
+ if (gs_app_get_size_installed (app, NULL) == GS_SIZE_TYPE_VALID &&
+ gs_app_get_size_download (app, NULL) == GS_SIZE_TYPE_VALID)
+ return TRUE;
+ }
+
+ /* need runtime */
+ if (!gs_plugin_refine_item_metadata (self, app, interactive, cancellable, error))
+ return FALSE;
+
+ /* calculate the platform size too if the app is not installed */
+ if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE &&
+ gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_APP) {
+ GsApp *app_runtime;
+
+ /* is the app_runtime already installed? */
+ app_runtime = gs_app_get_runtime (app);
+ if (!gs_flatpak_refine_app_state_unlocked (self,
+ app_runtime,
+ interactive,
+ cancellable,
+ error))
+ return FALSE;
+ if (gs_app_get_state (app_runtime) == GS_APP_STATE_INSTALLED) {
+ g_debug ("runtime %s is already installed, so not adding size",
+ gs_app_get_unique_id (app_runtime));
+ } else {
+ if (!gs_plugin_refine_item_size (self,
+ app_runtime,
+ interactive,
+ cancellable,
+ error))
+ return FALSE;
+ }
+ }
+
+ /* just get the size of the app */
+ if (!gs_plugin_refine_item_origin (self, app, interactive,
+ cancellable, error))
+ return FALSE;
+
+ /* if the app is installed we use the ref to fetch the installed size
+ * and ignore the download size as this is faster */
+ if (gs_app_is_installed (app)) {
+ g_autoptr(FlatpakInstalledRef) xref = NULL;
+ xref = gs_flatpak_get_installed_ref (self, app, interactive, cancellable, error);
+ if (xref == NULL)
+ return FALSE;
+ installed_size = flatpak_installed_ref_get_installed_size (xref);
+ size_type = (installed_size > 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWABLE;
+ } else {
+ g_autoptr(FlatpakRef) xref = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* no origin */
+ if (gs_app_get_origin (app) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "no origin set for %s",
+ gs_app_get_unique_id (app));
+ return FALSE;
+ }
+ xref = gs_flatpak_create_fake_ref (app, error);
+ if (xref == NULL)
+ return FALSE;
+ ret = flatpak_installation_fetch_remote_size_sync (gs_flatpak_get_installation (self, interactive),
+ gs_app_get_origin (app),
+ xref,
+ &download_size,
+ &installed_size,
+ cancellable,
+ &error_local);
+
+ if (!ret) {
+ /* This can happen when the remote is filtered */
+ g_debug ("libflatpak failed to return application size: %s", error_local->message);
+ g_clear_error (&error_local);
+ } else {
+ size_type = GS_SIZE_TYPE_VALID;
+ }
+ }
+
+ gs_app_set_size_installed (app, size_type, installed_size);
+ gs_app_set_size_download (app, size_type, download_size);
+
+ return TRUE;
+}
+
+static void
+gs_flatpak_refine_appstream_release (XbNode *component, GsApp *app)
+{
+ const gchar *version;
+
+ /* get first release */
+ version = xb_node_query_attr (component, "releases/release", "version", NULL);
+ if (version == NULL)
+ return;
+ switch (gs_app_get_state (app)) {
+ case GS_APP_STATE_INSTALLED:
+ case GS_APP_STATE_AVAILABLE:
+ case GS_APP_STATE_AVAILABLE_LOCAL:
+ gs_app_set_version (app, version);
+ break;
+ case GS_APP_STATE_UPDATABLE:
+ case GS_APP_STATE_UPDATABLE_LIVE:
+ gs_app_set_update_version (app, version);
+ break;
+ default:
+ g_debug ("%s is not installed, so ignoring version of %s",
+ gs_app_get_unique_id (app), version);
+ break;
+ }
+}
+
+/* This function is like gs_flatpak_refine_appstream(), but takes gzip
+ * compressed appstream data as a GBytes and assumes they are already uniquely
+ * tied to the app (and therefore app ID alone can be used to find the right
+ * component).
+ */
+static gboolean
+gs_flatpak_refine_appstream_from_bytes (GsFlatpak *self,
+ GsApp *app,
+ const char *origin, /* (nullable) */
+ FlatpakInstalledRef *installed_ref, /* (nullable) */
+ GBytes *appstream_gz,
+ GsPluginRefineFlags flags,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *const *locales = g_get_language_names ();
+ g_autofree gchar *xpath = NULL;
+ g_autoptr(XbBuilder) builder = NULL;
+ g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+ g_autoptr(XbNode) component_node = NULL;
+ g_autoptr(XbNode) n = NULL;
+ g_autoptr(XbSilo) silo = NULL;
+ g_autoptr(XbBuilderFixup) bundle_fixup = NULL;
+ g_autoptr(GBytes) appstream = NULL;
+ g_autoptr(GInputStream) stream_data = NULL;
+ g_autoptr(GInputStream) stream_gz = NULL;
+ g_autoptr(GZlibDecompressor) decompressor = NULL;
+ g_autoptr(GMainContext) old_thread_default = NULL;
+
+ /* FIXME: https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1422 */
+ old_thread_default = g_main_context_ref_thread_default ();
+ if (old_thread_default == g_main_context_default ())
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+ if (old_thread_default != NULL)
+ g_main_context_pop_thread_default (old_thread_default);
+ builder = xb_builder_new ();
+ if (old_thread_default != NULL)
+ g_main_context_push_thread_default (old_thread_default);
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+
+ /* add current locales */
+ for (guint i = 0; locales[i] != NULL; i++)
+ xb_builder_add_locale (builder, locales[i]);
+
+ /* decompress data */
+ decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+ stream_gz = g_memory_input_stream_new_from_bytes (appstream_gz);
+ if (stream_gz == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "unable to decompress appstream data");
+ return FALSE;
+ }
+ stream_data = g_converter_input_stream_new (stream_gz,
+ G_CONVERTER (decompressor));
+
+ appstream = g_input_stream_read_bytes (stream_data,
+ 0x100000, /* 1Mb */
+ cancellable,
+ error);
+ if (appstream == NULL) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+
+ /* build silo */
+ if (!xb_builder_source_load_bytes (source, appstream,
+ XB_BUILDER_SOURCE_FLAG_NONE,
+ error))
+ return FALSE;
+
+ /* Appdata from flatpak_installed_ref_load_appdata() may be missing the
+ * <bundle> tag but for this function we know it's the right component.
+ */
+ bundle_fixup = xb_builder_fixup_new ("AddBundle",
+ gs_flatpak_add_bundle_tag_cb,
+ gs_flatpak_app_get_ref_display (app), g_free);
+ xb_builder_fixup_set_max_depth (bundle_fixup, 2);
+ xb_builder_source_add_fixup (source, bundle_fixup);
+
+ fixup_flatpak_appstream_xml (source, origin);
+
+ /* add metadata */
+ if (installed_ref != NULL) {
+ g_autoptr(XbBuilderNode) info = NULL;
+ g_autofree char *icon_prefix = NULL;
+
+ info = xb_builder_node_insert (NULL, "info", NULL);
+ xb_builder_node_insert_text (info, "scope", as_component_scope_to_string (self->scope), NULL);
+ icon_prefix = g_build_filename (flatpak_installed_ref_get_deploy_dir (installed_ref),
+ "files", "share", "app-info", "icons", "flatpak", NULL);
+ xb_builder_node_insert_text (info, "icon-prefix", icon_prefix, NULL);
+ xb_builder_source_set_info (source, info);
+ }
+
+ xb_builder_import_source (builder, source);
+
+ /* FIXME: https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1422 */
+ old_thread_default = g_main_context_ref_thread_default ();
+ if (old_thread_default == g_main_context_default ())
+ g_clear_pointer (&old_thread_default, g_main_context_unref);
+ if (old_thread_default != NULL)
+ g_main_context_pop_thread_default (old_thread_default);
+
+ silo = xb_builder_compile (builder,
+ XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+ cancellable,
+ error);
+
+ if (old_thread_default != NULL)
+ g_main_context_push_thread_default (old_thread_default);
+
+ if (silo == NULL)
+ return FALSE;
+ if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+ g_autofree gchar *xml = NULL;
+ xml = xb_silo_export (silo,
+ XB_NODE_EXPORT_FLAG_FORMAT_INDENT |
+ XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE,
+ NULL);
+ g_debug ("showing AppStream data: %s", xml);
+ }
+
+ /* check for sanity */
+ n = xb_silo_query_first (silo, "components/component", NULL);
+ if (n == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "no apps found in AppStream data");
+ return FALSE;
+ }
+
+ /* find app */
+ xpath = g_strdup_printf ("components/component/id[text()='%s']/..",
+ gs_flatpak_app_get_ref_name (app));
+ component_node = xb_silo_query_first (silo, xpath, NULL);
+ if (component_node == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "application %s not found",
+ gs_flatpak_app_get_ref_name (app));
+ return FALSE;
+ }
+
+ /* copy details from AppStream to app */
+ if (!gs_appstream_refine_app (self->plugin, app, silo, component_node, flags, error))
+ return FALSE;
+
+ if (gs_app_get_origin (app))
+ gs_flatpak_set_app_origin (self, app, gs_app_get_origin (app), NULL, interactive, cancellable);
+
+ /* use the default release as the version number */
+ gs_flatpak_refine_appstream_release (component_node, app);
+
+ /* save the silo so it can be used for searches */
+ {
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->app_silos_mutex);
+ g_hash_table_replace (self->app_silos,
+ gs_flatpak_app_get_ref_display (app),
+ g_steal_pointer (&silo));
+ }
+
+ return TRUE;
+}
+
+static XbNode *
+get_renamed_component (GsFlatpak *self,
+ GsApp *app,
+ XbSilo *silo,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *origin = gs_app_get_origin (app);
+ const gchar *renamed_to;
+#if LIBXMLB_CHECK_VERSION(0, 3, 0)
+ g_autoptr(XbQuery) query = NULL;
+ g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT ();
+#else
+ g_autofree gchar *xpath = NULL;
+ g_autofree gchar *source_safe = NULL;
+#endif
+ g_autoptr(FlatpakRemoteRef) remote_ref = NULL;
+ g_autoptr(XbNode) component = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ remote_ref = flatpak_installation_fetch_remote_ref_sync (installation,
+ origin,
+ gs_flatpak_app_get_ref_kind (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ cancellable, error);
+ if (remote_ref == NULL)
+ return NULL;
+
+ renamed_to = flatpak_remote_ref_get_eol_rebase (remote_ref);
+ if (renamed_to == NULL)
+ return NULL;
+
+#if LIBXMLB_CHECK_VERSION(0, 3, 0)
+ query = xb_silo_lookup_query (silo, "components[@origin=?]/component/bundle[@type='flatpak'][text()=?]/..");
+ xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 0, origin, NULL);
+ xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 1, renamed_to, NULL);
+ component = xb_silo_query_first_with_context (silo, query, &context, NULL);
+#else
+ source_safe = xb_string_escape (renamed_to);
+ xpath = g_strdup_printf ("components[@origin='%s']/component/bundle[@type='flatpak'][text()='%s']/..",
+ origin, source_safe);
+ component = xb_silo_query_first (silo, xpath, NULL);
+#endif
+
+ /* Get the previous name so it can be displayed in the UI */
+ if (component != NULL) {
+ g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
+ const gchar *installed_name = NULL;
+
+ installed_ref = flatpak_installation_get_installed_ref (installation,
+ gs_flatpak_app_get_ref_kind (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ cancellable, error);
+ if (installed_ref != NULL)
+ installed_name = flatpak_installed_ref_get_appdata_name (installed_ref);
+ if (installed_name != NULL)
+ gs_app_set_renamed_from (app, installed_name);
+ }
+
+ return g_steal_pointer (&component);
+}
+
+/* Returns %TRUE if @error exists and is set to G_IO_ERROR_CANCELLED */
+static inline gboolean
+propagate_cancelled_error (GError **dest,
+ GError **error)
+{
+ g_assert (error != NULL);
+
+ if (*error && g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_propagate_error (dest, g_steal_pointer (error));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gs_flatpak_refine_appstream (GsFlatpak *self,
+ GsApp *app,
+ XbSilo *silo,
+ GsPluginRefineFlags flags,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gchar *origin = gs_app_get_origin (app);
+ const gchar *source = gs_app_get_source_default (app);
+ g_autofree gchar *source_safe = NULL;
+ g_autofree gchar *xpath = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(XbNode) component = NULL;
+
+ if (origin == NULL || source == NULL)
+ return TRUE;
+
+ /* find using source and origin */
+ source_safe = xb_string_escape (source);
+ xpath = g_strdup_printf ("components[@origin='%s']/component/bundle[@type='flatpak'][text()='%s']/..",
+ origin, source_safe);
+ component = xb_silo_query_first (silo, xpath, &error_local);
+
+ if (propagate_cancelled_error (error, &error_local))
+ return FALSE;
+
+ /* Ensure the gs_flatpak_app_get_ref_*() metadata are set */
+ gs_refine_item_metadata (self, app, NULL);
+
+ /* If the app was renamed, use the appstream data from the new name;
+ * usually it will not exist under the old name */
+ if (component == NULL && gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_APP) {
+ g_autoptr(GError) renamed_component_error = NULL;
+
+ component = get_renamed_component (self, app, silo,
+ interactive,
+ cancellable,
+ &renamed_component_error);
+
+ if (propagate_cancelled_error (error, &renamed_component_error))
+ return FALSE;
+ }
+
+ if (component == NULL) {
+ g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
+ g_autoptr(GBytes) appstream_gz = NULL;
+
+ g_debug ("no match for %s: %s", xpath, error_local->message);
+
+ g_clear_error (&error_local);
+
+ /* For apps installed from .flatpak bundles there may not be any remote
+ * appstream data in @silo for it, so use the appstream data from
+ * within the app.
+ */
+ installed_ref = flatpak_installation_get_installed_ref (gs_flatpak_get_installation (self, interactive),
+ gs_flatpak_app_get_ref_kind (app),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ cancellable,
+ &error_local);
+
+ if (installed_ref == NULL)
+ return !propagate_cancelled_error (error, &error_local); /* the app may not be installed */
+
+ appstream_gz = flatpak_installed_ref_load_appdata (installed_ref,
+ cancellable,
+ &error_local);
+ if (appstream_gz == NULL)
+ return !propagate_cancelled_error (error, &error_local);
+
+ g_debug ("using installed appdata for %s", gs_flatpak_app_get_ref_name (app));
+ return gs_flatpak_refine_appstream_from_bytes (self,
+ app,
+ flatpak_installed_ref_get_origin (installed_ref),
+ installed_ref,
+ appstream_gz,
+ flags,
+ interactive,
+ cancellable, error);
+ }
+
+ if (!gs_appstream_refine_app (self->plugin, app, silo, component, flags, error))
+ return FALSE;
+
+ /* use the default release as the version number */
+ gs_flatpak_refine_appstream_release (component, app);
+ return TRUE;
+}
+
+static gboolean
+gs_flatpak_refine_app_unlocked (GsFlatpak *self,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ gboolean interactive,
+ GRWLockReaderLocker **locker,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsAppState old_state = gs_app_get_state (app);
+ g_autoptr(GError) local_error = NULL;
+
+ /* not us */
+ if (gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_FLATPAK)
+ return TRUE;
+
+ g_clear_pointer (locker, g_rw_lock_reader_locker_free);
+
+ if (!ensure_flatpak_silo_with_locker (self, locker, interactive, cancellable, error))
+ return FALSE;
+
+ /* always do AppStream properties */
+ if (!gs_flatpak_refine_appstream (self, app, self->silo, flags, interactive, cancellable, error))
+ return FALSE;
+
+ /* AppStream sets the source to appname/arch/branch */
+ if (!gs_refine_item_metadata (self, app, error)) {
+ g_prefix_error (error, "failed to get metadata: ");
+ return FALSE;
+ }
+
+ /* check the installed state */
+ if (!gs_flatpak_refine_app_state_unlocked (self, app, interactive, cancellable, error)) {
+ g_prefix_error (error, "failed to get state: ");
+ return FALSE;
+ }
+
+ /* hide any addons that aren't for this app */
+ if (!gs_flatpak_prune_addons_list (self, app, interactive, cancellable, &local_error)) {
+ g_warning ("failed to prune addons: %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ /* scope is fast, do unconditionally */
+ if (gs_app_get_state (app) != GS_APP_STATE_AVAILABLE_LOCAL)
+ gs_plugin_refine_item_scope (self, app);
+
+ /* if the state was changed, perhaps set the version from the release */
+ if (old_state != gs_app_get_state (app)) {
+ if (!gs_flatpak_refine_appstream (self, app, self->silo, flags, interactive, cancellable, error))
+ return FALSE;
+ }
+
+ /* version fallback */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) {
+ if (gs_app_get_version (app) == NULL) {
+ const gchar *branch;
+ branch = gs_app_get_branch (app);
+ gs_app_set_version (app, branch);
+ }
+ }
+
+ /* size */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) {
+ g_autoptr(GError) error_local = NULL;
+ if (!gs_plugin_refine_item_size (self, app, interactive,
+ cancellable, &error_local)) {
+ if (g_error_matches (error_local, GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NO_NETWORK)) {
+ g_debug ("failed to get size while "
+ "refining app %s: %s",
+ gs_app_get_unique_id (app),
+ error_local->message);
+ } else {
+ g_prefix_error (&error_local, "failed to get size: ");
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+ }
+ }
+
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE_DATA) != 0 &&
+ gs_app_is_installed (app) &&
+ gs_app_get_kind (app) != AS_COMPONENT_KIND_RUNTIME) {
+ if (gs_app_get_size_cache_data (app, NULL) != GS_SIZE_TYPE_VALID)
+ gs_app_set_size_cache_data (app, GS_SIZE_TYPE_VALID,
+ gs_flatpak_get_app_directory_size (app, "cache", cancellable));
+ if (gs_app_get_size_user_data (app, NULL) != GS_SIZE_TYPE_VALID)
+ gs_app_set_size_user_data (app, GS_SIZE_TYPE_VALID,
+ gs_flatpak_get_app_directory_size (app, "config", cancellable) +
+ gs_flatpak_get_app_directory_size (app, "data", cancellable));
+
+ if (g_cancellable_is_cancelled (cancellable)) {
+ gs_app_set_size_cache_data (app, GS_SIZE_TYPE_UNKNOWABLE, 0);
+ gs_app_set_size_user_data (app, GS_SIZE_TYPE_UNKNOWABLE, 0);
+ }
+ }
+
+ /* origin-hostname */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME) {
+ if (!gs_plugin_refine_item_origin_hostname (self, app, interactive,
+ cancellable,
+ error)) {
+ g_prefix_error (error, "failed to get origin-hostname: ");
+ return FALSE;
+ }
+ }
+
+ /* permissions */
+ if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME ||
+ flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS) {
+ g_autoptr(GError) error_local = NULL;
+ if (!gs_plugin_refine_item_metadata (self, app, interactive,
+ cancellable, &error_local)) {
+ if (!gs_plugin_get_network_available (self->plugin) &&
+ g_error_matches (error_local, GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NO_NETWORK)) {
+ g_debug ("failed to get permissions while "
+ "refining app %s: %s",
+ gs_app_get_unique_id (app),
+ error_local->message);
+ } else {
+ g_prefix_error (&error_local, "failed to read permissions from app '%s' metadata: ", gs_app_get_unique_id (app));
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+ }
+ }
+
+ if (gs_app_get_origin (app))
+ gs_flatpak_set_app_origin (self, app, gs_app_get_origin (app), NULL, interactive, cancellable);
+
+ return TRUE;
+}
+
+void
+gs_flatpak_refine_addons (GsFlatpak *self,
+ GsApp *parent_app,
+ GsPluginRefineFlags flags,
+ GsAppState state,
+ gboolean interactive,
+ GCancellable *cancellable)
+{
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+ g_autoptr(GsAppList) addons = NULL;
+ g_autoptr(GString) errors = NULL;
+ guint ii, sz;
+
+ addons = gs_app_dup_addons (parent_app);
+ sz = addons ? gs_app_list_length (addons) : 0;
+
+ for (ii = 0; ii < sz; ii++) {
+ GsApp *addon = gs_app_list_index (addons, ii);
+ g_autoptr(GError) local_error = NULL;
+
+ if (state != gs_app_get_state (addon))
+ continue;
+
+ /* To have refined also the state */
+ gs_app_set_state (addon, GS_APP_STATE_UNKNOWN);
+
+ if (!gs_flatpak_refine_app_unlocked (self, addon, flags, interactive, &locker, cancellable, &local_error)) {
+ if (errors)
+ g_string_append_c (errors, '\n');
+ else
+ errors = g_string_new (NULL);
+ g_string_append_printf (errors, _("Failed to refine addon ‘%s’: %s"),
+ gs_app_get_name (addon), local_error->message);
+ }
+ }
+
+ if (errors) {
+ g_autoptr(GsPluginEvent) event = NULL;
+ g_autoptr(GError) error_local = g_error_new_literal (GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+ errors->str);
+
+ event = gs_plugin_event_new ("error", error_local,
+ NULL);
+ gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+ gs_plugin_report_event (self->plugin, event);
+ }
+}
+
+gboolean
+gs_flatpak_refine_app (GsFlatpak *self,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ /* ensure valid */
+ if (!gs_flatpak_rescan_app_data (self, interactive, cancellable, error))
+ return FALSE;
+
+ return gs_flatpak_refine_app_unlocked (self, app, flags, interactive, &locker, cancellable, error);
+}
+
+gboolean
+gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app,
+ GsAppList *list, GsPluginRefineFlags refine_flags,
+ gboolean interactive,
+ GCancellable *cancellable, GError **error)
+{
+ const gchar *id;
+ g_autofree gchar *xpath = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GPtrArray) components = NULL;
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ /* not enough info to find */
+ id = gs_app_get_id (app);
+ if (id == NULL)
+ return TRUE;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ /* find all apps when matching any prefixes */
+ xpath = g_strdup_printf ("components/component/id[text()='%s']/..", id);
+ components = xb_silo_query (self->silo, xpath, 0, &error_local);
+ if (components == NULL) {
+ if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ return TRUE;
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ return FALSE;
+ }
+
+ gs_flatpak_ensure_remote_title (self, interactive, cancellable);
+
+ for (guint i = 0; i < components->len; i++) {
+ XbNode *component = g_ptr_array_index (components, i);
+ g_autoptr(GsApp) new = NULL;
+ new = gs_appstream_create_app (self->plugin, self->silo, component, error);
+ if (new == NULL)
+ return FALSE;
+ gs_flatpak_claim_app (self, new);
+ if (!gs_flatpak_refine_app_unlocked (self, new, refine_flags, interactive, &locker, cancellable, error))
+ return FALSE;
+ gs_app_subsume_metadata (new, app);
+ gs_app_list_add (list, new);
+ }
+
+ /* success */
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_launch (GsFlatpak *self,
+ GsApp *app,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* launch the app */
+ if (!flatpak_installation_launch (gs_flatpak_get_installation (self, interactive),
+ gs_flatpak_app_get_ref_name (app),
+ gs_flatpak_app_get_ref_arch (app),
+ gs_app_get_branch (app),
+ NULL,
+ cancellable,
+ error)) {
+ gs_flatpak_error_convert (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_app_remove_source (GsFlatpak *self,
+ GsApp *app,
+ gboolean is_remove,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FlatpakRemote) xremote = NULL;
+ gboolean success;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* find the remote */
+ xremote = flatpak_installation_get_remote_by_name (installation,
+ gs_app_get_id (app),
+ cancellable, error);
+ if (xremote == NULL) {
+ gs_flatpak_error_convert (error);
+ g_prefix_error (error,
+ "flatpak source %s not found: ",
+ gs_app_get_id (app));
+ return FALSE;
+ }
+
+ /* remove */
+ gs_app_set_state (app, GS_APP_STATE_REMOVING);
+ if (is_remove) {
+ success = flatpak_installation_remove_remote (installation, gs_app_get_id (app), cancellable, error);
+ } else {
+ gboolean was_disabled = flatpak_remote_get_disabled (xremote);
+ flatpak_remote_set_disabled (xremote, TRUE);
+ success = flatpak_installation_modify_remote (installation, xremote, cancellable, error);
+ if (!success)
+ flatpak_remote_set_disabled (xremote, was_disabled);
+ }
+
+ if (!success) {
+ gs_flatpak_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ /* invalidate cache */
+ gs_flatpak_invalidate_silo (self);
+
+ gs_app_set_state (app, is_remove ? GS_APP_STATE_UNAVAILABLE : GS_APP_STATE_AVAILABLE);
+
+ gs_plugin_repository_changed (self->plugin, app);
+
+ return TRUE;
+}
+
+GsApp *
+gs_flatpak_file_to_app_bundle (GsFlatpak *self,
+ GFile *file,
+ gboolean unrefined,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GBytes) appstream_gz = NULL;
+ g_autoptr(GBytes) icon_data64 = NULL, icon_data128 = NULL;
+ g_autoptr(GBytes) metadata = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(FlatpakBundleRef) xref_bundle = NULL;
+
+ /* load bundle */
+ xref_bundle = flatpak_bundle_ref_new (file, error);
+ if (xref_bundle == NULL) {
+ gs_flatpak_error_convert (error);
+ g_prefix_error (error, "error loading bundle: ");
+ return NULL;
+ }
+
+ /* load metadata */
+ app = gs_flatpak_create_app (self, NULL, FLATPAK_REF (xref_bundle), NULL, interactive, cancellable);
+ if (unrefined)
+ return g_steal_pointer (&app);
+
+ gs_flatpak_app_set_file_kind (app, GS_FLATPAK_APP_FILE_KIND_BUNDLE);
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE_LOCAL);
+ gs_app_set_size_installed (app, GS_SIZE_TYPE_VALID, flatpak_bundle_ref_get_installed_size (xref_bundle));
+ gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref_bundle));
+ metadata = flatpak_bundle_ref_get_metadata (xref_bundle);
+ if (!gs_flatpak_set_app_metadata (self, app,
+ g_bytes_get_data (metadata, NULL),
+ g_bytes_get_size (metadata),
+ interactive,
+ cancellable,
+ error))
+ return NULL;
+
+ /* load AppStream */
+ appstream_gz = flatpak_bundle_ref_get_appstream (xref_bundle);
+ if (appstream_gz != NULL) {
+ if (!gs_flatpak_refine_appstream_from_bytes (self, app, NULL, NULL,
+ appstream_gz,
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID,
+ interactive,
+ cancellable, error))
+ return NULL;
+ } else {
+ g_warning ("no appstream metadata in file");
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST,
+ gs_flatpak_app_get_ref_name (app));
+ gs_app_set_summary (app, GS_APP_QUALITY_LOWEST,
+ "A flatpak application");
+ gs_app_set_description (app, GS_APP_QUALITY_LOWEST, "");
+ }
+
+ /* Load icons. Currently flatpak only supports exactly 64px or 128px
+ * icons in bundles. */
+ icon_data64 = flatpak_bundle_ref_get_icon (xref_bundle, 64);
+ if (icon_data64 != NULL) {
+ g_autoptr(GIcon) icon = g_bytes_icon_new (icon_data64);
+ gs_icon_set_width (icon, 64);
+ gs_icon_set_height (icon, 64);
+ gs_app_add_icon (app, icon);
+ }
+
+ icon_data128 = flatpak_bundle_ref_get_icon (xref_bundle, 128);
+ if (icon_data128 != NULL) {
+ g_autoptr(GIcon) icon = g_bytes_icon_new (icon_data128);
+ gs_icon_set_width (icon, 128);
+ gs_icon_set_height (icon, 128);
+ gs_app_add_icon (app, icon);
+ }
+
+ /* Fallback */
+ if (icon_data64 == NULL && icon_data128 == NULL) {
+ g_autoptr(GIcon) icon = g_themed_icon_new ("system-component-application");
+ gs_app_add_icon (app, icon);
+ }
+
+ /* not quite true: this just means we can update this specific app */
+ if (flatpak_bundle_ref_get_origin (xref_bundle))
+ gs_app_add_quirk (app, GS_APP_QUIRK_HAS_SOURCE);
+
+ /* success */
+ return g_steal_pointer (&app);
+}
+
+static gboolean
+_txn_abort_on_ready (FlatpakTransaction *transaction)
+{
+ return FALSE;
+}
+
+static gboolean
+_txn_add_new_remote (FlatpakTransaction *transaction,
+ FlatpakTransactionRemoteReason reason,
+ const char *from_id,
+ const char *remote_name,
+ const char *url)
+{
+ return TRUE;
+}
+
+static int
+_txn_choose_remote_for_ref (FlatpakTransaction *transaction,
+ const char *for_ref,
+ const char *runtime_ref,
+ const char * const *remotes)
+{
+ /* This transaction is just for displaying the app not installing it so
+ * this choice shouldn't matter */
+ return 0;
+}
+
+GsApp *
+gs_flatpak_file_to_app_ref (GsFlatpak *self,
+ GFile *file,
+ gboolean unrefined,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsApp *runtime;
+ const gchar *const *locales = g_get_language_names ();
+ const gchar *remote_name;
+ gboolean is_runtime, success;
+ gsize len = 0;
+ GList *txn_ops;
+#if !FLATPAK_CHECK_VERSION(1,13,1)
+ guint64 app_installed_size = 0, app_download_size = 0;
+#endif
+ g_autofree gchar *contents = NULL;
+ g_autoptr(FlatpakTransaction) transaction = NULL;
+ g_autoptr(FlatpakRef) parsed_ref = NULL;
+ g_autoptr(FlatpakRemoteRef) remote_ref = NULL;
+ g_autoptr(FlatpakRemote) xremote = NULL;
+ g_autoptr(GBytes) ref_file_data = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(XbBuilder) builder = xb_builder_new ();
+ g_autoptr(XbSilo) silo = NULL;
+ g_autofree gchar *origin_url = NULL;
+ g_autofree gchar *ref_comment = NULL;
+ g_autofree gchar *ref_description = NULL;
+ g_autofree gchar *ref_homepage = NULL;
+ g_autofree gchar *ref_icon = NULL;
+ g_autofree gchar *ref_title = NULL;
+ g_autofree gchar *ref_name = NULL;
+ g_autofree gchar *ref_branch = NULL;
+ FlatpakInstallation *installation = gs_flatpak_get_installation (self, interactive);
+
+ /* add current locales */
+ for (guint i = 0; locales[i] != NULL; i++)
+ xb_builder_add_locale (builder, locales[i]);
+
+ /* get file data */
+ if (!g_file_load_contents (file,
+ cancellable,
+ &contents,
+ &len,
+ NULL,
+ error)) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+
+ /* load the file */
+ kf = g_key_file_new ();
+ if (!g_key_file_load_from_data (kf, contents, len, G_KEY_FILE_NONE, error)) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+
+ /* check version */
+ if (g_key_file_has_key (kf, "Flatpak Ref", "Version", NULL)) {
+ guint64 ver = g_key_file_get_uint64 (kf, "Flatpak Ref", "Version", NULL);
+ if (ver != 1) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "unsupported version %" G_GUINT64_FORMAT, ver);
+ return NULL;
+ }
+ }
+
+ /* get name, branch, kind */
+ ref_name = g_key_file_get_string (kf, "Flatpak Ref", "Name", error);
+ if (ref_name == NULL) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+ if (g_key_file_has_key (kf, "Flatpak Ref", "Branch", NULL)) {
+ ref_branch = g_key_file_get_string (kf, "Flatpak Ref", "Branch", error);
+ if (ref_branch == NULL) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+ } else {
+ ref_branch = g_strdup ("master");
+ }
+ if (g_key_file_has_key (kf, "Flatpak Ref", "IsRuntime", NULL)) {
+ is_runtime = g_key_file_get_boolean (kf, "Flatpak Ref", "IsRuntime", error);
+ if (error != NULL && *error != NULL) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+ } else {
+ is_runtime = FALSE;
+ }
+
+ if (unrefined) {
+ /* Note: we don't support non-default arch here but it's not a
+ * regression since we never have for a flatpakref
+ */
+ g_autofree char *app_ref = g_strdup_printf ("%s/%s/%s/%s",
+ is_runtime ? "runtime" : "app",
+ ref_name,
+ flatpak_get_default_arch (),
+ ref_branch);
+ parsed_ref = flatpak_ref_parse (app_ref, error);
+ if (parsed_ref == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+
+ /* early return */
+ app = gs_flatpak_create_app (self, NULL, parsed_ref, NULL, interactive, cancellable);
+ return g_steal_pointer (&app);
+ }
+
+ /* Add the remote (to the temporary installation) but abort the
+ * transaction before it installs the app
+ */
+ transaction = flatpak_transaction_new_for_installation (installation, cancellable, error);
+ if (transaction == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ flatpak_transaction_set_no_interaction (transaction, TRUE);
+ g_signal_connect (transaction, "ready-pre-auth", G_CALLBACK (_txn_abort_on_ready), NULL);
+ g_signal_connect (transaction, "add-new-remote", G_CALLBACK (_txn_add_new_remote), NULL);
+ g_signal_connect (transaction, "choose-remote-for-ref", G_CALLBACK (_txn_choose_remote_for_ref), NULL);
+ ref_file_data = g_bytes_new (contents, len);
+ if (!flatpak_transaction_add_install_flatpakref (transaction, ref_file_data, error)) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ success = flatpak_transaction_run (transaction, cancellable, &error_local);
+ g_assert (!success); /* aborted in _txn_abort_on_ready */
+
+ /* We don't check for FLATPAK_ERROR_ALREADY_INSTALLED here because it's
+ * a temporary installation
+ */
+ if (!g_error_matches (error_local, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED)) {
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+
+ g_clear_error (&error_local);
+
+ /* find the operation for the flatpakref */
+ txn_ops = flatpak_transaction_get_operations (transaction);
+ for (GList *l = txn_ops; l != NULL; l = l->next) {
+ FlatpakTransactionOperation *op = l->data;
+ const char *op_ref = flatpak_transaction_operation_get_ref (op);
+ parsed_ref = flatpak_ref_parse (op_ref, error);
+ if (parsed_ref == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ if (g_strcmp0 (flatpak_ref_get_name (parsed_ref), ref_name) != 0) {
+ g_clear_object (&parsed_ref);
+ } else {
+ remote_name = flatpak_transaction_operation_get_remote (op);
+ g_debug ("auto-created remote name: %s", remote_name);
+#if !FLATPAK_CHECK_VERSION(1,13,1)
+ app_download_size = flatpak_transaction_operation_get_download_size (op);
+ app_installed_size = flatpak_transaction_operation_get_installed_size (op);
+#endif
+ break;
+ }
+ }
+ g_assert (parsed_ref != NULL);
+ g_list_free_full (g_steal_pointer (&txn_ops), g_object_unref);
+
+#if FLATPAK_CHECK_VERSION(1,13,1)
+ /* fetch remote ref */
+ remote_ref = flatpak_installation_fetch_remote_ref_sync (installation,
+ remote_name,
+ flatpak_ref_get_kind (parsed_ref),
+ flatpak_ref_get_name (parsed_ref),
+ flatpak_ref_get_arch (parsed_ref),
+ flatpak_ref_get_branch (parsed_ref),
+ cancellable,
+ error);
+ if (remote_ref == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ app = gs_flatpak_create_app (self, remote_name, FLATPAK_REF (remote_ref), NULL, interactive, cancellable);
+#else
+ app = gs_flatpak_create_app (self, remote_name, parsed_ref, NULL, interactive, cancellable);
+ gs_app_set_size_download (app, (app_download_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, app_download_size);
+ gs_app_set_size_installed (app, (app_installed_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, app_installed_size);
+#endif
+
+ gs_app_add_quirk (app, GS_APP_QUIRK_HAS_SOURCE);
+ gs_flatpak_app_set_file_kind (app, GS_FLATPAK_APP_FILE_KIND_REF);
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
+ runtime = gs_app_get_runtime (app);
+ if (runtime != NULL) {
+ g_autofree char *runtime_ref = gs_flatpak_app_get_ref_display (runtime);
+ if (gs_app_get_state (runtime) == GS_APP_STATE_UNKNOWN) {
+ g_autofree gchar *uri = NULL;
+ /* the new runtime is available from the RuntimeRepo */
+ uri = g_key_file_get_string (kf, "Flatpak Ref", "RuntimeRepo", NULL);
+ gs_flatpak_app_set_runtime_url (runtime, uri);
+ }
+
+ /* find the operation for the runtime to set its size data. Since this
+ * is all happening on a tmp installation, it won't be available later
+ * during the refine step
+ */
+ txn_ops = flatpak_transaction_get_operations (transaction);
+ for (GList *l = txn_ops; l != NULL; l = l->next) {
+ FlatpakTransactionOperation *op = l->data;
+ const char *op_ref = flatpak_transaction_operation_get_ref (op);
+ if (g_strcmp0 (runtime_ref, op_ref) == 0) {
+ guint64 installed_size = 0, download_size = 0;
+ download_size = flatpak_transaction_operation_get_download_size (op);
+ gs_app_set_size_download (runtime, (download_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, download_size);
+ installed_size = flatpak_transaction_operation_get_installed_size (op);
+ gs_app_set_size_installed (runtime, (installed_size != 0) ? GS_SIZE_TYPE_VALID : GS_SIZE_TYPE_UNKNOWN, installed_size);
+ break;
+ }
+ }
+ g_list_free_full (g_steal_pointer (&txn_ops), g_object_unref);
+ }
+
+ /* use the data from the flatpakref file as a fallback */
+ ref_title = g_key_file_get_string (kf, "Flatpak Ref", "Title", NULL);
+ if (ref_title != NULL)
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL, ref_title);
+ ref_comment = g_key_file_get_string (kf, "Flatpak Ref", "Comment", NULL);
+ if (ref_comment != NULL)
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, ref_comment);
+ ref_description = g_key_file_get_string (kf, "Flatpak Ref", "Description", NULL);
+ if (ref_description != NULL)
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL, ref_description);
+ ref_homepage = g_key_file_get_string (kf, "Flatpak Ref", "Homepage", NULL);
+ if (ref_homepage != NULL)
+ gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, ref_homepage);
+ ref_icon = g_key_file_get_string (kf, "Flatpak Ref", "Icon", NULL);
+ if (ref_icon != NULL &&
+ (g_str_has_prefix (ref_icon, "http:") ||
+ g_str_has_prefix (ref_icon, "https:"))) {
+ g_autoptr(GIcon) icon = gs_remote_icon_new (ref_icon);
+ gs_app_add_icon (app, icon);
+ }
+
+ /* set the origin data */
+ xremote = flatpak_installation_get_remote_by_name (installation,
+ remote_name,
+ cancellable,
+ error);
+ if (xremote == NULL) {
+ gs_flatpak_error_convert (error);
+ return NULL;
+ }
+ origin_url = flatpak_remote_get_url (xremote);
+ if (origin_url == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no URL for remote %s",
+ flatpak_remote_get_name (xremote));
+ return NULL;
+ }
+ gs_app_set_origin_hostname (app, origin_url);
+
+ /* get the new appstream data (nonfatal for failure) */
+ if (!gs_flatpak_refresh_appstream_remote (self, remote_name, interactive,
+ cancellable, &error_local)) {
+ g_autoptr(GsPluginEvent) event = NULL;
+
+ gs_flatpak_error_convert (&error_local);
+
+ event = gs_plugin_event_new ("app", app,
+ "error", error_local,
+ NULL);
+ gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+ gs_plugin_report_event (self->plugin, event);
+ g_clear_error (&error_local);
+ }
+
+ /* get this now, as it's not going to be available at install time */
+ if (!gs_plugin_refine_item_metadata (self, app, interactive, cancellable, error))
+ return NULL;
+
+ /* parse it */
+ if (!gs_flatpak_add_apps_from_xremote (self, builder, xremote, interactive, cancellable, error))
+ return NULL;
+
+ /* build silo */
+ /* No need to change the thread-default main context because the silo
+ * doesn’t live beyond this function */
+ silo = xb_builder_compile (builder,
+ XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+ cancellable,
+ error);
+ if (silo == NULL)
+ return NULL;
+ if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+ g_autofree gchar *xml = NULL;
+ xml = xb_silo_export (silo,
+ XB_NODE_EXPORT_FLAG_FORMAT_INDENT |
+ XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE,
+ NULL);
+ g_debug ("showing AppStream data: %s", xml);
+ }
+
+ /* get extra AppStream data if available */
+ if (!gs_flatpak_refine_appstream (self, app, silo,
+ GS_PLUGIN_REFINE_FLAGS_MASK,
+ interactive,
+ cancellable,
+ error))
+ return NULL;
+
+ /* success */
+ return g_steal_pointer (&app);
+}
+
+gboolean
+gs_flatpak_search (GsFlatpak *self,
+ const gchar * const *values,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+ g_autoptr(GMutexLocker) app_silo_locker = NULL;
+ g_autoptr(GPtrArray) silos_to_remove = g_ptr_array_new ();
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_search (self->plugin, self->silo, values, list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_flatpak_ensure_remote_title (self, interactive, cancellable);
+
+ gs_flatpak_claim_app_list (self, list_tmp, interactive);
+ gs_app_list_add_list (list, list_tmp);
+
+ /* Also search silos from installed apps which were missing from self->silo */
+ app_silo_locker = g_mutex_locker_new (&self->app_silos_mutex);
+ g_hash_table_iter_init (&iter, self->app_silos);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_autoptr(XbSilo) app_silo = g_object_ref (value);
+ g_autoptr(GsAppList) app_list_tmp = gs_app_list_new ();
+ const char *app_ref = (char *)key;
+ g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
+ g_auto(GStrv) split = NULL;
+ FlatpakRefKind kind;
+
+ /* Ignore any silos of apps that have since been removed.
+ * FIXME: can we use self->installed_refs here? */
+ split = g_strsplit (app_ref, "/", -1);
+ g_assert (g_strv_length (split) == 4);
+ if (g_strcmp0 (split[0], "app") == 0)
+ kind = FLATPAK_REF_KIND_APP;
+ else
+ kind = FLATPAK_REF_KIND_RUNTIME;
+ installed_ref = flatpak_installation_get_installed_ref (gs_flatpak_get_installation (self, interactive),
+ kind,
+ split[1],
+ split[2],
+ split[3],
+ NULL, NULL);
+ if (installed_ref == NULL) {
+ g_ptr_array_add (silos_to_remove, (gpointer) app_ref);
+ continue;
+ }
+
+ if (!gs_appstream_search (self->plugin, app_silo, values, app_list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_flatpak_claim_app_list (self, app_list_tmp, interactive);
+ gs_app_list_add_list (list, app_list_tmp);
+ }
+
+ for (guint i = 0; i < silos_to_remove->len; i++) {
+ const char *silo = g_ptr_array_index (silos_to_remove, i);
+ g_hash_table_remove (self->app_silos, silo);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_search_developer_apps (GsFlatpak *self,
+ const gchar * const *values,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+ g_autoptr(GMutexLocker) app_silo_locker = NULL;
+ g_autoptr(GPtrArray) silos_to_remove = g_ptr_array_new ();
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_search_developer_apps (self->plugin, self->silo, values, list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_flatpak_ensure_remote_title (self, interactive, cancellable);
+
+ gs_flatpak_claim_app_list (self, list_tmp, interactive);
+ gs_app_list_add_list (list, list_tmp);
+
+ /* Also search silos from installed apps which were missing from self->silo */
+ app_silo_locker = g_mutex_locker_new (&self->app_silos_mutex);
+ g_hash_table_iter_init (&iter, self->app_silos);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_autoptr(XbSilo) app_silo = g_object_ref (value);
+ g_autoptr(GsAppList) app_list_tmp = gs_app_list_new ();
+ const char *app_ref = (char *)key;
+ g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
+ g_auto(GStrv) split = NULL;
+ FlatpakRefKind kind;
+
+ /* Ignore any silos of apps that have since been removed.
+ * FIXME: can we use self->installed_refs here? */
+ split = g_strsplit (app_ref, "/", -1);
+ g_assert (g_strv_length (split) == 4);
+ if (g_strcmp0 (split[0], "app") == 0)
+ kind = FLATPAK_REF_KIND_APP;
+ else
+ kind = FLATPAK_REF_KIND_RUNTIME;
+ installed_ref = flatpak_installation_get_installed_ref (gs_flatpak_get_installation (self, interactive),
+ kind,
+ split[1],
+ split[2],
+ split[3],
+ NULL, NULL);
+ if (installed_ref == NULL) {
+ g_ptr_array_add (silos_to_remove, (gpointer) app_ref);
+ continue;
+ }
+
+ if (!gs_appstream_search_developer_apps (self->plugin, app_silo, values, app_list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_flatpak_claim_app_list (self, app_list_tmp, interactive);
+ gs_app_list_add_list (list, app_list_tmp);
+ }
+
+ for (guint i = 0; i < silos_to_remove->len; i++) {
+ const char *silo = g_ptr_array_index (silos_to_remove, i);
+ g_hash_table_remove (self->app_silos, silo);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_add_category_apps (GsFlatpak *self,
+ GsCategory *category,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ return gs_appstream_add_category_apps (self->plugin, self->silo,
+ category, list,
+ cancellable, error);
+}
+
+gboolean
+gs_flatpak_refine_category_sizes (GsFlatpak *self,
+ GPtrArray *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ return gs_appstream_refine_category_sizes (self->silo, list, cancellable, error);
+}
+
+gboolean
+gs_flatpak_add_popular (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_add_popular (self->silo, list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_app_list_add_list (list, list_tmp);
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_add_featured (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_add_featured (self->silo, list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_app_list_add_list (list, list_tmp);
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_add_deployment_featured (GsFlatpak *self,
+ GsAppList *list,
+ gboolean interactive,
+ const gchar *const *deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ return gs_appstream_add_deployment_featured (self->silo, deployments, list, cancellable, error);
+}
+
+gboolean
+gs_flatpak_add_alternates (GsFlatpak *self,
+ GsApp *app,
+ GsAppList *list,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_add_alternates (self->silo, app, list_tmp,
+ cancellable, error))
+ return FALSE;
+
+ gs_app_list_add_list (list, list_tmp);
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_add_recent (GsFlatpak *self,
+ GsAppList *list,
+ guint64 age,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_add_recent (self->plugin, self->silo, list_tmp, age,
+ cancellable, error))
+ return FALSE;
+
+ gs_flatpak_claim_app_list (self, list_tmp, interactive);
+ gs_app_list_add_list (list, list_tmp);
+
+ return TRUE;
+}
+
+gboolean
+gs_flatpak_url_to_app (GsFlatpak *self,
+ GsAppList *list,
+ const gchar *url,
+ gboolean interactive,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+ g_autoptr(GRWLockReaderLocker) locker = NULL;
+
+ if (!ensure_flatpak_silo_with_locker (self, &locker, interactive, cancellable, error))
+ return FALSE;
+
+ if (!gs_appstream_url_to_app (self->plugin, self->silo, list_tmp, url, cancellable, error))
+ return FALSE;
+
+ gs_flatpak_claim_app_list (self, list_tmp, interactive);
+ gs_app_list_add_list (list, list_tmp);
+
+ return TRUE;
+}
+
+const gchar *
+gs_flatpak_get_id (GsFlatpak *self)
+{
+ if (self->id == NULL) {
+ GString *str = g_string_new ("flatpak");
+ g_string_append_printf (str, "-%s",
+ as_component_scope_to_string (self->scope));
+ if (flatpak_installation_get_id (self->installation_noninteractive) != NULL) {
+ g_string_append_printf (str, "-%s",
+ flatpak_installation_get_id (self->installation_noninteractive));
+ }
+ if (self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY)
+ g_string_append (str, "-temp");
+ self->id = g_string_free (str, FALSE);
+ }
+ return self->id;
+}
+
+AsComponentScope
+gs_flatpak_get_scope (GsFlatpak *self)
+{
+ return self->scope;
+}
+
+FlatpakInstallation *
+gs_flatpak_get_installation (GsFlatpak *self,
+ gboolean interactive)
+{
+ return interactive ? self->installation_interactive : self->installation_noninteractive;
+}
+
+static void
+gs_flatpak_finalize (GObject *object)
+{
+ GsFlatpak *self;
+ g_return_if_fail (GS_IS_FLATPAK (object));
+ self = GS_FLATPAK (object);
+
+ if (self->changed_id > 0) {
+ g_signal_handler_disconnect (self->monitor, self->changed_id);
+ self->changed_id = 0;
+ }
+ if (self->silo != NULL)
+ g_object_unref (self->silo);
+ if (self->monitor != NULL)
+ g_object_unref (self->monitor);
+
+ g_free (self->id);
+ g_object_unref (self->installation_noninteractive);
+ g_object_unref (self->installation_interactive);
+ g_clear_pointer (&self->installed_refs, g_ptr_array_unref);
+ g_mutex_clear (&self->installed_refs_mutex);
+ g_object_unref (self->plugin);
+ g_hash_table_unref (self->broken_remotes);
+ g_mutex_clear (&self->broken_remotes_mutex);
+ g_rw_lock_clear (&self->silo_lock);
+ g_hash_table_unref (self->app_silos);
+ g_mutex_clear (&self->app_silos_mutex);
+ g_clear_pointer (&self->remote_title, g_hash_table_unref);
+ g_mutex_clear (&self->remote_title_mutex);
+
+ G_OBJECT_CLASS (gs_flatpak_parent_class)->finalize (object);
+}
+
+static void
+gs_flatpak_class_init (GsFlatpakClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gs_flatpak_finalize;
+}
+
+static void
+gs_flatpak_init (GsFlatpak *self)
+{
+ /* XbSilo needs external locking as we destroy the silo and build a new
+ * one when something changes */
+ g_rw_lock_init (&self->silo_lock);
+
+ g_mutex_init (&self->installed_refs_mutex);
+ self->installed_refs = NULL;
+ g_mutex_init (&self->broken_remotes_mutex);
+ self->broken_remotes = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ self->app_silos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ g_mutex_init (&self->app_silos_mutex);
+ self->remote_title = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ g_mutex_init (&self->remote_title_mutex);
+}
+
+GsFlatpak *
+gs_flatpak_new (GsPlugin *plugin, FlatpakInstallation *installation, GsFlatpakFlags flags)
+{
+ GsFlatpak *self;
+ g_autoptr(GFile) path = NULL;
+ gboolean is_user;
+
+ path = flatpak_installation_get_path (installation);
+ is_user = flatpak_installation_get_is_user (installation);
+
+ self = g_object_new (GS_TYPE_FLATPAK, NULL);
+
+ self->installation_noninteractive = g_object_ref (installation);
+ flatpak_installation_set_no_interaction (self->installation_noninteractive, TRUE);
+
+ /* Cloning it should never fail as the repo should already exist on disk. */
+ self->installation_interactive = flatpak_installation_new_for_path (path, is_user, NULL, NULL);
+ g_assert (self->installation_interactive != NULL);
+ flatpak_installation_set_no_interaction (self->installation_interactive, FALSE);
+
+ self->scope = is_user ? AS_COMPONENT_SCOPE_USER : AS_COMPONENT_SCOPE_SYSTEM;
+ self->plugin = g_object_ref (plugin);
+ self->flags = flags;
+ return GS_FLATPAK (self);
+}
+
+void
+gs_flatpak_set_busy (GsFlatpak *self,
+ gboolean busy)
+{
+ g_return_if_fail (GS_IS_FLATPAK (self));
+
+ if (busy) {
+ g_atomic_int_inc (&self->busy);
+ } else {
+ g_return_if_fail (g_atomic_int_get (&self->busy) > 0);
+ if (g_atomic_int_dec_and_test (&self->busy)) {
+ if (self->changed_while_busy) {
+ self->changed_while_busy = FALSE;
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, gs_flatpak_claim_changed_idle_cb,
+ g_object_ref (self), g_object_unref);
+ }
+ }
+ }
+}
+
+gboolean
+gs_flatpak_get_busy (GsFlatpak *self)
+{
+ g_return_val_if_fail (GS_IS_FLATPAK (self), FALSE);
+ return g_atomic_int_get (&self->busy) > 0;
+}