1
0
Fork 0
gnome-software/plugins/flatpak/gs-flatpak.c
Daniel Baumann 68ee05b3fd
Adding upstream version 48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 21:00:23 +02:00

4838 lines
159 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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-or-later
*/
/* 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 dont 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 wont race.
*/
#include <config.h>
#include <glib/gi18n.h>
#include <malloc.h>
#include <xmlb.h>
#include "gs-appstream.h"
#include "gs-app-private.h"
#include "gs-flatpak-app.h"
#include "gs-flatpak.h"
#include "gs-flatpak-transaction.h"
#include "gs-flatpak-utils.h"
#include "gs-profiler.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 */
GHashTable *remotes_by_name;
GMutex installed_refs_mutex;
GHashTable *broken_remotes;
GMutex broken_remotes_mutex;
GFileMonitor *monitor;
AsComponentScope scope;
GsPlugin *plugin;
XbSilo *silo;
GMutex silo_lock;
gchar *silo_filename;
GHashTable *silo_installed_by_desktopid;
gint silo_change_stamp;
gint silo_change_stamp_current;
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 &&
(self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY) == 0) {
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_runtime_kind_from_id (GsApp *app)
{
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 if (gs_app_get_kind (app) == AS_COMPONENT_KIND_UNKNOWN) {
gs_app_set_kind (app, AS_COMPONENT_KIND_RUNTIME);
}
}
static void
gs_flatpak_set_kind_from_flatpak (GsApp *app, FlatpakRef *xref)
{
if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_APP) {
/* the appstream plugin can set proper kind, like console-application,
from the appstream data */
if (gs_app_get_kind (app) == AS_COMPONENT_KIND_UNKNOWN)
gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
} else if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_RUNTIME) {
gs_flatpak_set_runtime_kind_from_id (app);
}
}
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 GsBusPolicyPermission
bus_policy_permission_from_string (const char *str)
{
if (str == NULL || g_str_equal (str, "none"))
return GS_BUS_POLICY_PERMISSION_NONE;
else if (g_str_equal (str, "see"))
return GS_BUS_POLICY_PERMISSION_SEE;
else if (g_str_equal (str, "talk"))
return GS_BUS_POLICY_PERMISSION_TALK;
else if (g_str_equal (str, "own"))
return GS_BUS_POLICY_PERMISSION_OWN;
else
return GS_BUS_POLICY_PERMISSION_UNKNOWN;
}
static GsAppPermissions *
perms_from_metadata (GKeyFile *keyfile)
{
char **strv;
char *str;
GsAppPermissions *permissions = gs_app_permissions_new ();
GsAppPermissionsFlags flags = GS_APP_PERMISSIONS_FLAGS_NONE;
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 | GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX;
if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "session-bus"))
flags |= GS_APP_PERMISSIONS_FLAGS_SESSION_BUS | GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX;
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;
/* "fallback-x11" without "wayland" means X11 */
if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "fallback-x11") &&
!g_strv_contains ((const gchar * const*)strv, "wayland"))
flags |= GS_APP_PERMISSIONS_FLAGS_X11;
if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "pulseaudio"))
flags |= GS_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES;
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;
if (strv != NULL && g_strv_contains ((const gchar * const*)strv, "input"))
flags |= GS_APP_PERMISSIONS_FLAGS_INPUT_DEVICES;
if (strv != NULL && (g_strv_contains ((const gchar * const*)strv, "shm") ||
g_strv_contains ((const gchar * const*)strv, "kvm")))
flags |= GS_APP_PERMISSIONS_FLAGS_SYSTEM_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-config/kdeglobals:ro", GS_APP_PERMISSIONS_FLAGS_NONE }, /* ignore this; all KDE apps need it */
{ "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 },
{ "xdg-run/pipewire-0", GS_APP_PERMISSIONS_FLAGS_SCREEN | GS_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES }, /* see https://gitlab.gnome.org/GNOME/gnome-software/-/issues/2329 */
{ "xdg-run/gvfsd", GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL }, /* see https://gitlab.gnome.org/GNOME/gnome-software/-/issues/2760 */
};
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 (bus_policy_permission_from_string (str) >= GS_BUS_POLICY_PERMISSION_TALK)
flags |= GS_APP_PERMISSIONS_FLAGS_SETTINGS;
g_free (str);
{
/* There are various services on the session bus which are known to give sandbox escapes. */
const char *known_session_bus_sandbox_escape_names[] = {
"org.freedesktop.Flatpak",
"org.freedesktop.impl.portal.PermissionStore",
};
for (size_t i = 0; !(flags & GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX) && i < G_N_ELEMENTS (known_session_bus_sandbox_escape_names); i++) {
g_autofree char *bus_policy = g_key_file_get_string (keyfile, "Session Bus Policy", known_session_bus_sandbox_escape_names[i], NULL);
if (bus_policy_permission_from_string (bus_policy) >= GS_BUS_POLICY_PERMISSION_TALK)
flags |= GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX;
}
/* org.gtk.vfs.* is known to allow file system access */
{
g_auto(GStrv) session_bus_policies = NULL;
session_bus_policies = g_key_file_get_keys (keyfile, "Session Bus Policy", NULL, NULL);
for (size_t i = 0; session_bus_policies != NULL && session_bus_policies[i] != NULL; i++) {
if (g_str_has_prefix (session_bus_policies[i], "org.gtk.vfs.")) {
g_autofree char *bus_policy = g_key_file_get_string (keyfile, "Session Bus Policy", session_bus_policies[i], NULL);
if (bus_policy_permission_from_string (bus_policy) >= GS_BUS_POLICY_PERMISSION_TALK)
flags |= GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL;
}
}
}
}
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 = NULL;
g_autoptr(GError) error_local = NULL;
old_bytes = flatpak_installed_ref_load_metadata (FLATPAK_INSTALLED_REF (xref), NULL, &error_local);
if (old_bytes == NULL) {
g_debug ("Failed to get metadata for app %s: %s",
gs_app_get_id (app), error_local->message);
g_clear_error (&error_local);
/* Permissions are unknown, so leave @additional_permissions as NULL */
g_assert (additional_permissions == NULL);
goto finish;
}
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);
/* Permissions are unknown, so leave @additional_permissions as NULL */
g_assert (additional_permissions == NULL);
} else {
g_autoptr(GsAppPermissions) old_permissions = NULL;
g_autoptr(GsAppPermissions) new_permissions = NULL;
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);
additional_permissions = gs_app_permissions_diff (old_permissions, new_permissions);
}
finish:
/* Have new permissions been requested by the app? */
gs_app_set_update_permissions (app, additional_permissions);
if (additional_permissions != NULL &&
!gs_app_permissions_is_empty (additional_permissions))
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_app_set_metadata (app, "GnomeSoftware::packagename-value", 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,
gboolean allow_cached,
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 (allow_cached && !(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 && allow_cached && !(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_atomic_int_inc (&self->silo_change_stamp);
}
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 (&self->remotes_by_name, g_hash_table_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;
}
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;
}
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;
g_autoptr(XbBuilderFixup) fixup5 = NULL;
/* 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);
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);
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 gchar *
gs_flatpak_get_desktop_files_dir (GsFlatpak *self)
{
g_autoptr(GFile) path = NULL;
g_autofree gchar *path_str = NULL;
path = flatpak_installation_get_path (self->installation_noninteractive);
path_str = g_file_get_path (path);
return g_build_filename (path_str, "exports", "share", "applications", NULL);
}
static void
gs_flatpak_rescan_installed (GsFlatpak *self,
XbBuilder *builder,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(GError) error_local = NULL;
/* add all installed desktop files */
path = gs_flatpak_get_desktop_files_dir (self);
if (!gs_appstream_load_desktop_files (builder, path, NULL, NULL, cancellable, &error_local))
g_debug ("Failed to read flatpak .desktop files in %s: %s", path, error_local->message);
}
static XbSilo *
gs_flatpak_ref_silo (GsFlatpak *self,
gboolean interactive,
gchar **out_silo_filename,
GHashTable **out_silo_installed_by_desktopid,
GCancellable *cancellable,
GError **error)
{
g_autofree gchar *blobfn = NULL;
g_autoptr(GFile) file = NULL;
g_autoptr(GPtrArray) xremotes = NULL;
g_autoptr(GPtrArray) desktop_paths = NULL;
g_autoptr(GMutexLocker) locker = NULL;
g_autoptr(XbBuilder) builder = NULL;
g_autoptr(GMainContext) old_thread_default = NULL;
locker = g_mutex_locker_new (&self->silo_lock);
/* everything is okay */
if (self->silo != NULL && xb_silo_is_valid (self->silo) &&
g_atomic_int_get (&self->silo_change_stamp_current) == g_atomic_int_get (&self->silo_change_stamp)) {
if (out_silo_filename != NULL)
*out_silo_filename = g_strdup (self->silo_filename);
if (out_silo_installed_by_desktopid != NULL && self->silo_installed_by_desktopid)
*out_silo_installed_by_desktopid = g_hash_table_ref (self->silo_installed_by_desktopid);
return g_object_ref (self->silo);
}
/* drat! silo needs regenerating */
reload:
g_clear_object (&self->silo);
g_clear_pointer (&self->silo_filename, g_free);
g_clear_pointer (&self->silo_installed_by_desktopid, g_hash_table_unref);
g_atomic_int_set (&self->silo_change_stamp_current, g_atomic_int_get (&self->silo_change_stamp));
/* 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);
}
gs_appstream_add_current_locales (builder);
/* 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 NULL;
}
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 NULL;
}
}
}
/* add any installed files without AppStream info */
gs_flatpak_rescan_installed (self, builder, cancellable, error);
/* regenerate with each minor release */
xb_builder_append_guid (builder, PACKAGE_VERSION);
/* Merge data from the installed files and the system appstream data,
which is always checked, even when the 'appstream_paths' is NULL. */
desktop_paths = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (desktop_paths, gs_flatpak_get_desktop_files_dir (self));
gs_appstream_add_data_merge_fixup (builder, NULL, desktop_paths, cancellable);
/* 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 NULL;
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);
#ifdef __GLIBC__
/* https://gitlab.gnome.org/GNOME/gnome-software/-/issues/941
* libxmlb <= 0.3.22 makes lots of temporary heap allocations parsing large XMLs
* trim the heap after parsing to control RSS growth. */
malloc_trim (0);
#endif
if (old_thread_default != NULL)
g_main_context_push_thread_default (old_thread_default);
if (g_atomic_int_get (&self->silo_change_stamp_current) != g_atomic_int_get (&self->silo_change_stamp)) {
g_clear_pointer (&blobfn, g_free);
g_clear_pointer (&xremotes, g_ptr_array_unref);
g_clear_pointer (&desktop_paths, g_ptr_array_unref);
g_clear_pointer (&old_thread_default, g_main_context_unref);
g_clear_object (&file);
g_clear_object (&builder);
g_debug ("flatpak: Reported change while loading appstream data, reloading...");
goto reload;
}
if (self->silo != NULL) {
g_autoptr(GPtrArray) installed = NULL;
g_autoptr(XbNode) info_filename = NULL;
self->silo_installed_by_desktopid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
installed = xb_silo_query (self->silo, "/component[@type='desktop-application']/launchable[@type='desktop-id']", 0, NULL);
for (guint i = 0; installed != NULL && i < installed->len; i++) {
XbNode *launchable = g_ptr_array_index (installed, i);
const gchar *id = xb_node_get_text (launchable);
if (id != NULL && *id != '\0') {
GPtrArray *nodes = g_hash_table_lookup (self->silo_installed_by_desktopid, id);
if (nodes == NULL) {
nodes = g_ptr_array_new_with_free_func (g_object_unref);
g_hash_table_insert (self->silo_installed_by_desktopid, g_strdup (id), nodes);
}
g_ptr_array_add (nodes, xb_node_get_parent (launchable));
}
}
info_filename = xb_silo_query_first (self->silo, "/info/filename", NULL);
if (info_filename != NULL)
self->silo_filename = g_strdup (xb_node_get_text (info_filename));
if (out_silo_filename != NULL)
*out_silo_filename = g_strdup (self->silo_filename);
if (out_silo_installed_by_desktopid != NULL && self->silo_installed_by_desktopid)
*out_silo_installed_by_desktopid = g_hash_table_ref (self->silo_installed_by_desktopid);
return g_object_ref (self->silo);
}
return NULL;
}
static gboolean
gs_flatpak_rescan_app_data (GsFlatpak *self,
gboolean interactive,
XbSilo **out_silo,
gchar **out_silo_filename,
GHashTable **out_silo_installed_by_desktopid,
GCancellable *cancellable,
GError **error)
{
g_autoptr(XbSilo) silo = NULL;
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;
}
}
silo = gs_flatpak_ref_silo (self, interactive, out_silo_filename, out_silo_installed_by_desktopid, cancellable, error);
if (silo == NULL) {
gs_flatpak_internal_data_changed (self);
return FALSE;
}
if (out_silo != NULL)
*out_silo = g_steal_pointer (&silo);
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;
g_autoptr(XbSilo) silo = 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 */
silo = gs_flatpak_ref_silo (self, interactive, NULL, NULL, cancellable, error);
if (silo == NULL) {
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, TRUE, cancellable);
/* Set the state to installed only from some states, to not override the updatable-live or other states */
if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN ||
gs_app_get_state (app) == GS_APP_STATE_AVAILABLE) {
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, NULL, NULL, NULL, 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, TRUE, 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;
g_autoptr(GPtrArray) icons = NULL;
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_dup_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 doesnt have a filter set,
* clear it on the @xremote (i.e. dont 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);
}
static FlatpakRemote * /* (transfer full) */
gs_flatpak_remote_by_name (GsFlatpak *self,
const gchar *lookup_name,
gboolean interactive,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->installed_refs_mutex);
FlatpakRemote *res = NULL;
if (self->remotes_by_name == NULL) {
g_autoptr(GPtrArray) remotes = flatpak_installation_list_remotes (gs_flatpak_get_installation (self, interactive),
cancellable, error);
if (remotes == NULL)
return NULL;
self->remotes_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
for (guint i = 0; i < remotes->len; i++) {
FlatpakRemote *remote = g_ptr_array_index (remotes, i);
const gchar *name = flatpak_remote_get_name (remote);
if (name != NULL) {
g_hash_table_insert (self->remotes_by_name, g_strdup (name), g_object_ref (remote));
if (res == NULL && g_strcmp0 (name, lookup_name) == 0)
res = g_object_ref (remote);
}
}
} else {
res = g_hash_table_lookup (self->remotes_by_name, lookup_name);
if (res != NULL)
g_object_ref (res);
}
if (res == NULL && error != NULL && *error == NULL)
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_REMOTE_NOT_FOUND, "Remote '%s' not found", lookup_name);
return res;
}
/* @is_install is %TRUE if the repo is being installed, or %FALSE if its being
* enabled. If its 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 = gs_flatpak_remote_by_name (self, gs_app_get_id (app), interactive, 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, NULL, NULL, NULL, 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_clear_pointer (&self->remotes_by_name, g_hash_table_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 = gs_flatpak_remote_by_name (self, gs_app_get_origin (app), interactive, 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;
}
static gboolean
gs_flatpak_refine_app_state_internal (GsFlatpak *self,
GsApp *app,
gboolean interactive,
gboolean force_state_update,
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 (!force_state_update &&
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 (force_state_update || 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 ((force_state_update || gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) &&
gs_app_get_origin (app) != NULL) {
g_autoptr(FlatpakRemote) xremote = NULL;
xremote = gs_flatpak_remote_by_name (self, gs_app_get_origin (app), interactive, 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,
gboolean force_state_update,
GCancellable *cancellable,
GError **error)
{
/* ensure valid */
if (!gs_flatpak_rescan_app_data (self, interactive, NULL, NULL, NULL, cancellable, error))
return FALSE;
return gs_flatpak_refine_app_state_internal (self, app, interactive, force_state_update, 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_metadata (app, "GnomeSoftware::packagename-value", 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);
gs_app_set_metadata (app_cache, "GnomeSoftware::packagename-value", 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_internal (self, app, interactive, FALSE, 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_autoptr(GPtrArray) remove_addons = 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);
remove_addons = g_ptr_array_new_full (gs_app_list_length (addons_list), g_object_unref);
/* 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; !found && 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; !found && 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)
g_ptr_array_add (remove_addons, g_object_ref (app_addon));
}
for (guint i = 0; i < remove_addons->len; i++) {
GsApp *addon = g_ptr_array_index (remove_addons, i);
g_debug ("removing addon '%s' from app '%s', because not related to it",
gs_app_get_unique_id (addon), gs_app_get_unique_id (app));
gs_app_remove_addon (app, addon);
}
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_internal (self,
app_runtime,
interactive,
FALSE,
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,
const gchar *silo_filename,
GHashTable *silo_installed_by_desktopid,
GCancellable *cancellable,
GError **error)
{
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);
gs_appstream_add_current_locales (builder);
/* 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, silo_installed_by_desktopid,
silo_filename ? silo_filename : "", self->scope, 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;
g_autoptr(XbQuery) query = NULL;
g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT ();
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;
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);
/* 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,
const gchar *silo_filename,
GHashTable *silo_installed_by_desktopid,
GsPluginRefineFlags flags,
GHashTable *components_by_bundle,
gboolean interactive,
GCancellable *cancellable,
GError **error)
{
const gchar *origin = gs_app_get_origin (app);
const gchar *source = gs_app_get_source_default (app);
g_autoptr(GError) error_local = NULL;
g_autoptr(XbNode) component = NULL;
if (origin == NULL || source == NULL)
return TRUE;
/* find using source and origin */
if (components_by_bundle != NULL) {
g_autofree gchar *key = g_strconcat (origin, "\n", source, NULL);
component = g_hash_table_lookup (components_by_bundle, key);
if (component != NULL)
g_object_ref (component);
} else {
g_autofree gchar *source_safe = NULL;
g_autofree gchar *xpath = NULL;
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;
g_clear_error (&error_local);
}
/* 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;
g_clear_error (&error_local);
}
if (component == NULL) {
g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
g_autoptr(GBytes) appstream_gz = NULL;
/* 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,
silo_filename, silo_installed_by_desktopid,
cancellable, error);
}
if (!gs_appstream_refine_app (self->plugin, app, silo, component, flags, silo_installed_by_desktopid,
silo_filename ? silo_filename : "", self->scope, 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_internal (GsFlatpak *self,
GsApp *app,
GsPluginRefineFlags flags,
gboolean interactive,
gboolean force_state_update,
GHashTable *components_by_bundle,
XbSilo *silo,
const gchar *silo_filename,
GHashTable *silo_installed_by_desktopid,
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;
/* always do AppStream properties */
if (!gs_flatpak_refine_appstream (self, app, silo, silo_filename, silo_installed_by_desktopid,
flags, components_by_bundle, 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_internal (self, app, interactive, force_state_update, 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, silo, silo_filename, silo_installed_by_desktopid,
flags, components_by_bundle, 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(XbSilo) silo = NULL;
g_autofree gchar *silo_filename = NULL;
g_autoptr(GHashTable) silo_installed_by_desktopid = NULL;
g_autoptr(GsAppList) addons = NULL;
g_autoptr(GString) errors = NULL;
guint ii, sz;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, &silo_filename, &silo_installed_by_desktopid, cancellable, NULL))
return;
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;
if (!gs_flatpak_refine_app_internal (self, addon, flags, interactive, TRUE, NULL, silo, silo_filename,
silo_installed_by_desktopid, 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,
"app", parent_app,
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,
gboolean force_state_update,
GCancellable *cancellable,
GError **error)
{
g_autoptr(XbSilo) silo = NULL;
g_autoptr(GHashTable) silo_installed_by_desktopid = NULL;
g_autofree gchar *silo_filename = NULL;
/* ensure valid */
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, &silo_filename, &silo_installed_by_desktopid, cancellable, error))
return FALSE;
return gs_flatpak_refine_app_internal (self, app, flags, interactive, force_state_update, NULL,
silo, silo_filename, silo_installed_by_desktopid, cancellable, error);
}
gboolean
gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app,
GsAppList *list, GsPluginRefineFlags refine_flags,
gboolean interactive,
GHashTable **inout_components_by_id,
GHashTable **inout_components_by_bundle,
GCancellable *cancellable, GError **error)
{
const gchar *id;
GPtrArray* components = NULL;
g_autoptr(GError) error_local = NULL;
g_autoptr(XbSilo) silo = NULL;
g_autoptr(GHashTable) silo_installed_by_desktopid = NULL;
g_autofree gchar *silo_filename = NULL;
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL);
/* not enough info to find */
id = gs_app_get_id (app);
if (id == NULL)
return TRUE;
silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error);
if (silo == NULL)
return FALSE;
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardQuerySilo, "Flatpak (query silo)", NULL);
if (*inout_components_by_id != NULL) {
components = g_hash_table_lookup (*inout_components_by_id, gs_app_get_id (app));
} else {
g_autoptr(GPtrArray) components_with_id = NULL;
*inout_components_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
components_with_id = xb_silo_query (silo, "components/component/id", 0, &error_local);
if (components_with_id == 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;
}
for (guint i = 0; i < components_with_id->len; i++) {
XbNode *node = g_ptr_array_index (components_with_id, i);
XbNode *comp_node = xb_node_get_parent (node);
const gchar *comp_id = xb_node_get_text (node);
GPtrArray *comps = g_hash_table_lookup (*inout_components_by_id, comp_id);
if (comps == NULL) {
comps = g_ptr_array_new_with_free_func (g_object_unref);
g_hash_table_insert (*inout_components_by_id, g_strdup (comp_id), comps);
}
g_ptr_array_add (comps, comp_node);
if (components == NULL && g_strcmp0 (id, comp_id) == 0)
components = comps;
}
}
GS_PROFILER_END_SCOPED (FlatpakRefineWildcardQuerySilo);
if (components == NULL)
return TRUE;
gs_flatpak_ensure_remote_title (self, interactive, cancellable);
if (*inout_components_by_bundle == NULL) {
g_autoptr(GPtrArray) bundles = NULL;
*inout_components_by_bundle = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
bundles = xb_silo_query (silo, "/components/component/bundle[@type='flatpak']", 0, NULL);
for (guint b = 0; bundles != NULL && b < bundles->len; b++) {
XbNode *bundle_node = g_ptr_array_index (bundles, b);
g_autoptr(XbNode) component_node = xb_node_get_parent (bundle_node);
g_autoptr(XbNode) components_node = xb_node_get_parent (component_node);
const gchar *origin = xb_node_get_attr (components_node, "origin");
if (origin != NULL) {
const gchar *bundle = xb_node_get_text (bundle_node);
if (bundle != NULL) {
g_autofree gchar *key = g_strconcat (origin, "\n", bundle, NULL);
g_hash_table_insert (*inout_components_by_bundle, g_steal_pointer (&key), g_steal_pointer (&component_node));
}
}
}
}
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardGenerateApps, "Flatpak (create app)", NULL);
for (guint i = 0; i < components->len; i++) {
XbNode *component = g_ptr_array_index (components, i);
g_autoptr(GsApp) new = NULL;
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardCreateAppstreamApp, "Flatpak (create Appstream app)", NULL);
new = gs_appstream_create_app (self->plugin, silo, component, silo_filename ? silo_filename : "",
self->scope, error);
GS_PROFILER_END_SCOPED (FlatpakRefineWildcardCreateAppstreamApp);
if (new == NULL)
return FALSE;
gs_flatpak_claim_app (self, new);
/* The appstream plugin did not find the component in the plugin's cache,
thus read the required info from the 'bundle' element. */
if (gs_flatpak_app_get_ref_name (new) == NULL ||
gs_flatpak_app_get_ref_arch (new) == NULL) {
const gchar *xref_str = NULL;
g_autoptr(XbNode) child = NULL;
g_autoptr(XbNode) next = NULL;
for (child = xb_node_get_child (component); child != NULL && xref_str == NULL;
g_object_unref (child), child = g_steal_pointer (&next)) {
next = xb_node_get_next (child);
if (g_strcmp0 (xb_node_get_element (child), "bundle") == 0 &&
g_strcmp0 (xb_node_get_attr (child, "type"), "flatpak") == 0) {
xref_str = xb_node_get_text (child);
break;
}
}
if (xref_str != NULL) {
g_auto(GStrv) split = NULL;
/* get the kind/name/arch/branch */
split = g_strsplit (xref_str, "/", -1);
if (g_strv_length (split) == 4) {
const gchar *comp_type = xb_node_get_attr (component, "type");
AsComponentKind kind = as_component_kind_from_string (comp_type);
if (kind != AS_COMPONENT_KIND_UNKNOWN)
gs_app_set_kind (new, kind);
else if (g_ascii_strcasecmp (split[0], "app") == 0)
gs_app_set_kind (new, AS_COMPONENT_KIND_DESKTOP_APP);
else if (g_ascii_strcasecmp (split[0], "runtime") == 0)
gs_flatpak_set_runtime_kind_from_id (new);
gs_flatpak_app_set_ref_name (new, split[1]);
gs_flatpak_app_set_ref_arch (new, split[2]);
gs_app_set_branch (new, split[3]);
gs_app_set_metadata (new, "GnomeSoftware::packagename-value", xref_str);
}
}
}
if (gs_flatpak_app_get_ref_name (new) == NULL ||
gs_flatpak_app_get_ref_arch (new) == NULL) {
g_debug ("Failed to get ref info for '%s' from wildcard '%s', skipping it...", gs_app_get_id (new), id);
} else {
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardRefineNewApp, "Flatpak (refine new app)", NULL);
if (!gs_flatpak_refine_app_internal (self, new, refine_flags, interactive, FALSE, *inout_components_by_bundle,
silo, silo_filename, silo_installed_by_desktopid, cancellable, error))
return FALSE;
GS_PROFILER_END_SCOPED (FlatpakRefineWildcardRefineNewApp);
GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardSubsumeMetadata, "Flatpak (subsume metadata)", NULL);
gs_app_subsume_metadata (new, app);
GS_PROFILER_END_SCOPED (FlatpakRefineWildcardSubsumeMetadata);
gs_app_list_add (list, new);
}
}
GS_PROFILER_END_SCOPED (FlatpakRefineWildcardGenerateApps);
GS_PROFILER_END_SCOPED (FlatpakRefineWildcard);
/* 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 = gs_flatpak_remote_by_name (self, gs_app_get_id (app), interactive, 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, FALSE, 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) {
g_autofree gchar *silo_filename = NULL;
g_autoptr(GHashTable) silo_installed_by_desktopid = NULL;
g_autoptr(XbSilo) tmp_silo = NULL;
tmp_silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error);
if (tmp_silo == NULL)
return NULL;
if (!gs_flatpak_refine_appstream_from_bytes (self, app, NULL, NULL,
appstream_gz,
GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID,
interactive,
silo_filename, silo_installed_by_desktopid,
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 *remote_name = NULL;
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_autoptr(XbSilo) tmp_silo = NULL;
g_autoptr(GHashTable) silo_installed_by_desktopid = NULL;
g_autofree gchar *silo_filename = 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);
gs_appstream_add_current_locales (builder);
/* 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, FALSE, 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_assert (remote_name != NULL);
g_list_free_full (g_steal_pointer (&txn_ops), g_object_unref);
#if FLATPAK_CHECK_VERSION(1,13,1)
/* fetch remote ref */
g_assert (remote_name != NULL);
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, FALSE, cancellable);
#else
app = gs_flatpak_create_app (self, remote_name, parsed_ref, NULL, interactive, FALSE, 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:"))) {
/* Unfortunately the .flatpakref file doesnt specify the icon
* size or scale out of band. */
g_autoptr(GIcon) icon = gs_remote_icon_new (ref_icon);
gs_app_add_icon (app, icon);
}
/* set the origin data */
xremote = gs_flatpak_remote_by_name (self, remote_name, interactive, 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
* doesnt 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);
}
tmp_silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error);
if (tmp_silo == NULL)
return NULL;
/* get extra AppStream data if available */
if (!gs_flatpak_refine_appstream (self, app, silo, silo_filename, silo_installed_by_desktopid,
GS_PLUGIN_REFINE_FLAGS_MASK,
NULL,
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(GMutexLocker) app_silo_locker = NULL;
g_autoptr(GPtrArray) silos_to_remove = g_ptr_array_new ();
g_autoptr(XbSilo) silo = NULL;
GHashTableIter iter;
gpointer key, value;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_search (self->plugin, 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 *app_ref = g_ptr_array_index (silos_to_remove, i);
g_hash_table_remove (self->app_silos, app_ref);
}
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(GMutexLocker) app_silo_locker = NULL;
g_autoptr(GPtrArray) silos_to_remove = g_ptr_array_new ();
g_autoptr(XbSilo) silo = NULL;
GHashTableIter iter;
gpointer key, value;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_search_developer_apps (self->plugin, 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 *app_ref = g_ptr_array_index (silos_to_remove, i);
g_hash_table_remove (self->app_silos, app_ref);
}
return TRUE;
}
gboolean
gs_flatpak_add_category_apps (GsFlatpak *self,
GsCategory *category,
GsAppList *list,
gboolean interactive,
GCancellable *cancellable,
GError **error)
{
g_autoptr(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
return gs_appstream_add_category_apps (self->plugin, silo, category, list, cancellable, error);
}
gboolean
gs_flatpak_refine_category_sizes (GsFlatpak *self,
GPtrArray *list,
gboolean interactive,
GCancellable *cancellable,
GError **error)
{
g_autoptr(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
return gs_appstream_refine_category_sizes (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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_add_popular (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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_add_featured (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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
return gs_appstream_add_deployment_featured (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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_add_alternates (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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_add_recent (self->plugin, 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(XbSilo) silo = NULL;
if (!gs_flatpak_rescan_app_data (self, interactive, &silo, NULL, NULL, cancellable, error))
return FALSE;
if (!gs_appstream_url_to_app (self->plugin, 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;
}
g_clear_object (&self->silo);
g_clear_object (&self->monitor);
g_clear_pointer (&self->silo_filename, g_free);
g_clear_pointer (&self->silo_installed_by_desktopid, g_hash_table_unref);
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_clear_pointer (&self->remotes_by_name, g_hash_table_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_mutex_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_mutex_init (&self->silo_lock);
g_mutex_init (&self->installed_refs_mutex);
self->installed_refs = NULL;
self->remotes_by_name = 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;
}
gboolean
gs_flatpak_purge_sync (GsFlatpak *self,
GCancellable *cancellable,
GError **error)
{
FlatpakInstallation *installation;
g_autoptr(GPtrArray) unused_refs = NULL;
installation = gs_flatpak_get_installation (self, FALSE);
if (installation == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Non-interactive installation not found");
return FALSE;
}
unused_refs = flatpak_installation_list_unused_refs (installation, NULL, cancellable, error);
if (unused_refs == NULL)
return FALSE;
g_debug ("Installation '%s' has %u unused refs", gs_flatpak_get_id (self), unused_refs->len);
if (unused_refs->len > 0) {
g_autoptr(FlatpakTransaction) transaction = NULL;
transaction = gs_flatpak_transaction_new (installation, GS_FLATPAK_ERROR_MODE_STOP_ON_FIRST_ERROR, cancellable, error);
if (transaction == NULL) {
g_prefix_error_literal (error, "failed to build transaction: ");
return FALSE;
}
flatpak_transaction_set_no_interaction (transaction, TRUE);
flatpak_transaction_set_no_pull (transaction, TRUE);
/* use system installations as dependency sources for user installations */
flatpak_transaction_add_default_dependency_sources (transaction);
for (guint i = 0; i < unused_refs->len; i++) {
g_autoptr(GsApp) app = NULL;
FlatpakRef *ref = g_ptr_array_index (unused_refs, i);
const gchar *ref_str = flatpak_ref_format_ref_cached (ref);
app = gs_flatpak_ref_to_app (self, ref_str, FALSE, cancellable, error);
if (app == NULL) {
g_prefix_error (error, "failed to create app from ref '%s': ", ref_str);
return FALSE;
}
gs_flatpak_transaction_add_app (transaction, app);
if (!flatpak_transaction_add_uninstall (transaction, ref_str, error)) {
g_prefix_error (error, "failed to add ref to transaction: ");
return FALSE;
}
g_debug ("Going to uninstall '%s'", ref_str);
}
return gs_flatpak_transaction_run (transaction, cancellable, error);
} else {
/* Nothing to uninstall. */
return TRUE;
}
}