*
* 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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "BAZAAR::FLATPAK"
#define BAZAAR_MODULE "flatpak"
#include <malloc.h>
#include <xmlb.h>
#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"
G_DEFINE_QUARK (bz-flatpak-error-quark, bz_flatpak_error);
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;
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;
}
#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
{
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;
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;
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__
*
* 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);
}
* 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);
if (strstr (remote_name, "fedora") != NULL)
is_noenumerate = TRUE;
* 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));
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)
{
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);
}
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;
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);
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,
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);
}