Files
flatpak/app/flatpak-cli-transaction.c
Matthew Leeds a22dfbd7c1 Default to only choice in multiple choice prompts
This commit implements a feature in the multiple choice prompt that
mirrors the behavior in the yes/no prompt where it can default to "yes"
when the user only presses Enter. In the case of the multiple choice
prompt, it only defaults to the first choice on Enter if there's only
one option (e.g. you're asked if you want to install from the "flathub"
remote as opposed to being asked to choose between the "flathub" and
"eos-apps" remotes).

This feature can be turned off on a per-prompt basis if we want explicit
user input for something but I didn't find that necessary for any of the
existing prompts.

Also note that as with the yes/no prompt defaulting to yes on Enter,
this only applies for interactive terminals. If Flatpak is being run by
a script no choice will be made automatically.

This should save users unnecessary keystrokes, such as when they use
"flatpak install devhelp" and are asked to confirm that the Flathub
remote is the one to use (now they can just press Enter).

Closes: #2288
Approved by: matthiasclasen
2018-11-06 02:48:06 +00:00

802 lines
25 KiB
C

/*
* Copyright © 2018 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include "flatpak-cli-transaction.h"
#include "flatpak-transaction-private.h"
#include "flatpak-installation-private.h"
#include "flatpak-run-private.h"
#include "flatpak-table-printer.h"
#include "flatpak-utils-private.h"
#include "flatpak-error.h"
#include <glib/gi18n.h>
#include <sys/ioctl.h>
struct _FlatpakCliTransaction
{
FlatpakTransaction parent;
char *name;
gboolean disable_interaction;
gboolean stop_on_first_error;
gboolean is_user;
gboolean aborted;
GError *first_operation_error;
gboolean progress_initialized;
int progress_n_columns;
int progress_last_width;
};
struct _FlatpakCliTransactionClass
{
FlatpakCliTransactionClass parent_class;
};
G_DEFINE_TYPE (FlatpakCliTransaction, flatpak_cli_transaction, FLATPAK_TYPE_TRANSACTION);
static int
choose_remote_for_ref (FlatpakTransaction *transaction,
const char *for_ref,
const char *runtime_ref,
const char * const *remotes)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
int n_remotes = g_strv_length ((char **) remotes);
int chosen = -1;
const char *pref;
int i;
pref = strchr (for_ref, '/') + 1;
if (self->disable_interaction)
{
g_print (_("Required runtime for %s (%s) found in remote %s\n"),
pref, runtime_ref, remotes[0]);
chosen = 0;
}
else if (n_remotes == 1)
{
g_print (_("Required runtime for %s (%s) found in remote %s\n"),
pref, runtime_ref, remotes[0]);
if (flatpak_yes_no_prompt (TRUE, _("Do you want to install it?")))
chosen = 0;
}
else
{
g_print (_("Required runtime for %s (%s) found in remotes: %s\n"),
pref, runtime_ref, remotes[0]);
for (i = 0; remotes[i] != NULL; i++)
{
g_print ("%d) %s\n", i + 1, remotes[i]);
}
chosen = flatpak_number_prompt (TRUE, 0, n_remotes, _("Which do you want to install (0 to abort)?"));
chosen -= 1; /* convert from base-1 to base-0 (and -1 to abort) */
}
return chosen;
}
static gboolean
add_new_remote (FlatpakTransaction *transaction,
FlatpakTransactionRemoteReason reason,
const char *from_id,
const char *remote_name,
const char *url)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
if (self->disable_interaction)
{
g_print (_("Configuring %s as new remote '%s'"), url, remote_name);
return TRUE;
}
if (reason == FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO)
{
if (flatpak_yes_no_prompt (TRUE, /* default to yes on Enter */
_("The remote '%s', refered to by '%s' at location %s contains additional applications.\n"
"Should the remote be kept for future installations?"),
remote_name, from_id, url))
return TRUE;
}
else if (reason == FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS)
{
if (flatpak_yes_no_prompt (TRUE, /* default to yes on Enter */
_("The application %s depends on runtimes from:\n %s\n"
"Configure this as new remote '%s'"),
from_id, url, remote_name))
return TRUE;
}
return FALSE;
}
static char *
op_type_to_string (FlatpakTransactionOperationType operation_type)
{
switch (operation_type)
{
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
return _("install");
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
return _("update");
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
return _("install bundle");
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
return _("uninstall");
default:
return "Unknown type"; /* Should not happen */
}
}
#define BAR_LENGTH 20
#define BAR_CHARS " -=#"
static void
progress_changed_cb (FlatpakTransactionProgress *progress,
gpointer data)
{
FlatpakCliTransaction *cli = data;
g_autoptr(GString) str = g_string_new ("");
int i;
int n_full, remainder, partial;
int width, padded_width;
guint percent = flatpak_transaction_progress_get_progress (progress);
g_autofree char *status = flatpak_transaction_progress_get_status (progress);
if (!cli->progress_initialized)
{
struct winsize w;
cli->progress_n_columns = 80;
if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
cli->progress_n_columns = w.ws_col;
cli->progress_last_width = 0;
cli->progress_initialized = TRUE;
}
g_string_append (str, "[");
n_full = (BAR_LENGTH * percent) / 100;
remainder = percent - (n_full * 100 / BAR_LENGTH);
partial = (remainder * strlen (BAR_CHARS) * BAR_LENGTH) / 100;
for (i = 0; i < n_full; i++)
g_string_append_c (str, BAR_CHARS[strlen (BAR_CHARS) - 1]);
if (i < BAR_LENGTH)
{
g_string_append_c (str, BAR_CHARS[partial]);
i++;
}
for (; i < BAR_LENGTH; i++)
g_string_append (str, " ");
g_string_append (str, "] ");
g_string_append (str, status);
g_print ("\r");
width = MIN (strlen (str->str), cli->progress_n_columns);
padded_width = MAX (cli->progress_last_width, width);
cli->progress_last_width = width;
g_print ("%-*.*s", padded_width, padded_width, str->str);
}
static void
progress_done (FlatpakTransaction *transaction)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
if (self->progress_initialized)
g_print ("\n");
}
static void
new_operation (FlatpakTransaction *transaction,
FlatpakTransactionOperation *operation,
FlatpakTransactionProgress *progress)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
const char *pref;
g_autofree char *bundle_basename = NULL;
const char *ref = flatpak_transaction_operation_get_ref (operation);
const char *remote = flatpak_transaction_operation_get_remote (operation);
GFile *bundle = flatpak_transaction_operation_get_bundle_path (operation);
FlatpakTransactionOperationType operation_type = flatpak_transaction_operation_get_operation_type (operation);
pref = strchr (ref, '/') + 1;
switch (operation_type)
{
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
if (self->is_user)
g_print (_("Installing for user: %s from %s\n"), pref, remote);
else
g_print (_("Installing: %s from %s\n"), pref, remote);
break;
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
if (self->is_user)
g_print (_("Updating for user: %s from %s\n"), pref, remote);
else
g_print (_("Updating: %s from %s\n"), pref, remote);
break;
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
{
bundle_basename = g_file_get_basename (bundle);
if (self->is_user)
g_print (_("Installing for user: %s from bundle %s\n"), pref, bundle_basename);
else
g_print (_("Installing: %s from bundle %s\n"), pref, bundle_basename);
}
break;
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
if (self->is_user)
g_print (_("Uninstalling for user: %s\n"), pref);
else
g_print (_("Uninstalling: %s\n"), pref);
break;
default:
g_assert_not_reached ();
break;
}
self->progress_initialized = FALSE;
g_signal_connect (progress, "changed", G_CALLBACK (progress_changed_cb), self);
flatpak_transaction_progress_set_update_frequency (progress, FLATPAK_CLI_UPDATE_FREQUENCY);
}
static void
operation_done (FlatpakTransaction *transaction,
FlatpakTransactionOperation *operation,
const char *commit,
FlatpakTransactionResult details)
{
FlatpakTransactionOperationType operation_type = flatpak_transaction_operation_get_operation_type (operation);
g_autofree char *short_commit = g_strndup (commit, 12);
progress_done (transaction);
if (operation_type != FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
if (details & FLATPAK_TRANSACTION_RESULT_NO_CHANGE)
g_print (_("No updates.\n"));
else
g_print (_("Now at %s.\n"), short_commit);
}
}
static gboolean
operation_error (FlatpakTransaction *transaction,
FlatpakTransactionOperation *operation,
const GError *error,
FlatpakTransactionErrorDetails detail)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
FlatpakTransactionOperationType operation_type = flatpak_transaction_operation_get_operation_type (operation);
const char *ref = flatpak_transaction_operation_get_ref (operation);
const char *pref;
progress_done (transaction);
pref = strchr (ref, '/') + 1;
if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED))
{
g_printerr ("%s\n", error->message);
return TRUE;
}
if (detail & FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL)
{
g_printerr (_("Warning: Failed to %s %s: %s\n"),
op_type_to_string (operation_type), pref, error->message);
}
else
{
if (self->first_operation_error == NULL)
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to %s %s: "),
op_type_to_string (operation_type), pref);
if (self->stop_on_first_error)
return FALSE;
g_printerr (_("Error: Failed to %s %s: %s\n"),
op_type_to_string (operation_type), pref, error->message);
}
return TRUE; /* Continue */
}
static void
end_of_lifed (FlatpakTransaction *transaction,
const char *ref,
const char *reason,
const char *rebase)
{
if (rebase)
{
g_printerr (_("Warning: %s is end-of-life, in preference of %s\n"), ref, rebase);
}
else if (reason)
{
g_printerr (_("Warning: %s is end-of-life, with reason: %s\n"), ref, reason);
}
}
static int
cmpstringp (const void *p1, const void *p2)
{
return strcmp (*(char * const *) p1, *(char * const *) p2);
}
static void
append_permissions (GPtrArray *permissions,
GKeyFile *metadata,
GKeyFile *old_metadata,
const char *group)
{
g_auto(GStrv) options = g_key_file_get_string_list (metadata, FLATPAK_METADATA_GROUP_CONTEXT, group, NULL, NULL);
g_auto(GStrv) old_options = NULL;
int i;
if (options == NULL)
return;
qsort (options, g_strv_length (options), sizeof (const char *), cmpstringp);
if (old_metadata)
old_options = g_key_file_get_string_list (old_metadata, FLATPAK_METADATA_GROUP_CONTEXT, group, NULL, NULL);
for (i = 0; options[i] != NULL; i++)
{
const char *option = options[i];
if (option[0] == '!')
continue;
if (old_options && g_strv_contains ((const char * const *) old_options, option))
continue;
if (strcmp (group, FLATPAK_METADATA_KEY_DEVICES) == 0 && strcmp (option, "all") == 0)
option = "devices";
g_ptr_array_add (permissions, g_strdup (option));
}
}
static void
append_bus (GPtrArray *talk,
GPtrArray *own,
GKeyFile *metadata,
GKeyFile *old_metadata,
const char *group)
{
g_auto(GStrv) keys = NULL;
gsize i, keys_count;
keys = g_key_file_get_keys (metadata, group, &keys_count, NULL);
if (keys == NULL)
return;
qsort (keys, g_strv_length (keys), sizeof (const char *), cmpstringp);
for (i = 0; i < keys_count; i++)
{
const char *key = keys[i];
g_autofree char *value = g_key_file_get_string (metadata, group, key, NULL);
if (g_strcmp0 (value, "none") == 0)
continue;
if (old_metadata)
{
g_autofree char *old_value = g_key_file_get_string (old_metadata, group, key, NULL);
if (g_strcmp0 (old_value, value) == 0)
continue;
}
if (g_strcmp0 (value, "own") == 0)
g_ptr_array_add (own, g_strdup (key));
else
g_ptr_array_add (talk, g_strdup (key));
}
}
static void
append_tags (GPtrArray *tags_array,
GKeyFile *metadata,
GKeyFile *old_metadata)
{
gsize i, size = 0;
g_auto(GStrv) tags = g_key_file_get_string_list (metadata, FLATPAK_METADATA_GROUP_APPLICATION, "tags",
&size, NULL);
g_auto(GStrv) old_tags = NULL;
if (old_metadata)
old_tags = g_key_file_get_string_list (old_metadata, FLATPAK_METADATA_GROUP_APPLICATION, "tags",
NULL, NULL);
for (i = 0; i < size; i++)
{
const char *tag = tags[i];
if (old_tags == NULL || !g_strv_contains ((const char * const *)old_tags, tag))
g_ptr_array_add (tags_array, g_strdup (tag));
}
}
static void
print_perm_line (FlatpakTablePrinter *printer,
const char *title,
GPtrArray *items)
{
g_autoptr(GString) res = g_string_new (NULL);
int i;
if (items->len == 0)
return;
g_string_append_printf (res, " %s: ", title);
for (i = 0; i < items->len; i++)
{
if (i != 0)
g_string_append (res, ", ");
g_string_append (res, (char *) items->pdata[i]);
}
flatpak_table_printer_add_span (printer, res->str);
flatpak_table_printer_finish_row (printer);
}
static void
print_permissions (FlatpakTablePrinter *printer,
GKeyFile *metadata,
GKeyFile *old_metadata,
const char *ref)
{
g_autoptr(GPtrArray) permissions = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) session_bus_talk = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) session_bus_own = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) system_bus_talk = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) system_bus_own = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) tags = g_ptr_array_new_with_free_func (g_free);
if (metadata == NULL)
return;
/* Only apps have permissions */
if (!g_str_has_prefix (ref, "app/"))
return;
append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_SHARED);
append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_SOCKETS);
append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_DEVICES);
append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_FEATURES);
print_perm_line (printer,
old_metadata ? _("new permissions") : _("permissions"),
permissions);
append_permissions (files, metadata, old_metadata, FLATPAK_METADATA_KEY_FILESYSTEMS);
print_perm_line (printer,
old_metadata ? _("new file access") : _("file access"),
files);
append_bus (session_bus_talk, session_bus_own,
metadata, old_metadata, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY);
print_perm_line (printer,
old_metadata ? _("new dbus access") : _("dbus access"),
session_bus_talk);
print_perm_line (printer,
old_metadata ? _("new dbus ownership") : _("dbus ownership"),
session_bus_own);
append_bus (system_bus_talk, system_bus_own,
metadata, old_metadata, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY);
print_perm_line (printer,
old_metadata ? _("new system dbus access") : _("system dbus access"),
system_bus_talk);
print_perm_line (printer,
old_metadata ? _("new system dbus ownership") : _("system dbus ownership"),
system_bus_own);
append_tags (tags, metadata, old_metadata);
print_perm_line (printer,
old_metadata ? _("new tags") : _("tags"),
tags);
}
static gboolean
transaction_ready (FlatpakTransaction *transaction)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
GList *ops = flatpak_transaction_get_operations (transaction);
GList *l;
gboolean found_one;
FlatpakTablePrinter *printer = NULL;
if (ops == NULL)
return TRUE;
found_one = FALSE;
for (l = ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
const char *ref = flatpak_transaction_operation_get_ref (op);
const char *pref = strchr (ref, '/') + 1;
if (type != FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
continue;
if (!found_one)
g_print (_("Uninstalling from %s:\n"), self->name);
found_one = TRUE;
g_print ("%s\n", pref);
}
found_one = FALSE;
for (l = ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
const char *ref = flatpak_transaction_operation_get_ref (op);
const char *remote = flatpak_transaction_operation_get_remote (op);
const char *commit = flatpak_transaction_operation_get_commit (op);
GKeyFile *metadata = flatpak_transaction_operation_get_metadata (op);
const char *pref = strchr (ref, '/') + 1;
if (type != FLATPAK_TRANSACTION_OPERATION_INSTALL &&
type != FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
continue;
if (!found_one)
{
g_print (_("Installing in %s:\n"), self->name);
printer = flatpak_table_printer_new ();
found_one = TRUE;
}
flatpak_table_printer_add_column (printer, pref);
flatpak_table_printer_add_column (printer, remote);
flatpak_table_printer_add_column_len (printer, commit, 12);
flatpak_table_printer_finish_row (printer);
print_permissions (printer, metadata, NULL, ref);
}
if (printer)
{
flatpak_table_printer_print (printer);
flatpak_table_printer_free (printer);
printer = NULL;
}
found_one = FALSE;
for (l = ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
const char *ref = flatpak_transaction_operation_get_ref (op);
const char *remote = flatpak_transaction_operation_get_remote (op);
const char *commit = flatpak_transaction_operation_get_commit (op);
GKeyFile *metadata = flatpak_transaction_operation_get_metadata (op);
GKeyFile *old_metadata = flatpak_transaction_operation_get_old_metadata (op);
const char *pref = strchr (ref, '/') + 1;
if (type != FLATPAK_TRANSACTION_OPERATION_UPDATE)
continue;
if (!found_one)
{
g_print (_("Updating in %s:\n"), self->name);
printer = flatpak_table_printer_new ();
found_one = TRUE;
}
flatpak_table_printer_add_column (printer, pref);
flatpak_table_printer_add_column (printer, remote);
flatpak_table_printer_add_column_len (printer, commit, 12);
flatpak_table_printer_finish_row (printer);
print_permissions (printer, metadata, old_metadata, ref);
}
if (printer)
{
flatpak_table_printer_print (printer);
flatpak_table_printer_free (printer);
printer = NULL;
}
g_list_free_full (ops, g_object_unref);
if (!self->disable_interaction &&
!flatpak_yes_no_prompt (TRUE, _("Is this ok")))
return FALSE;
return TRUE;
}
static void
flatpak_cli_transaction_finalize (GObject *object)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (object);
if (self->first_operation_error)
g_error_free (self->first_operation_error);
g_free (self->name);
G_OBJECT_CLASS (flatpak_cli_transaction_parent_class)->finalize (object);
}
static void
flatpak_cli_transaction_init (FlatpakCliTransaction *self)
{
}
static void
flatpak_cli_transaction_class_init (FlatpakCliTransactionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FlatpakTransactionClass *transaction_class = FLATPAK_TRANSACTION_CLASS (klass);
object_class->finalize = flatpak_cli_transaction_finalize;
transaction_class->add_new_remote = add_new_remote;
transaction_class->ready = transaction_ready;
transaction_class->new_operation = new_operation;
transaction_class->operation_done = operation_done;
transaction_class->operation_error = operation_error;
transaction_class->choose_remote_for_ref = choose_remote_for_ref;
transaction_class->end_of_lifed = end_of_lifed;
}
FlatpakTransaction *
flatpak_cli_transaction_new (FlatpakDir *dir,
gboolean disable_interaction,
gboolean stop_on_first_error,
GError **error)
{
g_autoptr(FlatpakInstallation) installation = NULL;
g_autoptr(FlatpakCliTransaction) self = NULL;
installation = flatpak_installation_new_for_dir (dir, NULL, error);
if (installation == NULL)
return NULL;
self = g_initable_new (FLATPAK_TYPE_CLI_TRANSACTION,
NULL, error,
"installation", installation,
NULL);
if (self == NULL)
return NULL;
self->disable_interaction = disable_interaction;
self->stop_on_first_error = stop_on_first_error;
self->name = flatpak_dir_get_name (dir);
self->is_user = flatpak_dir_is_user (dir);
flatpak_transaction_add_default_dependency_sources (FLATPAK_TRANSACTION (self));
return (FlatpakTransaction *) g_steal_pointer (&self);
}
gboolean
flatpak_cli_transaction_add_install (FlatpakTransaction *transaction,
const char *remote,
const char *ref,
const char **subpaths,
GError **error)
{
g_autoptr(GError) local_error = NULL;
if (!flatpak_transaction_add_install (transaction, remote, ref, subpaths, &local_error))
{
if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
{
g_printerr (_("Skipping: %s\n"), local_error->message);
return TRUE;
}
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
return TRUE;
}
gboolean
flatpak_cli_transaction_run (FlatpakTransaction *transaction,
GCancellable *cancellable,
GError **error)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
g_autoptr(GError) local_error = NULL;
gboolean res;
res = flatpak_transaction_run (transaction, cancellable, &local_error);
/* If we got some weird error (i.e. not ABORTED because we chose to abort
on an error, report that */
if (!res)
{
if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED))
{
self->aborted = TRUE;
}
else
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
}
if (self->first_operation_error)
{
/* We always want to return an error if there was some kind of operation error,
as that causes the main CLI to return an error status. */
if (self->stop_on_first_error)
{
/* For the install/stop_on_first_error we return the first operation error,
as we have not yet printed it. */
g_propagate_error (error, g_steal_pointer (&self->first_operation_error));
return FALSE;
}
else
{
/* For updates/!stop_on_first_error we already printed all errors so we make up
a different one. */
return flatpak_fail (error, _("There were one or more errors"));
}
}
return TRUE;
}
gboolean
flatpak_cli_transaction_was_aborted (FlatpakTransaction *transaction)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
return self->aborted;
}