/* bz-flatpak-instance.c * * Copyright 2025 Adam Masciola * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "BAZAAR::FLATPAK" #define BAZAAR_MODULE "flatpak" #include #include #include "config.h" #include "bz-app-permissions.h" #include "bz-backend-notification.h" #include "bz-backend-transaction-op-payload.h" #include "bz-backend-transaction-op-progress-payload.h" #include "bz-backend.h" #include "bz-flatpak-bundle-result.h" #include "bz-flatpak-private.h" #include "bz-flatpak-repo.h" #include "bz-repository.h" #include "env.h" #include "global-net.h" #include "io.h" #include "util.h" /* clang-format off */ G_DEFINE_QUARK (bz-flatpak-error-quark, bz_flatpak_error); /* clang-format on */ struct _BzFlatpakInstance { GObject parent_instance; DexScheduler *scheduler; FlatpakInstallation *system; FlatpakInstallation *system_interactive; GFileMonitor *system_events; int system_mute; FlatpakInstallation *user; FlatpakInstallation *user_interactive; GFileMonitor *user_events; int user_mute; GMutex mute_mutex; GMutex notif_mutex; GPtrArray *notif_channels; DexFuture *notif_send; GMutex transactions_mutex; /* BzEntry* -> GPtrArray* -> GCancellable* */ GHashTable *ongoing_cancellables; }; static void backend_iface_init (BzBackendInterface *iface); G_DEFINE_FINAL_TYPE_WITH_CODE ( BzFlatpakInstance, bz_flatpak_instance, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (BZ_TYPE_BACKEND, backend_iface_init)); BZ_DEFINE_DATA ( init, Init, { BzFlatpakInstance *self; }, BZ_RELEASE_DATA (self, g_object_unref)) static DexFuture * init_fiber (InitData *data); BZ_DEFINE_DATA ( check_has_flathub, CheckHasFlathub, { GWeakRef *self; GCancellable *cancellable; }, BZ_RELEASE_DATA (self, bz_weak_release); BZ_RELEASE_DATA (cancellable, g_object_unref)); static DexFuture * check_has_flathub_fiber (CheckHasFlathubData *data); BZ_DEFINE_DATA ( ensure_flathub, EnsureFlathub, { GWeakRef *self; GCancellable *cancellable; }, BZ_RELEASE_DATA (self, bz_weak_release); BZ_RELEASE_DATA (cancellable, g_object_unref)); static DexFuture * ensure_flathub_fiber (EnsureFlathubData *data); BZ_DEFINE_DATA ( load_local_ref, LoadLocalRef, { GWeakRef *self; GCancellable *cancellable; GFile *file; }, BZ_RELEASE_DATA (self, bz_weak_release); BZ_RELEASE_DATA (cancellable, g_object_unref); BZ_RELEASE_DATA (file, g_object_unref)); static DexFuture * load_local_ref_fiber (LoadLocalRefData *data); DexFuture * bz_flatpak_repo_new_from_url (const char *url, GCancellable *cancellable); BZ_DEFINE_DATA ( gather_refs, GatherRefs, { GWeakRef *self; GCancellable *cancellable; guint total; }, BZ_RELEASE_DATA (self, bz_weak_release); BZ_RELEASE_DATA (cancellable, g_object_unref)); static DexFuture * retrieve_remote_refs_fiber (GatherRefsData *data); static DexFuture * retrieve_installs_fiber (GatherRefsData *data); static DexFuture * retrieve_updates_fiber (GatherRefsData *data); BZ_DEFINE_DATA ( list_repos, ListRepos, { GWeakRef *self; GCancellable *cancellable; }, BZ_RELEASE_DATA (self, bz_weak_release); BZ_RELEASE_DATA (cancellable, g_object_unref)); static gboolean append_repos_for_installation (GListStore *repos, FlatpakInstallation *installation, gboolean is_user, GCancellable *cancellable, GError **error); static DexFuture * list_repositories_fiber (ListReposData *data); static DexFuture * ensure_runtime_remote_fiber (BzFlatpakInstance *self, FlatpakInstallation *installation, const char *bundle_path, GCancellable *cancellable); BZ_DEFINE_DATA ( retrieve_refs_for_remote, RetrieveRefsForRemote, { GatherRefsData *parent; FlatpakInstallation *installation; FlatpakRemote *remote; }, BZ_RELEASE_DATA (parent, gather_refs_data_unref); BZ_RELEASE_DATA (installation, g_object_unref); BZ_RELEASE_DATA (remote, g_object_unref)); static DexFuture * retrieve_refs_for_remote_fiber (RetrieveRefsForRemoteData *data); static DexFuture * retrieve_refs_for_enumerable_remote (BzFlatpakInstance *self, GCancellable *cancellable, const char *remote_name, FlatpakInstallation *installation, FlatpakRemote *remote); static DexFuture * retrieve_refs_for_noenumerable_remote (BzFlatpakInstance *self, GCancellable *cancellable, const char *remote_name, FlatpakInstallation *installation, FlatpakRemote *remote); BZ_DEFINE_DATA ( transaction, Transaction, { GWeakRef *self; GMutex mutex; GCancellable *cancellable; GPtrArray *installs; GPtrArray *updates; GPtrArray *removals; DexChannel *channel; GPtrArray *send_futures; GHashTable *ref_to_entry_hash; GHashTable *op_to_progress_hash; GHashTable *rebased_refs; guint unidentified_op_cnt; }, BZ_RELEASE_DATA (self, bz_weak_release); g_mutex_clear (&self->mutex); BZ_RELEASE_DATA (cancellable, g_object_unref); BZ_RELEASE_DATA (installs, g_ptr_array_unref); BZ_RELEASE_DATA (updates, g_ptr_array_unref); BZ_RELEASE_DATA (removals, g_ptr_array_unref); BZ_RELEASE_DATA (channel, dex_unref); BZ_RELEASE_DATA (send_futures, g_ptr_array_unref); BZ_RELEASE_DATA (ref_to_entry_hash, g_hash_table_unref); BZ_RELEASE_DATA (op_to_progress_hash, g_hash_table_unref) BZ_RELEASE_DATA (rebased_refs, g_hash_table_unref)); static DexFuture * transaction_fiber (TransactionData *data); BZ_DEFINE_DATA ( transaction_job, TransactionJob, { TransactionData *parent; FlatpakTransaction *transaction; }, BZ_RELEASE_DATA (parent, transaction_data_unref); BZ_RELEASE_DATA (transaction, g_object_unref)); static DexFuture * transaction_job_fiber (TransactionJobData *data); static void transaction_new_operation (FlatpakTransaction *object, FlatpakTransactionOperation *operation, FlatpakTransactionProgress *progress, TransactionData *data); static void transaction_operation_done (FlatpakTransaction *object, FlatpakTransactionOperation *operation, gchar *commit, gint result, TransactionData *data); static gboolean transaction_operation_error (FlatpakTransaction *object, FlatpakTransactionOperation *operation, GError *error, gint details, TransactionData *data); static gboolean transaction_ready (FlatpakTransaction *object, TransactionData *data); static BzFlatpakEntry * find_entry_from_operation (TransactionData *data, FlatpakTransactionOperation *operation); BZ_DEFINE_DATA ( transaction_operation, TransactionOperation, { TransactionData *parent; BzFlatpakEntry *entry; BzBackendTransactionOpPayload *op; }, BZ_RELEASE_DATA (parent, transaction_data_unref); BZ_RELEASE_DATA (entry, g_object_unref); BZ_RELEASE_DATA (op, g_object_unref)); static void transaction_progress_changed (FlatpakTransactionProgress *object, TransactionOperationData *data); BZ_DEFINE_DATA ( transaction_operation_done, TransactionOperationDone, { TransactionData *parent; FlatpakTransaction *transaction; FlatpakTransactionOperation *operation; }, BZ_RELEASE_DATA (parent, transaction_data_unref); BZ_RELEASE_DATA (transaction, g_object_unref); BZ_RELEASE_DATA (operation, g_object_unref)); static DexFuture * transaction_operation_done_fiber (TransactionOperationDoneData *data); static void installation_event (BzFlatpakInstance *self, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GFileMonitor *monitor); static void send_notif (BzFlatpakInstance *self, DexChannel *channel, BzBackendNotification *notif, gboolean lock); static void send_notif_all (BzFlatpakInstance *self, BzBackendNotification *notif, gboolean lock); #define SEND_AND_RETURN_ERROR(_self, _lock, _error, ...) \ G_STMT_START \ { \ g_autofree char *_error_string = NULL; \ g_autoptr (BzBackendNotification) _notif = NULL; \ \ _error_string = g_strdup_printf (__VA_ARGS__); \ \ _notif = bz_backend_notification_new (); \ bz_backend_notification_set_kind (_notif, BZ_BACKEND_NOTIFICATION_KIND_ERROR); \ bz_backend_notification_set_error (_notif, _error_string); \ send_notif_all ((_self), _notif, (_lock)); \ \ return dex_future_new_for_error ( \ g_error_new_literal (BZ_FLATPAK_ERROR, \ (_error), \ _error_string)); \ } \ G_STMT_END BZ_DEFINE_DATA ( wait_notif, WaitNotif, { GWeakRef self; DexChannel *channel; BzBackendNotification *notif; }, g_weak_ref_clear (&self->self); BZ_RELEASE_DATA (channel, dex_unref); BZ_RELEASE_DATA (notif, g_object_unref)); static DexFuture * wait_notif_finally (DexFuture *future, WaitNotifData *data); static gint cmp_rref (FlatpakRemoteRef *a, FlatpakRemoteRef *b, GHashTable *hash); static AsComponent * parse_component_for_node (XbNode *node, GError **error); static GBytes * decompress_appstream_gz (GBytes *appstream_gz, GCancellable *cancellable, GError **error); static XbSilo * build_silo (XbBuilderSource *source, GCancellable *cancellable, GError **error); static AsComponent * extract_first_component_for_silo (XbSilo *silo, GError **error); static void bz_flatpak_instance_dispose (GObject *object) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (object); dex_clear (&self->scheduler); g_clear_object (&self->system); g_clear_object (&self->system_interactive); g_clear_object (&self->system_events); g_clear_object (&self->user); g_clear_object (&self->user_interactive); g_clear_object (&self->user_events); g_mutex_clear (&self->mute_mutex); g_clear_pointer (&self->notif_channels, g_ptr_array_unref); dex_clear (&self->notif_send); g_mutex_clear (&self->notif_mutex); g_clear_pointer (&self->ongoing_cancellables, g_hash_table_unref); g_mutex_clear (&self->transactions_mutex); G_OBJECT_CLASS (bz_flatpak_instance_parent_class)->dispose (object); } static void bz_flatpak_instance_class_init (BzFlatpakInstanceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = bz_flatpak_instance_dispose; } static void bz_flatpak_instance_init (BzFlatpakInstance *self) { self->scheduler = dex_thread_pool_scheduler_new (); self->system_mute = 0; self->user_mute = 0; g_mutex_init (&self->mute_mutex); self->notif_channels = g_ptr_array_new_with_free_func (dex_unref); g_mutex_init (&self->notif_mutex); self->ongoing_cancellables = g_hash_table_new_full ( g_direct_hash, g_direct_equal, g_object_unref, (GDestroyNotify) g_ptr_array_unref); g_mutex_init (&self->transactions_mutex); } static DexChannel * bz_flatpak_instance_create_notification_channel (BzBackend *backend) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (DexChannel) channel = NULL; channel = dex_channel_new (0); g_mutex_lock (&self->notif_mutex); g_ptr_array_add (self->notif_channels, dex_ref (channel)); g_mutex_unlock (&self->notif_mutex); return g_steal_pointer (&channel); } static DexFuture * bz_flatpak_instance_load_local_package (BzBackend *backend, GFile *file, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (LoadLocalRefData) data = NULL; data = load_local_ref_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); data->file = g_object_ref (file); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) load_local_ref_fiber, load_local_ref_data_ref (data), load_local_ref_data_unref); } static DexFuture * bz_flatpak_instance_retrieve_remote_refs (BzBackend *backend, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (GatherRefsData) data = NULL; data = gather_refs_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); data->total = 0; return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) retrieve_remote_refs_fiber, gather_refs_data_ref (data), gather_refs_data_unref); } static DexFuture * bz_flatpak_instance_retrieve_install_ids (BzBackend *backend, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (GatherRefsData) data = NULL; data = gather_refs_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) retrieve_installs_fiber, gather_refs_data_ref (data), gather_refs_data_unref); } static DexFuture * bz_flatpak_instance_retrieve_update_ids (BzBackend *backend, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (GatherRefsData) data = NULL; data = gather_refs_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) retrieve_updates_fiber, gather_refs_data_ref (data), gather_refs_data_unref); } static DexFuture * bz_flatpak_instance_list_repositories (BzBackend *backend, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (ListReposData) data = NULL; data = list_repos_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) list_repositories_fiber, list_repos_data_ref (data), list_repos_data_unref); } static DexFuture * bz_flatpak_instance_schedule_transaction (BzBackend *backend, BzEntry **installs, guint n_installs, BzEntry **updates, guint n_updates, BzEntry **removals, guint n_removals, DexChannel *channel, GCancellable *cancellable) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); BzFlatpakEntry **installs_dup = NULL; BzFlatpakEntry **updates_dup = NULL; BzFlatpakEntry **removals_dup = NULL; g_autoptr (TransactionData) data = NULL; for (guint i = 0; i < n_installs; i++) dex_return_error_if_fail (BZ_IS_FLATPAK_ENTRY (installs[i])); for (guint i = 0; i < n_updates; i++) dex_return_error_if_fail (BZ_IS_FLATPAK_ENTRY (updates[i])); for (guint i = 0; i < n_removals; i++) dex_return_error_if_fail (BZ_IS_FLATPAK_ENTRY (removals[i])); if (n_installs > 0) { installs_dup = g_malloc0_n (n_installs, sizeof (*installs_dup)); for (guint i = 0; i < n_installs; i++) installs_dup[i] = g_object_ref (BZ_FLATPAK_ENTRY (installs[i])); } if (n_updates > 0) { updates_dup = g_malloc0_n (n_updates, sizeof (*updates_dup)); for (guint i = 0; i < n_updates; i++) updates_dup[i] = g_object_ref (BZ_FLATPAK_ENTRY (updates[i])); } if (n_removals > 0) { removals_dup = g_malloc0_n (n_removals, sizeof (*removals_dup)); for (guint i = 0; i < n_removals; i++) removals_dup[i] = g_object_ref (BZ_FLATPAK_ENTRY (removals[i])); } data = transaction_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); data->installs = installs_dup != NULL ? g_ptr_array_new_take ((gpointer *) installs_dup, n_installs, g_object_unref) : NULL; data->updates = updates_dup != NULL ? g_ptr_array_new_take ((gpointer *) updates_dup, n_updates, g_object_unref) : NULL; data->removals = removals_dup != NULL ? g_ptr_array_new_take ((gpointer *) removals_dup, n_removals, g_object_unref) : NULL; data->channel = bz_dex_maybe_ref (channel); data->send_futures = g_ptr_array_new_with_free_func (dex_unref); data->ref_to_entry_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); data->op_to_progress_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); data->rebased_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_mutex_init (&data->mutex); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) transaction_fiber, transaction_data_ref (data), transaction_data_unref); } static gboolean bz_flatpak_instance_cancel_task_for_entry (BzBackend *backend, BzEntry *entry) { BzFlatpakInstance *self = BZ_FLATPAK_INSTANCE (backend); g_autoptr (GMutexLocker) locker = NULL; GPtrArray *cancellables = NULL; locker = g_mutex_locker_new (&self->transactions_mutex); cancellables = g_hash_table_lookup (self->ongoing_cancellables, entry); if (cancellables == NULL) return FALSE; for (guint i = 0; i < cancellables->len; i++) { GCancellable *cancellable = NULL; cancellable = g_ptr_array_index (cancellables, i); g_cancellable_cancel (cancellable); } return TRUE; } static void backend_iface_init (BzBackendInterface *iface) { iface->create_notification_channel = bz_flatpak_instance_create_notification_channel; iface->load_local_package = bz_flatpak_instance_load_local_package; iface->retrieve_remote_entries = bz_flatpak_instance_retrieve_remote_refs; iface->retrieve_install_ids = bz_flatpak_instance_retrieve_install_ids; iface->retrieve_update_ids = bz_flatpak_instance_retrieve_update_ids; iface->list_repositories = bz_flatpak_instance_list_repositories; iface->schedule_transaction = bz_flatpak_instance_schedule_transaction; iface->cancel_task_for_entry = bz_flatpak_instance_cancel_task_for_entry; } FlatpakInstallation * bz_flatpak_instance_get_system_installation (BzFlatpakInstance *self) { g_return_val_if_fail (BZ_IS_FLATPAK_INSTANCE (self), NULL); return self->system; } FlatpakInstallation * bz_flatpak_instance_get_user_installation (BzFlatpakInstance *self) { g_return_val_if_fail (BZ_IS_FLATPAK_INSTANCE (self), NULL); return self->user; } DexFuture * bz_flatpak_instance_new (void) { g_autoptr (InitData) data = NULL; data = init_data_new (); data->self = g_object_new (BZ_TYPE_FLATPAK_INSTANCE, NULL); return dex_scheduler_spawn ( data->self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) init_fiber, init_data_ref (data), init_data_unref); } DexFuture * bz_flatpak_instance_has_flathub (BzFlatpakInstance *self, GCancellable *cancellable) { g_autoptr (CheckHasFlathubData) data = NULL; dex_return_error_if_fail (BZ_IS_FLATPAK_INSTANCE (self)); dex_return_error_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); data = check_has_flathub_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) check_has_flathub_fiber, check_has_flathub_data_ref (data), check_has_flathub_data_unref); } DexFuture * bz_flatpak_instance_ensure_has_flathub (BzFlatpakInstance *self, GCancellable *cancellable) { g_autoptr (EnsureFlathubData) data = NULL; dex_return_error_if_fail (BZ_IS_FLATPAK_INSTANCE (self)); dex_return_error_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); data = ensure_flathub_data_new (); data->self = bz_track_weak (self); data->cancellable = bz_object_maybe_ref (cancellable); return dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) ensure_flathub_fiber, ensure_flathub_data_ref (data), ensure_flathub_data_unref); } static DexFuture * init_fiber (InitData *data) { BzFlatpakInstance *self = data->self; g_autoptr (GError) local_error = NULL; g_autofree char *main_cache = NULL; bz_discard_module_dir (); self->system = flatpak_installation_new_system (NULL, &local_error); if (self->system != NULL) { g_autoptr (GFile) path = NULL; flatpak_installation_set_no_interaction (self->system, TRUE); path = flatpak_installation_get_path (self->system); self->system_interactive = flatpak_installation_new_for_path ( path, FALSE, NULL, NULL); g_assert (self->system_interactive != NULL); flatpak_installation_set_no_interaction (self->system_interactive, FALSE); self->system_events = flatpak_installation_create_monitor ( self->system, NULL, &local_error); if (self->system_events != NULL) g_signal_connect_swapped ( self->system_events, "changed", G_CALLBACK (installation_event), self); else { g_warning ("Failed to initialize event watch for system installation: %s", local_error->message); g_clear_pointer (&local_error, g_error_free); } } else { g_warning ("Failed to initialize system installation: %s", local_error->message); g_clear_pointer (&local_error, g_error_free); } #ifdef SANDBOXED_LIBFLATPAK { g_autoptr (GFile) user_installation_path = NULL; const char *home = g_get_home_dir (); g_autofree char *user_flatpak_path = g_build_filename (home, ".local", "share", "flatpak", NULL); user_installation_path = g_file_new_for_path (user_flatpak_path); self->user = flatpak_installation_new_for_path ( user_installation_path, TRUE, NULL, &local_error); } #else self->user = flatpak_installation_new_user (NULL, &local_error); #endif if (self->user != NULL) { g_autoptr (GFile) path = NULL; flatpak_installation_set_no_interaction (self->user, TRUE); path = flatpak_installation_get_path (self->user); self->user_interactive = flatpak_installation_new_for_path ( path, TRUE, NULL, NULL); g_assert (self->user_interactive != NULL); flatpak_installation_set_no_interaction (self->user_interactive, FALSE); self->user_events = flatpak_installation_create_monitor ( self->user, NULL, &local_error); if (self->user_events != NULL) g_signal_connect_swapped ( self->user_events, "changed", G_CALLBACK (installation_event), self); else { g_warning ("Failed to initialize event watch for user installation: %s", local_error->message); g_clear_pointer (&local_error, g_error_free); } } else { g_warning ("Failed to initialize user installation: %s", local_error->message); g_clear_pointer (&local_error, g_error_free); } #ifdef SANDBOXED_LIBFLATPAK if (g_getenv ("FLATPAK_BINARY") == NULL) { g_autofree char *flatpak_path = NULL; gint exit_status = 0; g_spawn_command_line_sync ("flatpak-spawn --host sh -c 'command -v flatpak'", &flatpak_path, NULL, &exit_status, NULL); if (exit_status == 0 && flatpak_path != NULL) g_setenv ("FLATPAK_BINARY", g_strstrip (flatpak_path), FALSE); else g_warning ("Failed to resolve host flatpak binary! User refs wont be updated"); } #endif if (self->system == NULL && self->user == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to initialize any flatpak installations"); return dex_future_new_for_object (self); } static DexFuture * check_has_flathub_fiber (CheckHasFlathubData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) system_remotes = NULL; guint n_system_remotes = 0; g_autoptr (GPtrArray) user_remotes = NULL; guint n_user_remotes = 0; bz_weak_get_or_return_reject (self, data->self); if (self->system != NULL) { system_remotes = flatpak_installation_list_remotes ( self->system, cancellable, &local_error); if (system_remotes == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for system installation: %s", local_error->message); n_system_remotes = system_remotes->len; } // Downloading from user remotes in the Flatpak is unsupported. #ifndef SANDBOXED_LIBFLATPAK if (self->user != NULL) { user_remotes = flatpak_installation_list_remotes ( self->user, cancellable, &local_error); if (user_remotes == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for user installation: %s", local_error->message); n_user_remotes = user_remotes->len; } #endif for (guint i = 0; i < n_system_remotes + n_user_remotes; i++) { FlatpakRemote *remote = NULL; const char *name = NULL; if (i < n_system_remotes) remote = g_ptr_array_index (system_remotes, i); else remote = g_ptr_array_index (user_remotes, i - n_system_remotes); if (flatpak_remote_get_disabled (remote) || flatpak_remote_get_noenumerate (remote)) continue; name = flatpak_remote_get_name (remote); if (g_strcmp0 (name, "flathub") == 0) return dex_future_new_true (); } return dex_future_new_false (); } static DexFuture * ensure_flathub_fiber (EnsureFlathubData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; g_autoptr (GError) local_error = NULL; g_autoptr (FlatpakRemote) remote = NULL; FlatpakInstallation *installation = NULL; gboolean result = FALSE; bz_weak_get_or_return_reject (self, data->self); #define REPO_URL "https://dl.flathub.org/repo/flathub.flatpakrepo" #ifdef SANDBOXED_LIBFLATPAK if (self->system_interactive != NULL) { remote = flatpak_installation_get_remote_by_name ( self->system_interactive, "flathub", cancellable, NULL); installation = self->system_interactive; } if (installation == NULL) return dex_future_new_true (); #else if (self->user != NULL) { remote = flatpak_installation_get_remote_by_name ( self->user, "flathub", cancellable, NULL); installation = self->user; } if (remote == NULL && self->system != NULL) { remote = flatpak_installation_get_remote_by_name ( self->system, "flathub", cancellable, NULL); if (remote != NULL) installation = self->system; } #endif if (remote != NULL) { flatpak_remote_set_disabled (remote, FALSE); flatpak_remote_set_noenumerate (remote, FALSE); flatpak_remote_set_gpg_verify (remote, TRUE); result = flatpak_installation_modify_remote ( installation, remote, cancellable, &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to modify existing system flathub remote: %s", local_error->message); } else { g_autoptr (SoupMessage) message = NULL; g_autoptr (GOutputStream) output = NULL; g_autoptr (GBytes) bytes = NULL; g_autoptr (FlatpakRemote) imported = NULL; message = soup_message_new (SOUP_METHOD_GET, REPO_URL); output = g_memory_output_stream_new_resizable (); result = dex_await ( bz_send_with_global_http_session_then_splice_into (message, output), &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to retrieve flatpakrepo file from %s: %s", REPO_URL, local_error->message); bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output)); imported = flatpak_remote_new_from_file ("flathub", bytes, &local_error); if (imported == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to construct flatpak remote from flatpakrepo file %s: %s", REPO_URL, local_error->message); flatpak_remote_set_gpg_verify (imported, TRUE); result = flatpak_installation_add_remote ( installation, imported, TRUE, cancellable, &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to add flathub to flatpak installation: %s", local_error->message); } return dex_future_new_true (); } DexFuture * bz_flatpak_repo_new_from_url (const char *url, GCancellable *cancellable) { g_autoptr (SoupMessage) message = NULL; g_autoptr (GOutputStream) output = NULL; g_autoptr (GBytes) bytes = NULL; g_autoptr (GKeyFile) key_file = NULL; g_autoptr (BzFlatpakRepo) repo = NULL; g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_autofree char *name = NULL; char *dot = NULL; g_autofree char *title = NULL; g_autofree char *repo_url = NULL; g_autofree char *homepage = NULL; g_autofree char *comment = NULL; g_autofree char *description = NULL; g_autofree char *icon = NULL; g_autofree char *gpg_key = NULL; g_autofree char *default_branch = NULL; g_autofree char *filter = NULL; g_autoptr (GError) bool_error = NULL; gboolean gpg_verify = FALSE; name = g_path_get_basename (url); dot = strrchr (name, '.'); if (dot != NULL) *dot = '\0'; message = soup_message_new (SOUP_METHOD_GET, url); output = g_memory_output_stream_new_resizable (); result = dex_await ( bz_send_with_global_http_session_then_splice_into (message, output), &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to retrieve flatpakrepo file from %s: %s", url, local_error->message); bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output)); key_file = g_key_file_new (); result = g_key_file_load_from_bytes (key_file, bytes, G_KEY_FILE_NONE, &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to parse flatpakrepo file from %s: %s", url, local_error->message); title = g_key_file_get_string (key_file, "Flatpak Repo", "Title", NULL); repo_url = g_key_file_get_string (key_file, "Flatpak Repo", "Url", NULL); homepage = g_key_file_get_string (key_file, "Flatpak Repo", "Homepage", NULL); comment = g_key_file_get_string (key_file, "Flatpak Repo", "Comment", NULL); description = g_key_file_get_string (key_file, "Flatpak Repo", "Description", NULL); icon = g_key_file_get_string (key_file, "Flatpak Repo", "Icon", NULL); gpg_key = g_key_file_get_string (key_file, "Flatpak Repo", "GPGKey", NULL); default_branch = g_key_file_get_string (key_file, "Flatpak Repo", "DefaultBranch", NULL); filter = g_key_file_get_string (key_file, "Flatpak Repo", "Filter", NULL); gpg_verify = g_key_file_get_boolean (key_file, "Flatpak Repo", "GPGVerify", &bool_error); if (bool_error != NULL) { gpg_verify = gpg_key != NULL; g_clear_error (&bool_error); } repo = g_object_new ( BZ_TYPE_FLATPAK_REPO, "name", name, "title", title, "url", repo_url, "homepage", homepage, "comment", comment, "description", description, "icon", icon, "gpg-key", gpg_key, "default-branch", default_branch, "filter", filter, "gpg-verify", gpg_verify, NULL); return dex_future_new_for_object (repo); } static DexFuture * load_local_ref_fiber (LoadLocalRefData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GFile *file = data->file; GCancellable *cancellable = data->cancellable; gboolean result = FALSE; g_autoptr (GError) local_error = NULL; g_autofree char *uri = NULL; g_autofree char *path = NULL; g_autofree char *runtime_repo_url = NULL; bz_weak_get_or_return_reject (self, data->self); uri = g_file_get_uri (file); path = g_file_get_path (file); if (uri == NULL) uri = g_strdup_printf ("file://%s", path); if (g_str_has_suffix (uri, ".flatpakref")) { const char *resolved_uri = NULL; g_autoptr (GKeyFile) key_file = NULL; g_autofree char *name = NULL; if (g_str_has_prefix (uri, "flatpak+https")) resolved_uri = uri + strlen ("flatpak+"); else resolved_uri = uri; key_file = g_key_file_new (); if (g_str_has_prefix (resolved_uri, "http")) { g_autoptr (SoupMessage) message = NULL; g_autoptr (GOutputStream) output = NULL; g_autoptr (GBytes) bytes = NULL; message = soup_message_new (SOUP_METHOD_GET, resolved_uri); output = g_memory_output_stream_new_resizable (); result = dex_await ( bz_send_with_global_http_session_then_splice_into (message, output), &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to retrieve flatpakref file from %s: %s", resolved_uri, local_error->message); bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output)); result = g_key_file_load_from_bytes (key_file, bytes, G_KEY_FILE_NONE, &local_error); } else if (path != NULL) result = g_key_file_load_from_file ( key_file, path, G_KEY_FILE_NONE, &local_error); else local_error = g_error_new ( G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Cannot handle URIs of this type"); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to load flatpakref '%s' into a key file: %s", uri, local_error->message); name = g_key_file_get_string (key_file, "Flatpak Ref", "Name", &local_error); if (name == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to load locate \"Name\" key in flatpakref '%s': %s", uri, local_error->message); return dex_future_new_take_string (g_steal_pointer (&name)); } else /* This is a bundle ref */ { g_autoptr (FlatpakBundleRef) bref = NULL; const char *name = NULL; const char *origin = NULL; FlatpakInstallation *add_to_installation = NULL; g_autoptr (FlatpakRemote) remote = NULL; g_autoptr (BzFlatpakEntry) entry = NULL; g_autoptr (BzFlatpakRepo) runtime_repo = NULL; g_autoptr (GBytes) appstream_gz = NULL; g_autoptr (GBytes) appstream = NULL; g_autoptr (AsComponent) component = NULL; g_autoptr (BzFlatpakBundleResult) bundle_result = NULL; if (path == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Cannot load '%s' as a flatpak bundle: URI is not a local file", uri); bref = flatpak_bundle_ref_new (file, &local_error); if (bref == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to load local flatpak bundle '%s': %s", path, local_error->message); name = flatpak_ref_get_name (FLATPAK_REF (bref)); origin = flatpak_bundle_ref_get_origin (bref); runtime_repo_url = flatpak_bundle_ref_get_runtime_repo_url (bref); if (runtime_repo_url != NULL) { g_autoptr (GError) repo_error = NULL; runtime_repo = dex_await_object ( bz_flatpak_repo_new_from_url (runtime_repo_url, cancellable), &repo_error); if (runtime_repo == NULL) g_warning ("Failed to parse runtime repo from %s: %s", runtime_repo_url, repo_error->message); } if (self->system != NULL) add_to_installation = self->system; else if (self->user != NULL) add_to_installation = self->user; /* First check if we already should have the origin remote installed */ if (self->system != NULL) { g_autoptr (GPtrArray) remotes = NULL; remotes = flatpak_installation_list_remotes ( self->system, NULL, NULL); if (remotes != NULL) { for (guint i = 0; i < remotes->len; i++) { FlatpakRemote *existing = NULL; const char *url = NULL; existing = g_ptr_array_index (remotes, i); url = flatpak_remote_get_url (existing); if (url != NULL && g_strcmp0 (url, origin) == 0) { remote = g_object_ref (existing); add_to_installation = NULL; break; } } } } if (self->user != NULL) { g_autoptr (GPtrArray) remotes = NULL; remotes = flatpak_installation_list_remotes ( self->user, NULL, NULL); if (remotes != NULL) { for (guint i = 0; i < remotes->len; i++) { FlatpakRemote *existing = NULL; const char *url = NULL; existing = g_ptr_array_index (remotes, i); url = flatpak_remote_get_url (existing); if (url != NULL && g_strcmp0 (url, origin) == 0) { remote = g_object_ref (existing); add_to_installation = NULL; break; } } } } if (add_to_installation != NULL) { g_autoptr (FlatpakRemote) config_remote = NULL; g_autofree char *remote_name = NULL; /* Configure and sync the new remote */ remote_name = g_strdup_printf ("%s-bazaar-origin", name); config_remote = flatpak_remote_new (remote_name); flatpak_remote_set_url (config_remote, origin); flatpak_remote_set_disabled (config_remote, FALSE); flatpak_remote_set_noenumerate (config_remote, FALSE); flatpak_remote_set_gpg_verify (config_remote, TRUE); result = flatpak_installation_add_remote ( add_to_installation, config_remote, FALSE, cancellable, NULL); if (result) { { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES); send_notif_all (self, notif, TRUE); } remote = flatpak_installation_get_remote_by_name ( add_to_installation, remote_name, cancellable, NULL); if (remote != NULL) { { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START); bz_backend_notification_set_remote_name (notif, remote_name); send_notif_all (self, notif, TRUE); } result = dex_await ( retrieve_refs_for_enumerable_remote ( self, cancellable, remote_name, add_to_installation, remote), NULL); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH); bz_backend_notification_set_remote_name (notif, remote_name); send_notif_all (self, notif, TRUE); } } } } appstream_gz = flatpak_bundle_ref_get_appstream (bref); if (appstream_gz != NULL) { appstream = decompress_appstream_gz (appstream_gz, NULL, &local_error); if (appstream == NULL) { g_warning ("Failed to decompress AppStream data: %s", local_error->message); g_clear_error (&local_error); } else { g_autoptr (XbBuilderSource) source = NULL; source = xb_builder_source_new (); if (!xb_builder_source_load_bytes (source, appstream, XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, &local_error)) { g_warning ("Failed to load AppStream bytes into xmlb: %s", local_error->message); g_clear_error (&local_error); } else { g_autoptr (XbSilo) silo = NULL; silo = build_silo (source, NULL, &local_error); if (silo == NULL) { g_warning ("Failed to compile xmlb silo: %s", local_error->message); g_clear_error (&local_error); } else { component = extract_first_component_for_silo (silo, &local_error); if (component == NULL && local_error != NULL) { g_warning ("Failed to parse component: %s", local_error->message); g_clear_error (&local_error); } } } } } entry = bz_flatpak_entry_new_for_ref ( FLATPAK_REF (bref), remote, FALSE, component, NULL, &local_error); if (entry == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to parse information from flatpak bundle '%s': %s", path, local_error->message); bundle_result = g_object_new (BZ_TYPE_FLATPAK_BUNDLE_RESULT, "entry", entry, "runtime-repo", runtime_repo, NULL); return dex_future_new_for_object (bundle_result); } } static DexFuture * retrieve_remote_refs_fiber (GatherRefsData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) system_remotes = NULL; guint n_system_remotes = 0; g_autoptr (GPtrArray) user_remotes = NULL; guint n_user_remotes = 0; g_autoptr (GHashTable) blocked_names_hash = NULL; g_autoptr (GPtrArray) jobs = NULL; g_autoptr (GPtrArray) job_names = NULL; g_autoptr (DexFuture) future = NULL; gboolean result = FALSE; g_autoptr (GString) error_string = NULL; bz_weak_get_or_return_reject (self, data->self); if (self->system != NULL) { system_remotes = flatpak_installation_list_remotes ( self->system, cancellable, &local_error); if (system_remotes == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for system installation: %s", local_error->message); n_system_remotes = system_remotes->len; } if (self->user != NULL) { user_remotes = flatpak_installation_list_remotes ( self->user, cancellable, &local_error); if (user_remotes == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for user installation: %s", local_error->message); n_user_remotes = user_remotes->len; } if (n_user_remotes + n_system_remotes == 0) return dex_future_new_true (); jobs = g_ptr_array_new_with_free_func (dex_unref); job_names = g_ptr_array_new_with_free_func (g_free); for (guint i = 0; i < n_system_remotes + n_user_remotes; i++) { FlatpakInstallation *installation = NULL; FlatpakRemote *remote = NULL; const char *name = NULL; g_autoptr (RetrieveRefsForRemoteData) job_data = NULL; g_autoptr (DexFuture) job_future = NULL; if (i < n_system_remotes) { installation = self->system; remote = g_ptr_array_index (system_remotes, i); } else { installation = self->user; remote = g_ptr_array_index (user_remotes, i - n_system_remotes); } name = flatpak_remote_get_name (remote); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START); bz_backend_notification_set_remote_name (notif, name); send_notif_all (self, notif, TRUE); } job_data = retrieve_refs_for_remote_data_new (); job_data->parent = gather_refs_data_ref (data); job_data->installation = g_object_ref (installation); job_data->remote = g_object_ref (remote); job_future = dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) retrieve_refs_for_remote_fiber, retrieve_refs_for_remote_data_ref (job_data), retrieve_refs_for_remote_data_unref); g_ptr_array_add (jobs, g_steal_pointer (&job_future)); g_ptr_array_add (job_names, g_strdup (name)); } if (jobs->len == 0) return dex_future_new_true (); result = dex_await (dex_future_allv ( (DexFuture *const *) jobs->pdata, jobs->len), NULL); if (!result) error_string = g_string_new ("No remotes could be synchronized:\n\n"); for (guint i = 0; i < jobs->len; i++) { DexFuture *job_future = NULL; char *name = NULL; job_future = g_ptr_array_index (jobs, i); name = g_ptr_array_index (job_names, i); dex_future_get_value (job_future, &local_error); if (local_error != NULL) { if (error_string == NULL) error_string = g_string_new ("Some remotes couldn't be fully sychronized:\n"); g_string_append_printf (error_string, "\n%s failed because: %s\n", name, local_error->message); } g_clear_pointer (&local_error, g_error_free); } if (result) { if (error_string != NULL) return dex_future_new_take_string ( g_string_free_and_steal (g_steal_pointer (&error_string))); else return dex_future_new_true (); } else return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "%s", error_string->str); } static DexFuture * retrieve_refs_for_enumerable_remote (BzFlatpakInstance *self, GCancellable *cancellable, const char *remote_name, FlatpakInstallation *installation, FlatpakRemote *remote) { g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_autoptr (GFile) appstream_dir = NULL; g_autofree char *appstream_dir_path = NULL; g_autofree char *appstream_xml_path = NULL; g_autoptr (GFile) appstream_xml = NULL; g_autoptr (XbBuilderSource) source = NULL; g_autoptr (XbSilo) silo = NULL; g_autoptr (XbNode) root = NULL; g_autoptr (GPtrArray) children = NULL; g_autoptr (GHashTable) component_hash = NULL; g_autoptr (GPtrArray) refs = NULL; g_debug ("Remote '%s' is enumerable, listing all remote refs", remote_name); result = flatpak_installation_update_remote_sync ( installation, remote_name, cancellable, &local_error); if (!result) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to synchronize remote '%s': %s", remote_name, local_error->message); result = flatpak_installation_update_appstream_full_sync ( installation, remote_name, NULL, NULL, NULL, NULL, cancellable, &local_error); if (!result) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to synchronize appstream data for remote '%s': %s", remote_name, local_error->message); appstream_dir = flatpak_remote_get_appstream_dir (remote, NULL); if (appstream_dir == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to locate appstream directory for remote '%s': %s", remote_name, local_error->message); appstream_dir_path = g_file_get_path (appstream_dir); appstream_xml_path = g_build_filename (appstream_dir_path, "appstream.xml.gz", NULL); if (!g_file_test (appstream_xml_path, G_FILE_TEST_EXISTS)) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to verify existence of appstream " "bundle download at path %s for remote '%s'", appstream_xml_path, remote_name); appstream_xml = g_file_new_for_path (appstream_xml_path); source = xb_builder_source_new (); result = xb_builder_source_load_file ( source, appstream_xml, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, cancellable, &local_error); if (!result) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to load binary xml from appstream bundle " "download at path %s for remote '%s': %s", appstream_xml_path, remote_name, local_error->message); silo = build_silo (source, cancellable, &local_error); #ifdef __GLIBC__ /* From gnome-software/plugins/core/gs-plugin-appstream.c * * 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 (silo == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to compile binary xml silo from appstream bundle " "download at path %s for remote '%s': %s", appstream_xml_path, remote_name, local_error->message); root = xb_silo_get_root (silo); children = xb_node_get_children (root); component_hash = g_hash_table_new (g_str_hash, g_str_equal); for (guint i = 0; i < children->len; i++) { XbNode *component_node = NULL; AsComponent *component = NULL; const char *id = NULL; component_node = g_ptr_array_index (children, i); component = parse_component_for_node (component_node, &local_error); if (component == NULL) { SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_APPSTREAM_FAILURE, "Failed to parse appstream component from appstream bundle silo " "originating from download at path %s for remote '%s': %s", appstream_xml_path, remote_name, local_error->message); } id = as_component_get_id (component); g_hash_table_replace (component_hash, (gpointer) id, component); } refs = flatpak_installation_list_remote_refs_sync ( installation, remote_name, cancellable, &local_error); if (refs == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to enumerate refs for remote '%s': %s", remote_name, local_error->message); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); bz_backend_notification_set_n_incoming (notif, refs->len); send_notif_all (self, notif, TRUE); } /* Ensure the receiving side of the channel gets * runtimes first, then addons, then applications */ g_ptr_array_sort_values_with_data ( refs, (GCompareDataFunc) cmp_rref, component_hash); for (guint i = 0; i < refs->len; i++) { FlatpakRemoteRef *rref = NULL; const char *name = NULL; AsComponent *component = NULL; g_autoptr (BzFlatpakEntry) entry = NULL; rref = g_ptr_array_index (refs, i); name = flatpak_ref_get_name (FLATPAK_REF (rref)); component = g_hash_table_lookup (component_hash, name); if (component == NULL) { g_autofree char *desktop_id = NULL; desktop_id = g_strdup_printf ("%s.desktop", name); component = g_hash_table_lookup (component_hash, desktop_id); } entry = bz_flatpak_entry_new_for_ref ( FLATPAK_REF (rref), remote, installation == self->user, component, appstream_dir_path, NULL); if (entry != NULL) { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY); bz_backend_notification_set_entry (notif, BZ_ENTRY (entry)); send_notif_all (self, notif, TRUE); } else { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); bz_backend_notification_set_n_incoming (notif, -1); send_notif_all (self, notif, TRUE); } } return dex_future_new_true (); } static DexFuture * retrieve_refs_for_noenumerable_remote (BzFlatpakInstance *self, GCancellable *cancellable, const char *remote_name, FlatpakInstallation *installation, FlatpakRemote *remote) { g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) installed_apps = NULL; guint matched = 0; installed_apps = flatpak_installation_list_installed_refs_by_kind ( installation, FLATPAK_REF_KIND_APP, cancellable, &local_error); if (installed_apps == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_LOCAL_SYNCHRONIZATION_FAILURE, "Failed to enumerate installed apps for non-enumerable remote '%s': %s", remote_name, local_error->message); g_debug ("Found %u total installed apps, filtering for remote '%s'", installed_apps->len, remote_name); for (guint i = 0; i < installed_apps->len; i++) { FlatpakInstalledRef *iref = NULL; const char *ref_origin = NULL; g_autoptr (AsComponent) component = NULL; g_autoptr (BzFlatpakEntry) entry = NULL; g_autoptr (GBytes) appstream_gz = NULL; iref = g_ptr_array_index (installed_apps, i); ref_origin = flatpak_installed_ref_get_origin (iref); if (g_strcmp0 (ref_origin, remote_name) != 0) continue; matched++; appstream_gz = flatpak_installed_ref_load_appdata (iref, cancellable, NULL); if (appstream_gz != NULL) { g_autoptr (GBytes) appstream = NULL; g_autoptr (XbBuilderSource) source = NULL; g_autoptr (XbSilo) silo = NULL; g_autoptr (GError) appstream_error = NULL; appstream = decompress_appstream_gz (appstream_gz, cancellable, &appstream_error); if (appstream == NULL) { g_info ("Could not decompress appstream for installed ref: %s", appstream_error ? appstream_error->message : "unknown error"); goto create_entry; } source = xb_builder_source_new (); if (!xb_builder_source_load_bytes (source, appstream, XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, &appstream_error)) { g_info ("Could not load appstream bytes: %s", appstream_error ? appstream_error->message : "unknown error"); goto create_entry; } silo = build_silo (source, cancellable, &appstream_error); if (silo == NULL) { g_info ("Could not build silo from appstream: %s", appstream_error ? appstream_error->message : "unknown error"); goto create_entry; } component = extract_first_component_for_silo (silo, &appstream_error); if (component == NULL) { g_info ("Could not parse appstream component: %s", appstream_error ? appstream_error->message : "unknown error"); } } create_entry: entry = bz_flatpak_entry_new_for_ref ( FLATPAK_REF (iref), remote, installation == self->user, component, NULL, NULL); if (entry != NULL) { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY); bz_backend_notification_set_entry (notif, BZ_ENTRY (entry)); send_notif_all (self, notif, TRUE); } } g_debug ("Found %u installed apps from non-enumerable remote '%s'", matched, remote_name); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); bz_backend_notification_set_n_incoming (notif, matched); send_notif_all (self, notif, TRUE); } return dex_future_new_true (); } static DexFuture * retrieve_refs_for_remote_fiber (RetrieveRefsForRemoteData *data) { FlatpakInstallation *installation = data->installation; FlatpakRemote *remote = data->remote; g_autoptr (BzFlatpakInstance) self = NULL; const char *remote_name = NULL; gboolean is_noenumerate = FALSE; gboolean is_whitelisted_user = FALSE; g_autoptr (DexFuture) ret = NULL; bz_weak_get_or_return_reject (self, data->parent->self); remote_name = flatpak_remote_get_name (remote); is_noenumerate = flatpak_remote_get_noenumerate (remote); /* the fedora flatpak repos cause too many issues */ if (strstr (remote_name, "fedora") != NULL) is_noenumerate = TRUE; /* Allowing this makes it possible for COSMIC users to install * their applets as no system remote is available for that * this remote also doesn't use extra data. */ is_whitelisted_user = g_strcmp0 (remote_name, COSMIC_APPLET_REMOTE_NAME) == 0; #ifdef SANDBOXED_LIBFLATPAK if (is_noenumerate || (installation == self->user && !is_whitelisted_user)) #else if (is_noenumerate) #endif ret = retrieve_refs_for_noenumerable_remote ( self, data->parent->cancellable, remote_name, installation, remote); else ret = retrieve_refs_for_enumerable_remote ( self, data->parent->cancellable, remote_name, installation, remote); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH); bz_backend_notification_set_remote_name (notif, remote_name); send_notif_all (self, notif, TRUE); } return g_steal_pointer (&ret); } static DexFuture * retrieve_installs_fiber (GatherRefsData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) system_refs = NULL; guint n_system_refs = 0; g_autoptr (GPtrArray) user_refs = NULL; guint n_user_refs = 0; g_autoptr (GHashTable) ids = NULL; bz_weak_get_or_return_reject (self, data->self); if (self->system != NULL) { flatpak_installation_drop_caches ( self->system, cancellable, NULL); system_refs = flatpak_installation_list_installed_refs ( self->system, cancellable, &local_error); if (system_refs == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_LOCAL_SYNCHRONIZATION_FAILURE, "Failed to discover installed refs for system installation: %s", local_error->message); n_system_refs = system_refs->len; } if (self->user != NULL) { flatpak_installation_drop_caches ( self->user, cancellable, NULL); user_refs = flatpak_installation_list_installed_refs ( self->user, cancellable, &local_error); if (user_refs == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_LOCAL_SYNCHRONIZATION_FAILURE, "Failed to discover installed refs for user installation: %s", local_error->message); n_user_refs = user_refs->len; } ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < n_system_refs + n_user_refs; i++) { gboolean user = FALSE; FlatpakInstalledRef *iref = NULL; const char *version = NULL; g_autofree char *unique_id = NULL; if (i < n_system_refs) { user = FALSE; iref = g_ptr_array_index (system_refs, i); } else { user = TRUE; iref = g_ptr_array_index (user_refs, i - n_system_refs); } version = flatpak_installed_ref_get_appdata_version (iref); unique_id = bz_flatpak_ref_format_unique (FLATPAK_REF (iref), user); g_hash_table_replace (ids, g_steal_pointer (&unique_id), g_strdup (version != NULL ? version : "")); } return dex_future_new_take_boxed ( G_TYPE_HASH_TABLE, g_steal_pointer (&ids)); } static gboolean should_skip_extension_ref (FlatpakInstalledRef *iref) { const gchar *ref_name = flatpak_ref_get_name (FLATPAK_REF (iref)); /* These get updated with their parents and look really bad in the UI */ return g_str_has_suffix (ref_name, ".Locale") || g_str_has_suffix (ref_name, ".Debug") || g_str_has_suffix (ref_name, ".Sources"); } static DexFuture * retrieve_updates_fiber (GatherRefsData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) system_refs = NULL; guint n_sys_refs = 0; g_autoptr (GPtrArray) user_refs = NULL; guint n_user_refs = 0; g_autoptr (GHashTable) ids = NULL; bz_weak_get_or_return_reject (self, data->self); if (self->system != NULL) { system_refs = flatpak_installation_list_installed_refs_for_update ( self->system, cancellable, &local_error); if (system_refs == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to discover update-elligible refs for system installation: %s", local_error->message); n_sys_refs = system_refs->len; } #ifndef SANDBOXED_LIBFLATPAK if (self->user != NULL) #else if (self->user != NULL && g_getenv ("FLATPAK_BINARY") != NULL) #endif { user_refs = flatpak_installation_list_installed_refs_for_update ( self->user, cancellable, &local_error); if (user_refs == NULL) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_REMOTE_SYNCHRONIZATION_FAILURE, "Failed to discover update-elligible refs for user installation: %s", local_error->message); n_user_refs = user_refs->len; } ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); for (guint i = 0; i < n_sys_refs + n_user_refs; i++) { gboolean user = FALSE; FlatpakInstalledRef *iref = NULL; FlatpakInstallation *installation = NULL; g_autofree char *unique_id = NULL; const char *origin = NULL; g_autoptr (GBytes) old_bytes = NULL; g_autoptr (GKeyFile) old_keyfile = NULL; g_autoptr (GBytes) new_bytes = NULL; g_autoptr (GKeyFile) new_keyfile = NULL; g_autoptr (BzAppPermissions) old_perm = NULL; g_autoptr (BzAppPermissions) new_perm = NULL; BzAppPermissions *delta = NULL; g_autoptr (GError) perm_error = NULL; if (i < n_sys_refs) { user = FALSE; iref = g_ptr_array_index (system_refs, i); installation = self->system; } else { user = TRUE; iref = g_ptr_array_index (user_refs, i - n_sys_refs); installation = self->user; } if (should_skip_extension_ref (iref)) continue; unique_id = bz_flatpak_ref_format_unique (FLATPAK_REF (iref), user); old_bytes = flatpak_installed_ref_load_metadata (iref, cancellable, &perm_error); if (old_bytes != NULL) { old_keyfile = g_key_file_new (); if (!g_key_file_load_from_bytes (old_keyfile, old_bytes, G_KEY_FILE_NONE, NULL)) g_clear_pointer (&old_keyfile, g_key_file_unref); } else { g_debug ("Failed to get installed metadata for '%s': %s", flatpak_ref_get_name (FLATPAK_REF (iref)), perm_error->message); } g_clear_error (&perm_error); origin = flatpak_installed_ref_get_origin (iref); if (old_keyfile != NULL && origin != NULL) { new_bytes = flatpak_installation_fetch_remote_metadata_sync ( installation, origin, FLATPAK_REF (iref), cancellable, &perm_error); if (new_bytes != NULL) { new_keyfile = g_key_file_new (); if (!g_key_file_load_from_bytes (new_keyfile, new_bytes, G_KEY_FILE_NONE, NULL)) g_clear_pointer (&new_keyfile, g_key_file_unref); } else { g_debug ("Failed to fetch remote metadata for '%s' from '%s': %s", flatpak_ref_get_name (FLATPAK_REF (iref)), origin, perm_error->message); } } g_clear_error (&perm_error); if (old_keyfile != NULL && new_keyfile != NULL) { old_perm = bz_app_permissions_new_from_metadata (old_keyfile, NULL); new_perm = bz_app_permissions_new_from_metadata (new_keyfile, NULL); } if (old_perm != NULL && new_perm != NULL) delta = bz_app_permissions_diff (old_perm, new_perm); else { delta = bz_app_permissions_new (); bz_app_permissions_seal (delta); } g_hash_table_replace (ids, g_steal_pointer (&unique_id), delta); } return dex_future_new_take_boxed ( G_TYPE_HASH_TABLE, g_steal_pointer (&ids)); } static gboolean append_repos_for_installation (GListStore *repos, FlatpakInstallation *installation, gboolean is_user, GCancellable *cancellable, GError **error) { g_autoptr (GPtrArray) remotes = NULL; remotes = flatpak_installation_list_remotes (installation, cancellable, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FlatpakRemote *remote = NULL; g_autoptr (BzRepository) repo = NULL; const char *name = NULL; const char *default_branch = NULL; gboolean is_beta = FALSE; remote = g_ptr_array_index (remotes, i); name = flatpak_remote_get_name (remote); default_branch = flatpak_remote_get_default_branch (remote); is_beta = (g_strcmp0 (name, "flathub-beta") == 0) || (name != NULL && g_str_has_suffix (name, "nightly")) || (g_strcmp0 (default_branch, "devel") == 0) || (g_strcmp0 (default_branch, "master") == 0) || (default_branch != NULL && g_str_has_suffix (default_branch, "beta")); repo = g_object_new (BZ_TYPE_REPOSITORY, "name", name, "title", flatpak_remote_get_title (remote), "url", flatpak_remote_get_url (remote), "is-user", is_user, "is-beta", is_beta, NULL); g_list_store_append (repos, repo); } return TRUE; } static DexFuture * list_repositories_fiber (ListReposData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = NULL; g_autoptr (GError) local_error = NULL; g_autoptr (GListStore) repos = NULL; cancellable = data->cancellable; bz_weak_get_or_return_reject (self, data->self); repos = g_list_store_new (BZ_TYPE_REPOSITORY); if (self->system != NULL && !append_repos_for_installation (repos, self->system, FALSE, cancellable, &local_error)) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for system installation: %s", local_error->message); if (self->user != NULL && !append_repos_for_installation (repos, self->user, TRUE, cancellable, &local_error)) SEND_AND_RETURN_ERROR ( self, TRUE, BZ_FLATPAK_ERROR_CANNOT_INITIALIZE, "Failed to enumerate remotes for user installation: %s", local_error->message); return dex_future_new_for_object (repos); } static DexFuture * ensure_runtime_remote_fiber (BzFlatpakInstance *self, FlatpakInstallation *installation, const char *bundle_path, GCancellable *cancellable) { g_autoptr (GError) local_error = NULL; g_autoptr (GFile) bundle_file = NULL; g_autoptr (FlatpakBundleRef) bundle_ref = NULL; g_autofree char *runtime_repo_url = NULL; g_autoptr (SoupMessage) message = NULL; g_autoptr (GOutputStream) output = NULL; g_autoptr (GBytes) bytes = NULL; g_autoptr (FlatpakRemote) remote = NULL; g_autofree char *remote_name = NULL; g_autoptr (GKeyFile) key_file = NULL; g_autofree char *gpg_key_base64 = NULL; g_autoptr (GBytes) gpg_key_bytes = NULL; bundle_file = g_file_new_for_path (bundle_path); bundle_ref = flatpak_bundle_ref_new (bundle_file, NULL); if (bundle_ref == NULL) return dex_future_new_true (); runtime_repo_url = flatpak_bundle_ref_get_runtime_repo_url (bundle_ref); if (runtime_repo_url == NULL) return dex_future_new_true (); remote_name = g_path_get_basename (runtime_repo_url); { char *dot = strrchr (remote_name, '.'); if (dot != NULL) *dot = '\0'; } { g_autoptr (FlatpakRemote) existing = NULL; existing = flatpak_installation_get_remote_by_name ( installation, remote_name, cancellable, NULL); if (existing != NULL) return dex_future_new_true (); } message = soup_message_new (SOUP_METHOD_GET, runtime_repo_url); output = g_memory_output_stream_new_resizable (); dex_await ( bz_send_with_global_http_session_then_splice_into (message, output), &local_error); if (local_error != NULL) { g_warning ("failed to fetch %s: %s", runtime_repo_url, local_error->message); return dex_future_new_true (); } bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output)); key_file = g_key_file_new (); if (!g_key_file_load_from_bytes (key_file, bytes, G_KEY_FILE_NONE, NULL)) return dex_future_new_true (); gpg_key_base64 = g_key_file_get_string (key_file, "Flatpak Repo", "GPGKey", NULL); if (gpg_key_base64 == NULL) return dex_future_new_true (); { gsize gpg_key_len = 0; guchar *gpg_key_data = g_base64_decode (gpg_key_base64, &gpg_key_len); gpg_key_bytes = g_bytes_new_take (gpg_key_data, gpg_key_len); } remote = flatpak_remote_new_from_file (remote_name, bytes, &local_error); if (remote == NULL) { g_warning ("failed to parse flatpakrepo from %s: %s", runtime_repo_url, local_error->message); return dex_future_new_true (); } flatpak_remote_set_gpg_verify (remote, TRUE); flatpak_remote_set_gpg_key (remote, gpg_key_bytes); if (!flatpak_installation_add_remote ( installation, remote, TRUE, cancellable, &local_error)) { g_warning ("failed to add remote '%s': %s", remote_name, local_error->message); g_clear_error (&local_error); } return dex_future_new_true (); } static DexFuture * transaction_fiber (TransactionData *data) { g_autoptr (BzFlatpakInstance) self = NULL; GCancellable *cancellable = data->cancellable; GPtrArray *installations = data->installs; GPtrArray *updates = data->updates; GPtrArray *removals = data->removals; DexChannel *channel = data->channel; g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_autoptr (GPtrArray) transactions = NULL; g_autoptr (GPtrArray) entries = NULL; g_autoptr (GPtrArray) jobs = NULL; g_autoptr (GHashTable) errored = NULL; bz_weak_get_or_return_reject (self, data->self); transactions = g_ptr_array_new_with_free_func (g_object_unref); entries = g_ptr_array_new_with_free_func (g_object_unref); if (installations != NULL) { for (guint i = 0; i < installations->len; i++) { BzFlatpakEntry *entry = NULL; const char *bundle_path = NULL; FlatpakInstallation *installation = NULL; entry = g_ptr_array_index (installations, i); bundle_path = bz_flatpak_entry_get_bundle_path (entry); if (bundle_path == NULL) continue; installation = bz_flatpak_entry_is_user (entry) ? self->user_interactive : self->system_interactive; if (bundle_path != NULL && installation != NULL) dex_await ( ensure_runtime_remote_fiber (self, installation, bundle_path, cancellable), NULL); } for (guint i = 0; i < installations->len; i++) { BzFlatpakEntry *entry = NULL; const char *bundle_path = NULL; FlatpakRef *ref = NULL; gboolean is_user = FALSE; g_autofree char *ref_fmt = NULL; g_autoptr (FlatpakTransaction) transaction = NULL; entry = g_ptr_array_index (installations, i); bundle_path = bz_flatpak_entry_get_bundle_path (entry); ref = bz_flatpak_entry_get_ref (entry); is_user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry)); ref_fmt = flatpak_ref_format_ref (ref); if ((is_user && self->user == NULL) || (!is_user && self->system == NULL)) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the update of %s to transaction " "because its installation couldn't be found", ref_fmt); } transaction = flatpak_transaction_new_for_installation ( is_user ? self->user_interactive : self->system_interactive, cancellable, &local_error); if (transaction == NULL) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to initialize potential transaction for installation: %s", local_error->message); } if (bundle_path != NULL) /* Prioritize bundle installation */ { g_autoptr (GFile) file = NULL; file = g_file_new_for_path (bundle_path); result = flatpak_transaction_add_install_bundle ( transaction, file, NULL, &local_error); } else result = flatpak_transaction_add_install ( transaction, bz_entry_get_remote_repo_name (BZ_ENTRY (entry)), ref_fmt, NULL, &local_error); if (!result) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the installation of %s to transaction: %s", ref_fmt, local_error->message); } g_ptr_array_add (transactions, g_steal_pointer (&transaction)); g_ptr_array_add (entries, g_object_ref (entry)); g_hash_table_replace (data->ref_to_entry_hash, g_steal_pointer (&ref_fmt), g_object_ref (entry)); } } if (updates != NULL) { g_autoptr (FlatpakTransaction) user_transaction = NULL; g_autoptr (FlatpakTransaction) sys_transaction = NULL; for (guint i = 0; i < updates->len; i++) { BzFlatpakEntry *entry = NULL; FlatpakRef *ref = NULL; gboolean is_user = FALSE; g_autofree char *ref_fmt = NULL; FlatpakInstallation *installation = NULL; g_autoptr (FlatpakInstalledRef) iref = NULL; const char *eol_rebase = NULL; FlatpakTransaction *target_transaction = NULL; entry = g_ptr_array_index (updates, i); ref = bz_flatpak_entry_get_ref (entry); is_user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry)); ref_fmt = flatpak_ref_format_ref (ref); if ((is_user && self->user == NULL) || (!is_user && self->system == NULL)) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the update of %s to transaction " "because its installation couldn't be found", ref_fmt); } installation = is_user ? self->user : self->system; if (is_user && user_transaction == NULL) user_transaction = flatpak_transaction_new_for_installation ( self->user_interactive, cancellable, &local_error); else if (!is_user && sys_transaction == NULL) sys_transaction = flatpak_transaction_new_for_installation ( self->system_interactive, cancellable, &local_error); if ((is_user && user_transaction == NULL) || (!is_user && sys_transaction == NULL)) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to initialize potential transaction for installation: %s", local_error->message); } /* Put updates in one transaction to prevent dependency race-conditions, since the update list is most likely coming from this instance */ target_transaction = is_user ? user_transaction : sys_transaction; iref = flatpak_installation_get_installed_ref ( installation, flatpak_ref_get_kind (ref), flatpak_ref_get_name (ref), flatpak_ref_get_arch (ref), flatpak_ref_get_branch (ref), cancellable, NULL); if (iref != NULL) eol_rebase = flatpak_installed_ref_get_eol_rebase (iref); if (eol_rebase != NULL) { const char *origin = NULL; static const char *empty_previous_ids[] = { NULL }; origin = flatpak_installed_ref_get_origin (iref); g_debug ("%s is end-of-life, rebasing to %s instead of updating", ref_fmt, eol_rebase); g_hash_table_add (data->rebased_refs, g_strdup (ref_fmt)); #if FLATPAK_CHECK_VERSION(1, 15, 6) result = flatpak_transaction_add_rebase_and_uninstall ( target_transaction, origin, eol_rebase, ref_fmt, NULL, empty_previous_ids, &local_error); #else result = flatpak_transaction_add_rebase ( target_transaction, origin, eol_rebase, NULL, NULL, &local_error) && flatpak_transaction_add_uninstall ( target_transaction, ref_fmt, &local_error); #endif } else result = flatpak_transaction_add_update ( target_transaction, ref_fmt, NULL, NULL, &local_error); if (!result) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the update of %s to transaction: %s", ref_fmt, local_error->message); } g_ptr_array_add (entries, g_object_ref (entry)); g_hash_table_replace (data->ref_to_entry_hash, g_steal_pointer (&ref_fmt), g_object_ref (entry)); } if (user_transaction != NULL) g_ptr_array_add (transactions, g_steal_pointer (&user_transaction)); if (sys_transaction != NULL) g_ptr_array_add (transactions, g_steal_pointer (&sys_transaction)); } if (removals != NULL) { for (guint i = 0; i < removals->len; i++) { BzFlatpakEntry *entry = NULL; FlatpakRef *ref = NULL; gboolean is_user = FALSE; g_autofree char *ref_fmt = NULL; g_autoptr (FlatpakTransaction) transaction = NULL; entry = g_ptr_array_index (removals, i); ref = bz_flatpak_entry_get_ref (entry); is_user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry)); ref_fmt = flatpak_ref_format_ref (ref); if ((is_user && self->user == NULL) || (!is_user && self->system == NULL)) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the removal of %s to transaction " "because its installation couldn't be found", ref_fmt); } transaction = flatpak_transaction_new_for_installation ( is_user ? self->user_interactive : self->system_interactive, cancellable, &local_error); if (transaction == NULL) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to initialize potential transaction for installation: %s", local_error->message); } result = flatpak_transaction_add_uninstall ( transaction, ref_fmt, &local_error); if (!result) { dex_channel_close_send (channel); return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to append the removal of %s to transaction: %s", ref_fmt, local_error->message); } g_ptr_array_add (transactions, g_steal_pointer (&transaction)); g_ptr_array_add (entries, g_object_ref (entry)); g_hash_table_replace (data->ref_to_entry_hash, g_steal_pointer (&ref_fmt), g_object_ref (entry)); } } g_mutex_lock (&self->transactions_mutex); #define REGISTER_CANCELLABLES(entry) \ G_STMT_START \ { \ GPtrArray *cancellables = NULL; \ \ cancellables = g_hash_table_lookup (self->ongoing_cancellables, entry); \ if (cancellables != NULL) \ g_ptr_array_add (cancellables, g_object_ref (cancellable)); \ else \ { \ cancellables = g_ptr_array_new_with_free_func (g_object_unref); \ g_ptr_array_add (cancellables, g_object_ref (cancellable)); \ g_hash_table_replace (self->ongoing_cancellables, g_object_ref (entry), cancellables); \ } \ } \ G_STMT_END if (installations != NULL) { for (guint i = 0; i < installations->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (installations, i); REGISTER_CANCELLABLES (entry); } } if (removals != NULL) { for (guint i = 0; i < removals->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (removals, i); REGISTER_CANCELLABLES (entry); } } if (updates != NULL) { for (guint i = 0; i < updates->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (updates, i); REGISTER_CANCELLABLES (entry); } } #undef REGISTER_CANCELLABLES g_mutex_unlock (&self->transactions_mutex); jobs = g_ptr_array_new_with_free_func (dex_unref); for (guint i = 0; i < transactions->len; i++) { FlatpakTransaction *transaction = NULL; g_autoptr (TransactionJobData) job_data = NULL; transaction = g_ptr_array_index (transactions, i); job_data = transaction_job_data_new (); job_data->parent = transaction_data_ref (data); job_data->transaction = g_object_ref (transaction); g_ptr_array_add ( jobs, dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) transaction_job_fiber, transaction_job_data_ref (job_data), transaction_job_data_unref)); } dex_await (dex_future_all_racev ( (DexFuture *const *) jobs->pdata, jobs->len), NULL); g_mutex_lock (&self->transactions_mutex); #define UNREGISTER_CANCELLABLES(entry) \ G_STMT_START \ { \ GPtrArray *cancellables = NULL; \ \ cancellables = g_hash_table_lookup (self->ongoing_cancellables, entry); \ if (cancellables != NULL) \ { \ g_ptr_array_remove (cancellables, cancellable); \ if (cancellables->len == 0) \ g_hash_table_remove (self->ongoing_cancellables, entry); \ } \ } \ G_STMT_END if (installations != NULL) { for (guint i = 0; i < installations->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (installations, i); UNREGISTER_CANCELLABLES (entry); } } if (removals != NULL) { for (guint i = 0; i < removals->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (removals, i); UNREGISTER_CANCELLABLES (entry); } } if (updates != NULL) { for (guint i = 0; i < updates->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (updates, i); UNREGISTER_CANCELLABLES (entry); } } #undef UNREGISTER_CANCELLABLES g_mutex_unlock (&self->transactions_mutex); if (data->send_futures->len > 0) dex_await (dex_future_allv ( (DexFuture *const *) data->send_futures->pdata, data->send_futures->len), NULL); errored = g_hash_table_new_full ( g_direct_hash, g_direct_equal, g_object_unref, (GDestroyNotify) g_error_free); for (guint i = 0; i < jobs->len; i++) { DexFuture *job = NULL; BzEntry *entry = NULL; job = g_ptr_array_index (jobs, i); entry = g_ptr_array_index (entries, i); dex_future_get_value (job, &local_error); if (local_error != NULL) g_hash_table_replace ( errored, g_object_ref (entry), g_steal_pointer (&local_error)); } dex_channel_close_send (channel); return dex_future_new_take_boxed (G_TYPE_HASH_TABLE, g_steal_pointer (&errored)); } static DexFuture * transaction_job_fiber (TransactionJobData *data) { TransactionData *parent = data->parent; FlatpakTransaction *transaction = data->transaction; GCancellable *cancellable = parent->cancellable; g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_signal_connect (transaction, "new-operation", G_CALLBACK (transaction_new_operation), parent); g_signal_connect (transaction, "operation-done", G_CALLBACK (transaction_operation_done), parent); g_signal_connect (transaction, "operation-error", G_CALLBACK (transaction_operation_error), parent); g_signal_connect (transaction, "ready", G_CALLBACK (transaction_ready), parent); result = flatpak_transaction_run (transaction, cancellable, &local_error); if (!result) return dex_future_new_reject ( BZ_FLATPAK_ERROR, BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, "Failed to run flatpak transaction on installation: %s", local_error->message); return dex_future_new_true (); } static void transaction_new_operation (FlatpakTransaction *transaction, FlatpakTransactionOperation *operation, FlatpakTransactionProgress *progress, TransactionData *data) { g_autoptr (BzFlatpakInstance) self = NULL; FlatpakTransactionOperationType kind = 0; BzFlatpakEntry *entry = NULL; g_autoptr (BzBackendTransactionOpPayload) payload = NULL; g_autoptr (TransactionOperationData) operation_data = NULL; bz_weak_get_or_return (self, data->self); kind = flatpak_transaction_operation_get_operation_type (operation); if (kind == FLATPAK_TRANSACTION_OPERATION_INSTALL || kind == FLATPAK_TRANSACTION_OPERATION_UPDATE || kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE || kind == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { g_mutex_lock (&self->mute_mutex); if (self->user_interactive == flatpak_transaction_get_installation (transaction)) self->user_mute++; else self->system_mute++; g_mutex_unlock (&self->mute_mutex); } if (data->channel == NULL) return; flatpak_transaction_progress_set_update_frequency (progress, 100); entry = find_entry_from_operation (data, operation); payload = bz_backend_transaction_op_payload_new (); bz_backend_transaction_op_payload_set_entry ( payload, BZ_ENTRY (entry)); bz_backend_transaction_op_payload_set_name ( payload, flatpak_transaction_operation_get_ref (operation)); bz_backend_transaction_op_payload_set_download_size ( payload, flatpak_transaction_operation_get_download_size (operation)); bz_backend_transaction_op_payload_set_installed_size ( payload, flatpak_transaction_operation_get_installed_size (operation)); g_mutex_lock (&data->mutex); g_ptr_array_add ( data->send_futures, dex_channel_send ( data->channel, dex_future_new_for_object (payload))); data->unidentified_op_cnt--; g_mutex_unlock (&data->mutex); g_object_set_data_full ( G_OBJECT (operation), "payload", g_object_ref (payload), g_object_unref); operation_data = transaction_operation_data_new (); operation_data->parent = transaction_data_ref (data); operation_data->entry = bz_object_maybe_ref (entry); operation_data->op = g_object_ref (payload); g_signal_connect_data ( progress, "changed", G_CALLBACK (transaction_progress_changed), transaction_operation_data_ref (operation_data), transaction_operation_data_unref_closure, G_CONNECT_DEFAULT); } static void transaction_operation_done (FlatpakTransaction *transaction, FlatpakTransactionOperation *operation, gchar *commit, gint result, TransactionData *data) { g_autoptr (GMutexLocker) locker = NULL; g_autoptr (BzFlatpakInstance) self = NULL; g_autoptr (BzBackendTransactionOpPayload) payload = NULL; g_autoptr (TransactionOperationDoneData) future_data = NULL; g_autoptr (DexFuture) future = NULL; bz_weak_get_or_return (self, data->self); locker = g_mutex_locker_new (&data->mutex); g_hash_table_replace ( data->op_to_progress_hash, g_object_ref (operation), GINT_TO_POINTER (100)); payload = g_object_steal_data (G_OBJECT (operation), "payload"); if (payload != NULL) g_ptr_array_add ( data->send_futures, dex_channel_send ( data->channel, dex_future_new_for_object (payload))); if (result == FLATPAK_TRANSACTION_RESULT_NO_CHANGE) return; future_data = transaction_operation_done_data_new (); future_data->parent = transaction_data_ref (data); future_data->transaction = g_object_ref (transaction); future_data->operation = g_object_ref (operation); future = dex_scheduler_spawn ( self->scheduler, bz_get_dex_stack_size (), (DexFiberFunc) transaction_operation_done_fiber, transaction_operation_done_data_ref (future_data), transaction_operation_done_data_unref); g_ptr_array_add (data->send_futures, g_steal_pointer (&future)); } static gboolean transaction_operation_error (FlatpakTransaction *object, FlatpakTransactionOperation *operation, GError *error, gint details, TransactionData *data) { g_autoptr (BzBackendTransactionOpPayload) payload = NULL; /* `FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL` is the only possible value of `details` */ g_warning ("Transaction failed to complete: %s", error->message); g_mutex_lock (&data->mutex); g_hash_table_replace ( data->op_to_progress_hash, g_object_ref (operation), GINT_TO_POINTER (100)); payload = g_object_steal_data (G_OBJECT (operation), "payload"); if (payload != NULL) { g_object_set_data_full ( G_OBJECT (payload), "error", g_strdup (error->message), g_free); g_ptr_array_add ( data->send_futures, dex_channel_send ( data->channel, dex_future_new_for_object (payload))); } g_mutex_unlock (&data->mutex); /* Don't recover for now */ return FALSE; } static gboolean transaction_ready (FlatpakTransaction *object, TransactionData *data) { g_autolist (GObject) operations = NULL; operations = flatpak_transaction_get_operations (object); g_mutex_lock (&data->mutex); data->unidentified_op_cnt += g_list_length (operations); g_mutex_unlock (&data->mutex); return TRUE; } static BzFlatpakEntry * find_entry_from_operation (TransactionData *data, FlatpakTransactionOperation *operation) { GPtrArray *related_to_ops = NULL; const char *ref_fmt = NULL; BzFlatpakEntry *entry = NULL; related_to_ops = flatpak_transaction_operation_get_related_to_ops (operation); ref_fmt = flatpak_transaction_operation_get_ref (operation); entry = g_hash_table_lookup (data->ref_to_entry_hash, ref_fmt); if (entry != NULL) return entry; if (related_to_ops != NULL) { for (guint i = 0; i < related_to_ops->len; i++) { FlatpakTransactionOperation *related_op = NULL; related_op = g_ptr_array_index (related_to_ops, i); entry = find_entry_from_operation (data, related_op); if (entry != NULL) break; } } return entry; } static void transaction_progress_changed (FlatpakTransactionProgress *progress, TransactionOperationData *data) { TransactionData *parent = data->parent; g_autoptr (BzBackendTransactionOpProgressPayload) payload = NULL; int int_progress = 0; double double_progress = 0.0; GHashTableIter iter = { 0 }; int progress_sum = 0; guint n_ops = 0; double total_progress = 0.0; g_mutex_lock (&parent->mutex); int_progress = flatpak_transaction_progress_get_progress (progress); double_progress = (double) flatpak_transaction_progress_get_progress (progress) / 100.0; g_hash_table_replace ( parent->op_to_progress_hash, g_object_ref (data->op), GINT_TO_POINTER (int_progress)); g_hash_table_iter_init (&iter, parent->op_to_progress_hash); for (;;) { gpointer key = NULL; gpointer val = NULL; if (!g_hash_table_iter_next (&iter, &key, &val)) break; progress_sum += GPOINTER_TO_INT (val); n_ops++; } total_progress = MIN ((double) progress_sum / (double) ((n_ops + parent->unidentified_op_cnt) * 100), 1.0); payload = bz_backend_transaction_op_progress_payload_new (); bz_backend_transaction_op_progress_payload_set_op ( payload, data->op); bz_backend_transaction_op_progress_payload_set_status ( payload, flatpak_transaction_progress_get_status (progress)); bz_backend_transaction_op_progress_payload_set_is_estimating ( payload, flatpak_transaction_progress_get_is_estimating (progress)); bz_backend_transaction_op_progress_payload_set_progress ( payload, double_progress); bz_backend_transaction_op_progress_payload_set_total_progress ( payload, total_progress); bz_backend_transaction_op_progress_payload_set_bytes_transferred ( payload, flatpak_transaction_progress_get_bytes_transferred (progress)); bz_backend_transaction_op_progress_payload_set_start_time ( payload, flatpak_transaction_progress_get_start_time (progress)); g_ptr_array_add ( data->parent->send_futures, dex_channel_send ( data->parent->channel, dex_future_new_for_object (payload))); g_mutex_unlock (&parent->mutex); } static DexFuture * transaction_operation_done_fiber (TransactionOperationDoneData *data) { g_autoptr (BzFlatpakInstance) self = NULL; FlatpakTransaction *transaction = data->transaction; FlatpakTransactionOperation *operation = data->operation; g_autoptr (GError) local_error = NULL; FlatpakTransactionOperationType op_type = 0; BzBackendNotificationKind notif_kind = 0; const char *origin = NULL; const char *ref = NULL; gboolean is_user = FALSE; g_autofree char *unique_id = NULL; const char *version = NULL; gboolean was_rebased = FALSE; FlatpakInstallation *installation = NULL; g_autoptr (FlatpakInstalledRef) iref = NULL; g_autoptr (FlatpakRef) parsed_ref = NULL; bz_weak_get_or_return_reject (self, data->parent->self); op_type = flatpak_transaction_operation_get_operation_type (operation); switch (op_type) { case FLATPAK_TRANSACTION_OPERATION_INSTALL: case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: notif_kind = BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE; break; case FLATPAK_TRANSACTION_OPERATION_UPDATE: notif_kind = BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE; break; case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: notif_kind = BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE; break; case FLATPAK_TRANSACTION_OPERATION_LAST_TYPE: default: g_assert_not_reached (); } installation = flatpak_transaction_get_installation (transaction); origin = flatpak_transaction_operation_get_remote (operation); ref = flatpak_transaction_operation_get_ref (operation); is_user = installation == self->user_interactive; unique_id = bz_flatpak_ref_parts_format_unique (origin, ref, is_user); was_rebased = g_hash_table_contains (data->parent->rebased_refs, ref); if (op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) { g_autoptr (FlatpakRemote) remote = NULL; { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES); send_notif_all (self, notif, TRUE); } remote = flatpak_installation_get_remote_by_name ( installation, origin, NULL, NULL); if (remote != NULL) { { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START); bz_backend_notification_set_remote_name (notif, origin); send_notif_all (self, notif, TRUE); } dex_await ( retrieve_refs_for_noenumerable_remote ( self, NULL, origin, installation, remote), NULL); { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH); bz_backend_notification_set_remote_name (notif, origin); send_notif_all (self, notif, TRUE); } } } if (notif_kind == BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE || notif_kind == BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE) { parsed_ref = flatpak_ref_parse (ref, &local_error); if (parsed_ref != NULL) { iref = flatpak_installation_get_installed_ref ( installation, flatpak_ref_get_kind (parsed_ref), flatpak_ref_get_name (parsed_ref), flatpak_ref_get_arch (parsed_ref), flatpak_ref_get_branch (parsed_ref), NULL, &local_error); if (iref != NULL) version = flatpak_installed_ref_get_appdata_version (iref); else if (local_error != NULL) { g_warning ("Failed to get installed ref for version: %s", local_error->message); g_clear_error (&local_error); } } else if (local_error != NULL) { g_warning ("Failed to parse ref for version: %s", local_error->message); g_clear_error (&local_error); } } { g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, notif_kind); bz_backend_notification_set_unique_id (notif, unique_id); bz_backend_notification_set_was_rebased (notif, was_rebased); if (version != NULL && *version != '\0') bz_backend_notification_set_version (notif, version); send_notif_all (self, notif, TRUE); } return dex_future_new_true (); } static void installation_event (BzFlatpakInstance *self, GFile *file, GFile *other_file, GFileMonitorEvent event_type, GFileMonitor *monitor) { gboolean emit = FALSE; g_autoptr (BzBackendNotification) notif = NULL; g_mutex_lock (&self->mute_mutex); if (monitor == self->user_events) { if (self->user_mute > 0) self->user_mute--; else emit = TRUE; } else { if (self->system_mute > 0) self->system_mute--; else emit = TRUE; } g_mutex_unlock (&self->mute_mutex); if (!emit) return; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_EXTERNAL_CHANGE); send_notif_all (self, notif, TRUE); } static void send_notif (BzFlatpakInstance *self, DexChannel *channel, BzBackendNotification *notif, gboolean lock) { g_autoptr (GMutexLocker) locker = NULL; if (lock) locker = g_mutex_locker_new (&self->notif_mutex); if (self->notif_send == NULL || !dex_future_is_pending (self->notif_send)) { dex_clear (&self->notif_send); self->notif_send = dex_channel_send ( channel, dex_future_new_for_object (notif)); } else { g_autoptr (WaitNotifData) data = NULL; data = wait_notif_data_new (); g_weak_ref_init (&data->self, self); data->channel = dex_ref (channel); data->notif = g_object_ref (notif); self->notif_send = dex_future_finally ( g_steal_pointer (&self->notif_send), (DexFutureCallback) wait_notif_finally, wait_notif_data_ref (data), wait_notif_data_unref); } } static void send_notif_all (BzFlatpakInstance *self, BzBackendNotification *notif, gboolean lock) { g_autoptr (GMutexLocker) locker = NULL; if (lock) locker = g_mutex_locker_new (&self->notif_mutex); for (guint i = 0; i < self->notif_channels->len;) { DexChannel *channel = NULL; channel = g_ptr_array_index (self->notif_channels, i); if (dex_channel_can_send (channel)) { send_notif (self, channel, notif, FALSE); i++; } else g_ptr_array_remove_index_fast (self->notif_channels, i); } } static DexFuture * wait_notif_finally (DexFuture *future, WaitNotifData *data) { g_autoptr (BzFlatpakInstance) self = NULL; g_autoptr (GMutexLocker) locker = NULL; bz_weak_get_or_return_reject (self, &data->self); locker = g_mutex_locker_new (&self->notif_mutex); if (future == self->notif_send) dex_clear (&self->notif_send); send_notif (self, data->channel, data->notif, FALSE); return dex_future_new_true (); } static gint cmp_rref (FlatpakRemoteRef *a, FlatpakRemoteRef *b, GHashTable *hash) { FlatpakRefKind a_fkind = 0; FlatpakRefKind b_fkind = 0; AsComponent *a_comp = NULL; AsComponent *b_comp = NULL; AsComponentKind a_kind = AS_COMPONENT_KIND_UNKNOWN; AsComponentKind b_kind = AS_COMPONENT_KIND_UNKNOWN; a_fkind = flatpak_ref_get_kind (FLATPAK_REF (a)); b_fkind = flatpak_ref_get_kind (FLATPAK_REF (b)); a_comp = g_hash_table_lookup (hash, flatpak_ref_get_name (FLATPAK_REF (a))); b_comp = g_hash_table_lookup (hash, flatpak_ref_get_name (FLATPAK_REF (b))); if (a_comp == NULL) return a_fkind == FLATPAK_REF_KIND_RUNTIME ? -1 : 1; if (b_comp == NULL) return b_fkind == FLATPAK_REF_KIND_RUNTIME ? 1 : -1; a_kind = as_component_get_kind (a_comp); b_kind = as_component_get_kind (b_comp); if (a_kind == AS_COMPONENT_KIND_RUNTIME) return -1; if (b_kind == AS_COMPONENT_KIND_RUNTIME) return 1; if (a_kind == AS_COMPONENT_KIND_ADDON) return -1; if (b_kind == AS_COMPONENT_KIND_ADDON) return 1; if (a_kind == AS_COMPONENT_KIND_DESKTOP_APP || a_kind == AS_COMPONENT_KIND_CONSOLE_APP || a_kind == AS_COMPONENT_KIND_WEB_APP) return 1; if (b_kind == AS_COMPONENT_KIND_DESKTOP_APP || b_kind == AS_COMPONENT_KIND_CONSOLE_APP || b_kind == AS_COMPONENT_KIND_WEB_APP) return -1; return 0; } static AsComponent * parse_component_for_node (XbNode *node, GError **error) { g_autofree char *component_xml = NULL; g_autoptr (AsMetadata) metadata = NULL; AsComponent *component = NULL; gboolean result = FALSE; component_xml = xb_node_export (node, XB_NODE_EXPORT_FLAG_NONE, error); if (component_xml == NULL) return NULL; metadata = as_metadata_new (); result = as_metadata_parse_data ( metadata, component_xml, -1, AS_FORMAT_KIND_XML, error); if (!result) return NULL; component = as_metadata_get_component (metadata); return bz_object_maybe_ref (component); } static GBytes * decompress_appstream_gz (GBytes *appstream_gz, GCancellable *cancellable, GError **error) { g_autoptr (GZlibDecompressor) decompressor = NULL; g_autoptr (GInputStream) stream_gz = NULL; g_autoptr (GInputStream) stream_data = NULL; g_autoptr (GBytes) appstream = NULL; decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); stream_gz = g_memory_input_stream_new_from_bytes (appstream_gz); 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) return NULL; return g_steal_pointer (&appstream); } static XbSilo * build_silo (XbBuilderSource *source, GCancellable *cancellable, GError **error) { g_autoptr (XbBuilder) builder = NULL; const gchar *const *locales = NULL; g_autoptr (XbSilo) silo = NULL; builder = xb_builder_new (); locales = g_get_language_names (); for (guint i = 0; locales[i] != NULL; i++) xb_builder_add_locale (builder, locales[i]); xb_builder_import_source (builder, source); silo = xb_builder_compile ( builder, XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS, cancellable, error); return g_steal_pointer (&silo); } static AsComponent * extract_first_component_for_silo (XbSilo *silo, GError **error) { g_autoptr (XbNode) root = NULL; g_autoptr (GPtrArray) children = NULL; root = xb_silo_get_root (silo); children = xb_node_get_children (root); if (children == NULL || children->len == 0) return NULL; return parse_component_for_node ( g_ptr_array_index (children, 0), error); }