diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/flatpak/gs-flatpak-transaction.c | 761 |
1 files changed, 761 insertions, 0 deletions
diff --git a/plugins/flatpak/gs-flatpak-transaction.c b/plugins/flatpak/gs-flatpak-transaction.c new file mode 100644 index 0000000..7377f6d --- /dev/null +++ b/plugins/flatpak/gs-flatpak-transaction.c @@ -0,0 +1,761 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2018 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2018 Kalev Lember <klember@redhat.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <config.h> + +#include "gs-flatpak-app.h" +#include "gs-flatpak-transaction.h" + +struct _GsFlatpakTransaction { + FlatpakTransaction parent_instance; + GHashTable *refhash; /* ref:GsApp */ + GError *first_operation_error; +}; + +enum { + SIGNAL_REF_TO_APP, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (GsFlatpakTransaction, gs_flatpak_transaction, FLATPAK_TYPE_TRANSACTION) + +static void +gs_flatpak_transaction_finalize (GObject *object) +{ + GsFlatpakTransaction *self; + g_return_if_fail (GS_IS_FLATPAK_TRANSACTION (object)); + self = GS_FLATPAK_TRANSACTION (object); + + g_assert (self != NULL); + g_hash_table_unref (self->refhash); + if (self->first_operation_error != NULL) + g_error_free (self->first_operation_error); + + G_OBJECT_CLASS (gs_flatpak_transaction_parent_class)->finalize (object); +} + +GsApp * +gs_flatpak_transaction_get_app_by_ref (FlatpakTransaction *transaction, const gchar *ref) +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + return g_hash_table_lookup (self->refhash, ref); +} + +static void +gs_flatpak_transaction_add_app_internal (GsFlatpakTransaction *self, GsApp *app) +{ + g_autofree gchar *ref = gs_flatpak_app_get_ref_display (app); + g_hash_table_insert (self->refhash, g_steal_pointer (&ref), g_object_ref (app)); +} + +void +gs_flatpak_transaction_add_app (FlatpakTransaction *transaction, GsApp *app) +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + gs_flatpak_transaction_add_app_internal (self, app); + if (gs_app_get_runtime (app) != NULL) + gs_flatpak_transaction_add_app_internal (self, gs_app_get_runtime (app)); +} + +static GsApp * +_ref_to_app (GsFlatpakTransaction *self, const gchar *ref) +{ + GsApp *app = g_hash_table_lookup (self->refhash, ref); + if (app != NULL) + return g_object_ref (app); + g_signal_emit (self, signals[SIGNAL_REF_TO_APP], 0, ref, &app); + + /* Cache the result */ + if (app != NULL) + g_hash_table_insert (self->refhash, g_strdup (ref), g_object_ref (app)); + + return app; +} + +static void +_transaction_operation_set_app (FlatpakTransactionOperation *op, GsApp *app) +{ + g_object_set_data_full (G_OBJECT (op), "GsApp", + g_object_ref (app), (GDestroyNotify) g_object_unref); +} + +static GsApp * +_transaction_operation_get_app (FlatpakTransactionOperation *op) +{ + return g_object_get_data (G_OBJECT (op), "GsApp"); +} + +gboolean +gs_flatpak_transaction_run (FlatpakTransaction *transaction, + GCancellable *cancellable, + GError **error) + +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + g_autoptr(GError) error_local = NULL; + + if (!flatpak_transaction_run (transaction, cancellable, &error_local)) { + /* whole transaction failed; restore the state for all the apps involved */ + g_autolist(GObject) ops = flatpak_transaction_get_operations (transaction); + for (GList *l = ops; l != NULL; l = l->next) { + FlatpakTransactionOperation *op = l->data; + const gchar *ref = flatpak_transaction_operation_get_ref (op); + g_autoptr(GsApp) app = _ref_to_app (self, ref); + if (app == NULL) { + g_warning ("failed to find app for %s", ref); + continue; + } + gs_app_set_state_recover (app); + } + + if (self->first_operation_error != NULL) { + g_propagate_error (error, g_steal_pointer (&self->first_operation_error)); + return FALSE; + } else { + g_propagate_error (error, g_steal_pointer (&error_local)); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +_transaction_ready (FlatpakTransaction *transaction) +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + g_autolist(GObject) ops = NULL; + + /* nothing to do */ + ops = flatpak_transaction_get_operations (transaction); + if (ops == NULL) + return TRUE; // FIXME: error? + for (GList *l = ops; l != NULL; l = l->next) { + FlatpakTransactionOperation *op = l->data; + const gchar *ref = flatpak_transaction_operation_get_ref (op); + g_autoptr(GsApp) app = _ref_to_app (self, ref); + if (app != NULL) { + _transaction_operation_set_app (op, app); + /* if we're updating a component, then mark all the apps + * involved to ensure updating the button state */ + if (flatpak_transaction_operation_get_operation_type (op) == FLATPAK_TRANSACTION_OPERATION_UPDATE) { + if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN || + gs_app_get_state (app) == GS_APP_STATE_INSTALLED) + gs_app_set_state (app, GS_APP_STATE_UPDATABLE_LIVE); + + gs_app_set_state (app, GS_APP_STATE_INSTALLING); + } + } + + /* Debug dump. */ + { + GPtrArray *related_to_ops = flatpak_transaction_operation_get_related_to_ops (op); + g_autoptr(GString) debug_message = g_string_new (""); + + g_string_append_printf (debug_message, + "%s: op %p, app %s (%p), download size %" G_GUINT64_FORMAT ", related-to:", + G_STRFUNC, op, + app ? gs_app_get_unique_id (app) : "?", + app, + flatpak_transaction_operation_get_download_size (op)); + for (gsize i = 0; related_to_ops != NULL && i < related_to_ops->len; i++) { + FlatpakTransactionOperation *related_to_op = g_ptr_array_index (related_to_ops, i); + g_string_append_printf (debug_message, + "\n ├ %s (%p)", flatpak_transaction_operation_get_ref (related_to_op), related_to_op); + } + g_string_append (debug_message, "\n └ (end)"); + g_debug ("%s", debug_message->str); + } + } + return TRUE; +} + +typedef struct +{ + GsFlatpakTransaction *transaction; /* (owned) */ + FlatpakTransactionOperation *operation; /* (owned) */ + GsApp *app; /* (owned) */ +} ProgressData; + +static void +progress_data_free (ProgressData *data) +{ + g_clear_object (&data->operation); + g_clear_object (&data->app); + g_clear_object (&data->transaction); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ProgressData, progress_data_free) + +static gboolean +op_is_related_to_op (FlatpakTransactionOperation *op, + FlatpakTransactionOperation *root_op) +{ + GPtrArray *related_to_ops; /* (element-type FlatpakTransactionOperation) */ + + if (op == root_op) + return TRUE; + + related_to_ops = flatpak_transaction_operation_get_related_to_ops (op); + for (gsize i = 0; related_to_ops != NULL && i < related_to_ops->len; i++) { + FlatpakTransactionOperation *related_to_op = g_ptr_array_index (related_to_ops, i); + if (related_to_op == root_op || op_is_related_to_op (related_to_op, root_op)) + return TRUE; + } + + return FALSE; +} + +static guint64 +saturated_uint64_add (guint64 a, guint64 b) +{ + return (a <= G_MAXUINT64 - b) ? a + b : G_MAXUINT64; +} + +/* + * update_progress_for_op: + * @self: a #GsFlatpakTransaction + * @current_progress: progress reporting object + * @ops: results of calling flatpak_transaction_get_operations() on @self, for performance + * @current_op: the #FlatpakTransactionOperation which the @current_progress is + * for; this is the operation currently being run by libflatpak + * @root_op: the #FlatpakTransactionOperation at the root of the operation subtree + * to calculate progress for + * + * Calculate and update the #GsApp:progress for each app associated with + * @root_op in a flatpak transaction. This will include the #GsApp for the app + * being installed (for example), but also the #GsApps for all of its runtimes + * and locales, and any other dependencies of them. + * + * Each #GsApp:progress is calculated based on the sum of the progress of all + * the apps related to that one — so the progress for an app will factor in the + * progress for all its runtimes. + */ +static void +update_progress_for_op (GsFlatpakTransaction *self, + FlatpakTransactionProgress *current_progress, + GList *ops, + FlatpakTransactionOperation *current_op, + FlatpakTransactionOperation *root_op) +{ + g_autoptr(GsApp) root_app = NULL; + guint64 related_prior_download_bytes = 0; + guint64 related_download_bytes = 0; + guint64 current_bytes_transferred = flatpak_transaction_progress_get_bytes_transferred (current_progress); + gboolean seen_current_op = FALSE, seen_root_op = FALSE; + gboolean root_op_skipped = flatpak_transaction_operation_get_is_skipped (root_op); + guint percent; + + /* If @root_op is being skipped and its GsApp isn't being + * installed/removed, don't update the progress on it. It may be that + * @root_op is the runtime of an app and the app is the thing the + * transaction was created for. + */ + if (root_op_skipped) { + /* _transaction_operation_set_app() is only called on non-skipped ops */ + const gchar *ref = flatpak_transaction_operation_get_ref (root_op); + root_app = _ref_to_app (self, ref); + if (root_app == NULL) { + g_warning ("Couldn't find GsApp for transaction operation %s", + flatpak_transaction_operation_get_ref (root_op)); + return; + } + if (gs_app_get_state (root_app) != GS_APP_STATE_INSTALLING && + gs_app_get_state (root_app) != GS_APP_STATE_REMOVING) + return; + } else { + GsApp *unskipped_root_app = _transaction_operation_get_app (root_op); + if (unskipped_root_app == NULL) { + g_warning ("Couldn't find GsApp for transaction operation %s", + flatpak_transaction_operation_get_ref (root_op)); + return; + } + root_app = g_object_ref (unskipped_root_app); + } + + /* This relies on ops in a #FlatpakTransaction being run in the order + * they’re returned by flatpak_transaction_get_operations(), which is true. */ + for (GList *l = ops; l != NULL; l = l->next) { + FlatpakTransactionOperation *op = FLATPAK_TRANSACTION_OPERATION (l->data); + guint64 op_download_size = flatpak_transaction_operation_get_download_size (op); + + if (op == current_op) + seen_current_op = TRUE; + if (op == root_op) + seen_root_op = TRUE; + + /* Currently libflatpak doesn't return skipped ops in + * flatpak_transaction_get_operations(), but check just in case. + */ + if (op == root_op && root_op_skipped) + continue; + + if (op_is_related_to_op (op, root_op)) { + /* Saturate instead of overflowing */ + related_download_bytes = saturated_uint64_add (related_download_bytes, op_download_size); + if (!seen_current_op) + related_prior_download_bytes = saturated_uint64_add (related_prior_download_bytes, op_download_size); + } + } + + g_assert (related_prior_download_bytes <= related_download_bytes); + g_assert (seen_root_op || root_op_skipped); + + /* Avoid overflows when converting to percent, at the cost of losing + * some precision in the least significant digits. */ + if (related_prior_download_bytes > G_MAXUINT64 / 100 || + current_bytes_transferred > G_MAXUINT64 / 100) { + related_prior_download_bytes /= 100; + current_bytes_transferred /= 100; + related_download_bytes /= 100; + } + + /* Update the progress of @root_app. */ + if (related_download_bytes > 0) + percent = ((related_prior_download_bytes * 100 / related_download_bytes) + + (current_bytes_transferred * 100 / related_download_bytes)); + else + percent = 0; + + if (gs_app_get_progress (root_app) == 100 || + gs_app_get_progress (root_app) == GS_APP_PROGRESS_UNKNOWN || + gs_app_get_progress (root_app) <= percent) { + gs_app_set_progress (root_app, percent); + } else { + g_warning ("ignoring percentage %u%% -> %u%% as going down on app %s", + gs_app_get_progress (root_app), percent, + gs_app_get_unique_id (root_app)); + } +} + +static void +update_progress_for_op_recurse_up (GsFlatpakTransaction *self, + FlatpakTransactionProgress *progress, + GList *ops, + FlatpakTransactionOperation *current_op, + FlatpakTransactionOperation *root_op) +{ + GPtrArray *related_to_ops = flatpak_transaction_operation_get_related_to_ops (root_op); + + /* Update progress for @root_op */ + update_progress_for_op (self, progress, ops, current_op, root_op); + + /* Update progress for ops related to @root_op, e.g. apps whose runtime is @root_op */ + for (gsize i = 0; related_to_ops != NULL && i < related_to_ops->len; i++) { + FlatpakTransactionOperation *related_to_op = g_ptr_array_index (related_to_ops, i); + update_progress_for_op_recurse_up (self, progress, ops, current_op, related_to_op); + } +} + +static void +_transaction_progress_changed_cb (FlatpakTransactionProgress *progress, + gpointer user_data) +{ + ProgressData *data = user_data; + GsApp *app = data->app; + GsFlatpakTransaction *self = data->transaction; + g_autolist(FlatpakTransactionOperation) ops = NULL; + + if (flatpak_transaction_progress_get_is_estimating (progress)) { + /* "Estimating" happens while fetching the metadata, which + * flatpak arbitrarily decides happens during the first 5% of + * each operation. At this point, no more detailed progress + * information is available. */ + gs_app_set_progress (app, GS_APP_PROGRESS_UNKNOWN); + return; + } + + /* Update the progress on this app, and then do the same for each + * related parent app up the hierarchy. For example, @data->operation + * could be for a runtime which was added to the transaction because of + * an app — so we need to update the progress on the app too. + * + * It’s important to note that a new @data->progress is created by + * libflatpak for each @data->operation, and there are multiple + * operations in a transaction. There is no #FlatpakTransactionProgress + * which represents the progress of the whole transaction. + * + * There may be arbitrary many levels of related-to ops. For example, + * one common situation would be to install an app which needs a new + * runtime, and that runtime needs a locale to be installed, which would + * give three levels of related-to relation: + * locale → runtime → app → (null) + * + * In addition, libflatpak may decide to skip some operations (if they + * turn out to not be necessary). These skipped operations are not + * included in the list returned by flatpak_transaction_get_operations(), + * but they can be accessed via + * flatpak_transaction_operation_get_related_to_ops(), so have to be + * ignored manually. + */ + ops = flatpak_transaction_get_operations (FLATPAK_TRANSACTION (self)); + update_progress_for_op_recurse_up (self, progress, ops, data->operation, data->operation); +} + +static const gchar * +_flatpak_transaction_operation_type_to_string (FlatpakTransactionOperationType ot) +{ + if (ot == FLATPAK_TRANSACTION_OPERATION_INSTALL) + return "install"; + if (ot == FLATPAK_TRANSACTION_OPERATION_UPDATE) + return "update"; + if (ot == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) + return "install-bundle"; + if (ot == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) + return "uninstall"; + return NULL; +} + +static void +progress_data_free_closure (gpointer user_data, + GClosure *closure) +{ + progress_data_free (user_data); +} + +static void +_transaction_new_operation (FlatpakTransaction *transaction, + FlatpakTransactionOperation *operation, + FlatpakTransactionProgress *progress) +{ + GsApp *app; + g_autoptr(ProgressData) progress_data = NULL; + + /* find app */ + app = _transaction_operation_get_app (operation); + if (app == NULL) { + FlatpakTransactionOperationType ot; + ot = flatpak_transaction_operation_get_operation_type (operation); + g_warning ("failed to find app for %s during %s", + flatpak_transaction_operation_get_ref (operation), + _flatpak_transaction_operation_type_to_string (ot)); + return; + } + + /* report progress */ + progress_data = g_new0 (ProgressData, 1); + progress_data->transaction = GS_FLATPAK_TRANSACTION (g_object_ref (transaction)); + progress_data->app = g_object_ref (app); + progress_data->operation = g_object_ref (operation); + + g_signal_connect_data (progress, "changed", + G_CALLBACK (_transaction_progress_changed_cb), + g_steal_pointer (&progress_data), + progress_data_free_closure, + 0 /* flags */); + flatpak_transaction_progress_set_update_frequency (progress, 500); /* FIXME? */ + + /* set app status */ + switch (flatpak_transaction_operation_get_operation_type (operation)) { + case FLATPAK_TRANSACTION_OPERATION_INSTALL: + if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) + gs_app_set_state (app, GS_APP_STATE_AVAILABLE); + gs_app_set_state (app, GS_APP_STATE_INSTALLING); + break; + case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: + if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) + gs_app_set_state (app, GS_APP_STATE_AVAILABLE_LOCAL); + gs_app_set_state (app, GS_APP_STATE_INSTALLING); + break; + case FLATPAK_TRANSACTION_OPERATION_UPDATE: + if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN || + gs_app_get_state (app) == GS_APP_STATE_INSTALLED) + gs_app_set_state (app, GS_APP_STATE_UPDATABLE_LIVE); + gs_app_set_state (app, GS_APP_STATE_INSTALLING); + break; + case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: + gs_app_set_state (app, GS_APP_STATE_REMOVING); + break; + default: + break; + } +} + +static gboolean +later_op_also_related (GList *ops, + FlatpakTransactionOperation *current_op, + FlatpakTransactionOperation *related_to_current_op) +{ + /* Here we're determining if anything in @ops which comes after + * @current_op is related to @related_to_current_op and not skipped + * (but all @ops are not skipped so no need to check explicitly) + */ + gboolean found_later_op = FALSE, seen_current_op = FALSE; + for (GList *l = ops; l != NULL; l = l->next) { + FlatpakTransactionOperation *op = l->data; + GPtrArray *related_to_ops; + if (current_op == op) { + seen_current_op = TRUE; + continue; + } + if (!seen_current_op) + continue; + + related_to_ops = flatpak_transaction_operation_get_related_to_ops (op); + for (gsize i = 0; related_to_ops != NULL && i < related_to_ops->len; i++) { + FlatpakTransactionOperation *related_to_op = g_ptr_array_index (related_to_ops, i); + if (related_to_op == related_to_current_op) { + g_assert (flatpak_transaction_operation_get_is_skipped (related_to_op)); + found_later_op = TRUE; + } + } + } + + return found_later_op; +} + +static void +set_skipped_related_apps_to_installed (GsFlatpakTransaction *self, + FlatpakTransaction *transaction, + FlatpakTransactionOperation *operation) +{ + /* It's possible the thing being updated/installed, @operation, is a + * related ref (e.g. extension or runtime) of an app which itself doesn't + * need an update and therefore won't have _transaction_operation_done() + * called for it directly. So we have to set the main app to installed + * here. + */ + g_autolist(GObject) ops = flatpak_transaction_get_operations (transaction); + GPtrArray *related_to_ops = flatpak_transaction_operation_get_related_to_ops (operation); + + for (gsize i = 0; related_to_ops != NULL && i < related_to_ops->len; i++) { + FlatpakTransactionOperation *related_to_op = g_ptr_array_index (related_to_ops, i); + if (flatpak_transaction_operation_get_is_skipped (related_to_op)) { + const gchar *ref; + g_autoptr(GsApp) related_to_app = NULL; + + /* Check that no later op is also related to related_to_op, in + * which case we want to let that operation finish before setting + * the main app to installed. + */ + if (later_op_also_related (ops, operation, related_to_op)) + continue; + + ref = flatpak_transaction_operation_get_ref (related_to_op); + related_to_app = _ref_to_app (self, ref); + if (related_to_app != NULL) + gs_app_set_state (related_to_app, GS_APP_STATE_INSTALLED); + } + } +} + +static void +_transaction_operation_done (FlatpakTransaction *transaction, + FlatpakTransactionOperation *operation, + const gchar *commit, + FlatpakTransactionResult details) +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + + /* invalidate */ + GsApp *app = _transaction_operation_get_app (operation); + if (app == NULL) { + g_warning ("failed to find app for %s", + flatpak_transaction_operation_get_ref (operation)); + return; + } + switch (flatpak_transaction_operation_get_operation_type (operation)) { + case FLATPAK_TRANSACTION_OPERATION_INSTALL: + case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: + gs_app_set_state (app, GS_APP_STATE_INSTALLED); + + set_skipped_related_apps_to_installed (self, transaction, operation); + break; + case FLATPAK_TRANSACTION_OPERATION_UPDATE: + gs_app_set_version (app, gs_app_get_update_version (app)); + gs_app_set_update_details_markup (app, NULL); + gs_app_set_update_urgency (app, AS_URGENCY_KIND_UNKNOWN); + gs_app_set_update_version (app, NULL); + /* force getting the new runtime */ + gs_app_remove_kudo (app, GS_APP_KUDO_SANDBOXED); + /* downloaded, but not yet installed */ + if (flatpak_transaction_get_no_deploy (transaction)) + gs_app_set_state (app, GS_APP_STATE_UPDATABLE_LIVE); + else + gs_app_set_state (app, GS_APP_STATE_INSTALLED); + + set_skipped_related_apps_to_installed (self, transaction, operation); + break; + case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: + /* we don't actually know if this app is re-installable */ + gs_flatpak_app_set_commit (app, NULL); + gs_app_set_state (app, GS_APP_STATE_UNKNOWN); + break; + default: + gs_app_set_state (app, GS_APP_STATE_UNKNOWN); + break; + } +} + +static gboolean +_transaction_operation_error (FlatpakTransaction *transaction, + FlatpakTransactionOperation *operation, + const GError *error, + FlatpakTransactionErrorDetails detail) +{ + GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction); + FlatpakTransactionOperationType operation_type = flatpak_transaction_operation_get_operation_type (operation); + GsApp *app = _transaction_operation_get_app (operation); + const gchar *ref = flatpak_transaction_operation_get_ref (operation); + + if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED)) { + g_debug ("skipped to %s %s: %s", + _flatpak_transaction_operation_type_to_string (operation_type), + ref, + error->message); + return TRUE; /* continue */ + } + + if (detail & FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL) { + g_warning ("failed to %s %s (non fatal): %s", + _flatpak_transaction_operation_type_to_string (operation_type), + ref, + error->message); + return TRUE; /* continue */ + } + + if (self->first_operation_error == NULL) { + g_propagate_error (&self->first_operation_error, + g_error_copy (error)); + if (app != NULL) + gs_utils_error_add_app_id (&self->first_operation_error, app); + } + return FALSE; /* stop */ +} + +static int +_transaction_choose_remote_for_ref (FlatpakTransaction *transaction, + const char *for_ref, + const char *runtime_ref, + const char * const *remotes) +{ + //FIXME: do something smarter + return 0; +} + +static void +_transaction_end_of_lifed (FlatpakTransaction *transaction, + const gchar *ref, + const gchar *reason, + const gchar *rebase) +{ + if (rebase) { + g_message ("%s is end-of-life, in favor of %s", ref, rebase); + } else if (reason) { + g_message ("%s is end-of-life, with reason: %s", ref, reason); + } + //FIXME: show something in the UI +} + +static gboolean +_transaction_end_of_lifed_with_rebase (FlatpakTransaction *transaction, + const gchar *remote, + const gchar *ref, + const gchar *reason, + const gchar *rebased_to_ref, + const gchar **previous_ids) +{ + if (rebased_to_ref) { + g_message ("%s is end-of-life, in favor of %s", ref, rebased_to_ref); + } else if (reason) { + g_message ("%s is end-of-life, with reason: %s", ref, reason); + } + + if (rebased_to_ref && remote) { + g_autoptr(GError) local_error = NULL; + + if (!flatpak_transaction_add_rebase (transaction, remote, rebased_to_ref, + NULL, previous_ids, &local_error) || + !flatpak_transaction_add_uninstall (transaction, ref, &local_error)) { + /* There's no way to make the whole transaction fail on + * this error path, so just print a warning and return + * FALSE, which will cause the operation on the + * end-of-lifed ref not to be skipped. + */ + g_warning ("Failed to rebase %s to %s: %s", ref, rebased_to_ref, local_error->message); + return FALSE; + } + + /* Note: A message about the rename will be shown in the UI + * thanks to code in gs_flatpak_refine_appstream() which + * sets gs_app_set_renamed_from(). + */ + return TRUE; + } + + return FALSE; +} + +static gboolean +_transaction_add_new_remote (FlatpakTransaction *transaction, + FlatpakTransactionRemoteReason reason, + const char *from_id, + const char *remote_name, + const char *url) +{ + /* additional applications */ + if (reason == FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO) { + g_debug ("configuring %s as new generic remote", url); + return TRUE; //FIXME? + } + + /* runtime deps always make sense */ + if (reason == FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS) { + g_debug ("configuring %s as new remote for deps", url); + return TRUE; + } + + return FALSE; +} + +static void +gs_flatpak_transaction_class_init (GsFlatpakTransactionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FlatpakTransactionClass *transaction_class = FLATPAK_TRANSACTION_CLASS (klass); + object_class->finalize = gs_flatpak_transaction_finalize; + transaction_class->ready = _transaction_ready; + transaction_class->add_new_remote = _transaction_add_new_remote; + transaction_class->new_operation = _transaction_new_operation; + transaction_class->operation_done = _transaction_operation_done; + transaction_class->operation_error = _transaction_operation_error; + transaction_class->choose_remote_for_ref = _transaction_choose_remote_for_ref; + transaction_class->end_of_lifed = _transaction_end_of_lifed; + transaction_class->end_of_lifed_with_rebase = _transaction_end_of_lifed_with_rebase; + + signals[SIGNAL_REF_TO_APP] = + g_signal_new ("ref-to-app", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, G_TYPE_OBJECT, 1, G_TYPE_STRING); +} + +static void +gs_flatpak_transaction_init (GsFlatpakTransaction *self) +{ + self->refhash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_object_unref); +} + +FlatpakTransaction * +gs_flatpak_transaction_new (FlatpakInstallation *installation, + GCancellable *cancellable, + GError **error) +{ + GsFlatpakTransaction *self; + self = g_initable_new (GS_TYPE_FLATPAK_TRANSACTION, + cancellable, error, + "installation", installation, + NULL); + if (self == NULL) + return NULL; + return FLATPAK_TRANSACTION (self); +} |