Files
flatpak/app/flatpak-cli-transaction.c
Simon McVittie ae2579637a app: Move terminal-related utility functions from common into app
These functions are to do with being an interactive, terminal-oriented
CLI/TUI, so it would be inappropriate for library code in libflatpak
to call them, and it would also be inappropriate for daemons like the
session and system helpers to call them.

In fact all calls to these were already isolated to app/, so we can
easily move the terminal-related utilities themselves into app/.

As well as shrinking libflatpak, this makes it obvious that the system
helper does not actually need to call flatpak_disable_fancy_output():
it does not link any code that would be affected by that API call.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2023-07-03 20:19:00 +02:00

1689 lines
56 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* 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-tty-utils-private.h"
#include "flatpak-utils-private.h"
#include "flatpak-error.h"
#include <glib/gi18n.h>
struct _FlatpakCliTransaction
{
FlatpakTransaction parent;
gboolean disable_interaction;
gboolean stop_on_first_error;
gboolean non_default_arch;
GError *first_operation_error;
GHashTable *eol_actions;
GHashTable *runtime_app_map;
GHashTable *extension_app_map;
int rows;
int cols;
int table_width;
int table_height;
int n_ops;
int op;
int op_progress;
gboolean installing;
gboolean updating;
gboolean uninstalling;
int download_col;
FlatpakTablePrinter *printer;
int progress_row;
char *progress_msg;
int speed_len;
gboolean did_interaction;
};
struct _FlatpakCliTransactionClass
{
FlatpakTransactionClass 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;
pref = strchr (for_ref, '/') + 1;
self->did_interaction = TRUE;
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
{
flatpak_format_choices ((const char **) remotes,
_("Required runtime for %s (%s) found in remotes:"),
pref, runtime_ref);
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);
self->did_interaction = TRUE;
if (self->disable_interaction)
{
g_print (_("Configuring %s as new remote '%s'\n"), 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', referred 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 void
install_authenticator (FlatpakTransaction *old_transaction,
const char *remote,
const char *ref)
{
FlatpakCliTransaction *old_cli = FLATPAK_CLI_TRANSACTION (old_transaction);
g_autoptr(FlatpakTransaction) transaction2 = NULL;
g_autoptr(GError) local_error = NULL;
FlatpakInstallation *installation = flatpak_transaction_get_installation (old_transaction);
FlatpakDir *dir = flatpak_installation_get_dir (installation, NULL);
if (dir == NULL)
{
/* This should not happen */
g_warning ("No dir in install_authenticator");
return;
}
old_cli->did_interaction = TRUE;
transaction2 = flatpak_cli_transaction_new (dir, old_cli->disable_interaction, TRUE, FALSE, &local_error);
if (transaction2 == NULL)
{
g_printerr ("Unable to install authenticator: %s\n", local_error->message);
return;
}
g_print ("Installing required authenticator for remote %s\n", remote);
if (!flatpak_transaction_add_install (transaction2, remote, ref, NULL, &local_error))
{
if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
g_printerr ("Unable to install authenticator: %s\n", local_error->message);
return;
}
if (!flatpak_transaction_run (transaction2, NULL, &local_error))
{
if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED))
g_printerr ("Unable to install authenticator: %s\n", local_error->message);
return;
}
return;
}
static gboolean
redraw (FlatpakCliTransaction *self)
{
int top;
int row;
int current_row;
int current_col;
int skip;
/* We may have resized and thus repositioned the cursor since last redraw */
flatpak_get_window_size (&self->rows, &self->cols);
if (flatpak_get_cursor_pos (&current_row, &current_col))
{
/* We're currently displaying the last row of the table, extept the
very first time where the user pressed return for the prompt causing us
to scroll down one extra row */
top = current_row - self->table_height + 1;
if (top > 0)
{
row = top;
skip = 0;
}
else
{
row = 1;
skip = 1 - top;
}
g_print (FLATPAK_ANSI_ROW_N FLATPAK_ANSI_CLEAR, row);
// we update table_height and end_row here, since we might have added to the table
flatpak_table_printer_print_full (self->printer, skip, self->cols,
&self->table_height, &self->table_width);
return TRUE;
}
return FALSE;
}
static void
set_op_progress (FlatpakCliTransaction *self,
FlatpakTransactionOperation *op,
const char *progress)
{
if (flatpak_fancy_output ())
{
int row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "row"));
g_autofree char *cell = g_strdup_printf ("[%s]", progress);
flatpak_table_printer_set_cell (self->printer, row, 1, cell);
}
}
static void
spin_op_progress (FlatpakCliTransaction *self,
FlatpakTransactionOperation *op)
{
const char *p[] = {
"|",
"/",
"",
"\\",
};
set_op_progress (self, op, p[self->op_progress++ % G_N_ELEMENTS (p)]);
}
static char *
format_duration (guint64 duration)
{
int h, m, s;
m = duration / 60;
s = duration % 60;
h = m / 60;
m = m % 60;
if (h > 0)
return g_strdup_printf ("%02d:%02d:%02d", h, m, s);
else
return g_strdup_printf ("%02d:%02d", m, s);
}
static void
progress_changed_cb (FlatpakTransactionProgress *progress,
gpointer data)
{
FlatpakCliTransaction *cli = data;
FlatpakTransaction *self = FLATPAK_TRANSACTION (cli);
g_autoptr(FlatpakTransactionOperation) op = flatpak_transaction_get_current_operation (self);
g_autoptr(GString) str = g_string_new ("");
int i;
int n_full, partial;
g_autofree char *speed = NULL;
int bar_length;
const char *partial_blocks[] = {
" ",
"",
"",
"",
"",
"",
"",
"",
};
const char *full_block = "";
guint percent = flatpak_transaction_progress_get_progress (progress);
guint64 start_time = flatpak_transaction_progress_get_start_time (progress);
guint64 elapsed_time = (g_get_monotonic_time () - start_time) / G_USEC_PER_SEC;
guint64 transferred = flatpak_transaction_progress_get_bytes_transferred (progress);
guint64 max = flatpak_transaction_operation_get_download_size (op);
if (elapsed_time > 0)
{
g_autofree char *formatted_bytes_sec = g_format_size (transferred / elapsed_time);
g_autofree char *remaining = NULL;
if (elapsed_time > 3 && percent > 0)
{
guint64 total_time = elapsed_time * 100 / (double) percent;
remaining = format_duration (total_time - elapsed_time);
}
speed = g_strdup_printf ("%s/s%s%s", formatted_bytes_sec, remaining ? " " : "", remaining ? remaining : "");
cli->speed_len = MAX (cli->speed_len, strlen (speed) + 2);
}
spin_op_progress (cli, op);
bar_length = MIN (20, cli->table_width - (strlen (cli->progress_msg) + 6 + cli->speed_len));
n_full = (bar_length * percent) / 100;
partial = (((bar_length * percent) % 100) * G_N_ELEMENTS (partial_blocks)) / 100;
/* The above should guarantee this: */
g_assert (partial >= 0);
g_assert (partial < G_N_ELEMENTS (partial_blocks));
g_string_append (str, cli->progress_msg);
g_string_append (str, " ");
if (flatpak_fancy_output ())
g_string_append (str, FLATPAK_ANSI_FAINT_ON);
for (i = 0; i < n_full; i++)
g_string_append (str, full_block);
if (i < bar_length)
{
g_string_append (str, partial_blocks[partial]);
i++;
}
if (flatpak_fancy_output ())
g_string_append (str, FLATPAK_ANSI_FAINT_OFF);
for (; i < bar_length; i++)
g_string_append (str, " ");
g_string_append (str, " ");
g_string_append_printf (str, "%3d%%", percent);
if (speed)
g_string_append_printf (str, " %s", speed);
if (flatpak_fancy_output ())
{
flatpak_table_printer_set_cell (cli->printer, cli->progress_row, 0, str->str);
if (flatpak_transaction_operation_get_operation_type (op) != FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
{
g_autofree char *formatted_max = NULL;
g_autofree char *formatted = NULL;
g_autofree char *text = NULL;
int row;
// avoid "bytes"
formatted = transferred < 1000 ? g_format_size (1000) : g_format_size (transferred);
formatted_max = max < 1000 ? g_format_size (1000) : g_format_size (max);
text = g_strdup_printf ("%s / %s", formatted, formatted_max);
row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "row"));
flatpak_table_printer_set_decimal_cell (cli->printer, row, cli->download_col, text);
}
if (!redraw (cli))
g_print ("\r%s", str->str); /* redraw failed, just update the progress */
}
else
g_print ("%s\n", str->str);
}
static void
set_progress (FlatpakCliTransaction *self,
const char *text)
{
flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text);
}
static void
new_operation (FlatpakTransaction *transaction,
FlatpakTransactionOperation *op,
FlatpakTransactionProgress *progress)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op);
g_autofree char *text = NULL;
self->op++;
self->op_progress = 0;
switch (op_type)
{
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
if (self->n_ops == 1)
text = g_strdup (_("Installing…"));
else
text = g_strdup_printf (_("Installing %d/%d…"), self->op, self->n_ops);
break;
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
if (self->n_ops == 1)
text = g_strdup (_("Updating…"));
else
text = g_strdup_printf (_("Updating %d/%d…"), self->op, self->n_ops);
break;
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
if (self->n_ops == 1)
text = g_strdup (_("Uninstalling…"));
else
text = g_strdup_printf (_("Uninstalling %d/%d…"), self->op, self->n_ops);
break;
default:
g_assert_not_reached ();
break;
}
if (flatpak_fancy_output ())
{
set_progress (self, text);
spin_op_progress (self, op);
redraw (self);
}
else
g_print ("%s\n", text);
g_free (self->progress_msg);
self->progress_msg = g_steal_pointer (&text);
g_signal_connect (progress, "changed", G_CALLBACK (progress_changed_cb), self);
flatpak_transaction_progress_set_update_frequency (progress, FLATPAK_CLI_UPDATE_INTERVAL_MS);
}
static void
operation_done (FlatpakTransaction *transaction,
FlatpakTransactionOperation *op,
const char *commit,
FlatpakTransactionResult details)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op);
if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
set_op_progress (self, op, FLATPAK_ANSI_GREEN "-" FLATPAK_ANSI_COLOR_RESET);
else
set_op_progress (self, op, FLATPAK_ANSI_GREEN "" FLATPAK_ANSI_COLOR_RESET);
if (flatpak_fancy_output ())
redraw (self);
}
static gboolean
operation_error (FlatpakTransaction *transaction,
FlatpakTransactionOperation *op,
const GError *error,
FlatpakTransactionErrorDetails detail)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op);
const char *ref = flatpak_transaction_operation_get_ref (op);
g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL);
gboolean non_fatal = (detail & FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL) != 0;
g_autofree char *text = NULL;
const char *on = "";
const char *off = "";
if (flatpak_fancy_output ())
{
on = FLATPAK_ANSI_BOLD_ON;
off = FLATPAK_ANSI_BOLD_OFF;
}
if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED))
{
set_op_progress (self, op, "");
text = g_strdup_printf (_("Info: %s was skipped"), flatpak_ref_get_name (rref));
if (flatpak_fancy_output ())
{
flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text);
self->progress_row++;
flatpak_table_printer_add_span (self->printer, "");
flatpak_table_printer_finish_row (self->printer);
redraw (self);
}
else
g_print ("%s\n", text);
return TRUE;
}
set_op_progress (self, op, "");
/* Here we go to great lengths not to split the sentences. See
* https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences
*/
if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
{
if (non_fatal)
text = g_strdup_printf (_("Warning: %s%s%s already installed"),
on, flatpak_ref_get_name (rref), off);
else
text = g_strdup_printf (_("Error: %s%s%s already installed"),
on, flatpak_ref_get_name (rref), off);
}
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
{
if (non_fatal)
text = g_strdup_printf (_("Warning: %s%s%s not installed"),
on, flatpak_ref_get_name (rref), off);
else
text = g_strdup_printf (_("Error: %s%s%s not installed"),
on, flatpak_ref_get_name (rref), off);
}
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NEED_NEW_FLATPAK))
{
if (non_fatal)
text = g_strdup_printf (_("Warning: %s%s%s needs a later flatpak version"),
on, flatpak_ref_get_name (rref), off);
else
text = g_strdup_printf (_("Error: %s%s%s needs a later flatpak version"),
on, flatpak_ref_get_name (rref), off);
}
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_OUT_OF_SPACE))
{
if (non_fatal)
text = g_strdup (_("Warning: Not enough disk space to complete this operation"));
else
text = g_strdup (_("Error: Not enough disk space to complete this operation"));
}
else if (error)
{
if (non_fatal)
text = g_strdup_printf (_("Warning: %s"), error->message);
else
text = g_strdup_printf (_("Error: %s"), error->message);
}
else
text = g_strdup ("(internal error, please report)");
if (!non_fatal && self->first_operation_error == NULL)
{
/* Here we go to great lengths not to split the sentences. See
* https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences
*/
switch (op_type)
{
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to install %s%s%s: "),
on, flatpak_ref_get_name (rref), off);
break;
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to update %s%s%s: "),
on, flatpak_ref_get_name (rref), off);
break;
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to install bundle %s%s%s: "),
on, flatpak_ref_get_name (rref), off);
break;
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to uninstall %s%s%s: "),
on, flatpak_ref_get_name (rref), off);
break;
default:
g_assert_not_reached ();
}
}
if (flatpak_fancy_output ())
{
flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text);
self->progress_row++;
flatpak_table_printer_add_span (self->printer, "");
flatpak_table_printer_finish_row (self->printer);
redraw (self);
}
else
g_printerr ("%s\n", text);
if (!non_fatal && self->stop_on_first_error)
return FALSE;
return TRUE; /* Continue */
}
static gboolean
webflow_start (FlatpakTransaction *transaction,
const char *remote,
const char *url,
GVariant *options,
guint id)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
const char *browser;
g_autoptr(GError) local_error = NULL;
const char *args[3] = { NULL, url, NULL };
self->did_interaction = TRUE;
if (!self->disable_interaction)
{
g_print (_("Authentication required for remote '%s'\n"), remote);
if (!flatpak_yes_no_prompt (TRUE, _("Open browser?")))
return FALSE;
}
/* Allow hard overrides with $BROWSER */
browser = g_getenv ("BROWSER");
if (browser != NULL)
{
args[0] = browser;
if (!g_spawn_async (NULL, (char **)args, NULL, G_SPAWN_SEARCH_PATH,
NULL, NULL, NULL, &local_error))
{
g_printerr ("Failed to start browser %s: %s\n", browser, local_error->message);
return FALSE;
}
}
else
{
if (!g_app_info_launch_default_for_uri (url, NULL, &local_error))
{
g_printerr ("Failed to show url: %s\n", local_error->message);
return FALSE;
}
}
g_print ("Waiting for browser...\n");
return TRUE;
}
static void
webflow_done (FlatpakTransaction *transaction,
GVariant *options,
guint id)
{
g_print ("Browser done\n");
}
static gboolean
basic_auth_start (FlatpakTransaction *transaction,
const char *remote,
const char *realm,
GVariant *options,
guint id)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
char *user, *password, *previous_error = NULL;
if (self->disable_interaction)
return FALSE;
self->did_interaction = TRUE;
if (g_variant_lookup (options, "previous-error", "&s", &previous_error))
g_print ("%s\n", previous_error);
g_print (_("Login required remote %s (realm %s)\n"), remote, realm);
user = flatpak_prompt (FALSE, _("User"));
if (user == NULL)
return FALSE;
password = flatpak_password_prompt (_("Password"));
if (password == NULL)
return FALSE;
flatpak_transaction_complete_basic_auth (transaction, id, user, password, NULL);
return TRUE;
}
typedef enum {
EOL_UNDECIDED,
EOL_IGNORE, /* Don't do anything, we already printed a warning */
EOL_NO_REBASE, /* Choose to not rebase */
EOL_REBASE, /* Choose to rebase */
} EolAction;
static void
print_eol_info_message (FlatpakDir *dir,
FlatpakDecomposed *ref,
const char *ref_name,
const char *rebased_to_ref,
const char *reason)
{
gboolean is_pinned = flatpak_dir_ref_is_pinned (dir, flatpak_decomposed_get_ref (ref));
g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);
const char *on = "";
const char *off = "";
if (flatpak_fancy_output ())
{
on = FLATPAK_ANSI_BOLD_ON;
off = FLATPAK_ANSI_BOLD_OFF;
}
/* Here we go to great lengths not to split the sentences. See
* https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences
*/
if (rebased_to_ref)
{
g_autoptr(FlatpakDecomposed) eolr_decomposed = NULL;
g_autofree char *eolr_name = NULL;
const char *eolr_branch;
eolr_decomposed = flatpak_decomposed_new_from_ref (rebased_to_ref, NULL);
/* These are guarantees from FlatpakTransaction */
g_assert (eolr_decomposed != NULL);
g_assert (flatpak_decomposed_get_kind (ref) == flatpak_decomposed_get_kind (eolr_decomposed));
eolr_name = flatpak_decomposed_dup_id (eolr_decomposed);
eolr_branch = flatpak_decomposed_get_branch (eolr_decomposed);
if (is_pinned)
{
/* Only runtimes can be pinned */
g_print (_("\nInfo: (pinned) runtime %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"),
on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off);
}
else
{
if (flatpak_decomposed_is_runtime (ref))
g_print (_("\nInfo: runtime %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"),
on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off);
else
g_print (_("\nInfo: app %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"),
on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off);
}
}
else if (reason)
{
g_autofree char *escaped_reason = flatpak_escape_string (reason,
FLATPAK_ESCAPE_ALLOW_NEWLINES |
FLATPAK_ESCAPE_DO_NOT_QUOTE);
if (is_pinned)
{
/* Only runtimes can be pinned */
g_print (_("\nInfo: (pinned) runtime %s%s%s branch %s%s%s is end-of-life, with reason:\n"),
on, ref_name, off, on, ref_branch, off);
}
else
{
if (flatpak_decomposed_is_runtime (ref))
g_print (_("\nInfo: runtime %s%s%s branch %s%s%s is end-of-life, with reason:\n"),
on, ref_name, off, on, ref_branch, off);
else
g_print (_("\nInfo: app %s%s%s branch %s%s%s is end-of-life, with reason:\n"),
on, ref_name, off, on, ref_branch, off);
}
g_print (" %s\n", escaped_reason);
}
}
static void
check_current_transaction_for_dependent_apps (GPtrArray *apps,
FlatpakTransaction *transaction,
FlatpakDecomposed *ref)
{
g_autoptr(FlatpakTransactionOperation) ref_op = NULL;
GPtrArray *related_ops;
ref_op = flatpak_transaction_get_operation_for_ref (transaction, NULL, flatpak_decomposed_get_ref (ref), NULL);
g_assert (ref_op != NULL);
/* Get the related ops to find any apps that use @ref as a runtime or extension */
related_ops = flatpak_transaction_operation_get_related_to_ops (ref_op);
if (related_ops == NULL)
return;
for (int i = 0; i < related_ops->len; i++)
{
FlatpakTransactionOperation *related_op = g_ptr_array_index (related_ops, i);
const char *related_op_ref = flatpak_transaction_operation_get_ref (related_op);
g_autoptr(FlatpakDecomposed) related_op_decomposed = flatpak_decomposed_new_from_ref (related_op_ref, NULL);
if (related_op_decomposed == NULL)
continue;
if (flatpak_decomposed_id_is_subref (related_op_decomposed))
continue;
/* Recurse in case @ref was a runtime extension. We need to check since a
* runtime can have a runtime extension in its related ops in the
* extra-data case, so if we recurse unconditionally it could be infinite
* recursion.
*/
if (flatpak_decomposed_is_runtime (related_op_decomposed))
{
GKeyFile *metadata = flatpak_transaction_operation_get_metadata (ref_op);
if (g_key_file_has_group (metadata, FLATPAK_METADATA_GROUP_EXTENSION_OF))
check_current_transaction_for_dependent_apps (apps, transaction, related_op_decomposed);
}
else if (!g_ptr_array_find_with_equal_func (apps, related_op_decomposed, (GEqualFunc)flatpak_decomposed_equal, NULL))
g_ptr_array_add (apps, g_steal_pointer (&related_op_decomposed));
}
}
static GPtrArray *
find_reverse_dep_apps (FlatpakTransaction *transaction,
FlatpakDir *dir,
FlatpakDecomposed *ref,
gboolean *out_is_extension)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
g_autoptr(GPtrArray) apps = NULL;
g_autoptr(GError) local_error = NULL;
g_assert (out_is_extension);
*out_is_extension = flatpak_dir_is_runtime_extension (dir, ref);
if (*out_is_extension)
{
/* Find apps which are using the ref as an extension directly or as an
* extension of their runtime.
*/
apps = flatpak_dir_list_app_refs_with_runtime_extension (dir,
&self->runtime_app_map,
&self->extension_app_map,
ref, NULL, &local_error);
if (apps == NULL)
{
g_info ("Unable to list apps using extension %s: %s\n",
flatpak_decomposed_get_ref (ref), local_error->message);
return NULL;
}
}
else
{
/* Find any apps using the runtime directly */
apps = flatpak_dir_list_app_refs_with_runtime (dir, &self->runtime_app_map, ref,
NULL, &local_error);
if (apps == NULL)
{
g_info ("Unable to find apps using runtime %s: %s\n",
flatpak_decomposed_get_ref (ref), local_error->message);
return NULL;
}
}
/* Also check the current transaction since it's possible the EOL ref
* and/or any app(s) that depend on it are not installed. It's also
* possible the current transaction updates one of the apps to a
* newer runtime but we don't handle that yet
* (https://github.com/flatpak/flatpak/issues/4832)
*/
check_current_transaction_for_dependent_apps (apps, transaction, ref);
return g_steal_pointer (&apps);
}
static gboolean
end_of_lifed_with_rebase (FlatpakTransaction *transaction,
const char *remote,
const char *ref_str,
const char *reason,
const char *rebased_to_ref,
const char **previous_ids)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
g_autoptr(FlatpakDecomposed) ref = flatpak_decomposed_new_from_ref (ref_str, NULL);
g_autofree char *name = NULL;
EolAction action = EOL_UNDECIDED;
EolAction old_action = EOL_UNDECIDED;
gboolean can_rebase = rebased_to_ref != NULL && remote != NULL;
FlatpakInstallation *installation = flatpak_transaction_get_installation (transaction);
FlatpakDir *dir = flatpak_installation_get_dir (installation, NULL);
if (ref == NULL)
return FALSE; /* Shouldn't happen, the ref should be valid */
name = flatpak_decomposed_dup_id (ref);
self->did_interaction = TRUE;
if (flatpak_decomposed_id_is_subref (ref))
{
GLNX_HASH_TABLE_FOREACH_KV (self->eol_actions, FlatpakDecomposed *, eoled_ref, gpointer, value)
{
guint old_eol_action = GPOINTER_TO_UINT (value);
if (flatpak_decomposed_id_is_subref_of (ref, eoled_ref))
{
old_action = old_eol_action; /* Do the same */
break;
}
}
}
if (old_action != EOL_UNDECIDED)
{
switch (old_action)
{
default:
case EOL_IGNORE:
if (!can_rebase)
action = EOL_IGNORE;
/* Else, ask if we want to rebase */
break;
case EOL_REBASE:
case EOL_NO_REBASE:
if (can_rebase)
action = old_action;
else
action = EOL_IGNORE;
}
}
if (action == EOL_UNDECIDED)
{
action = EOL_IGNORE;
print_eol_info_message (dir, ref, name, rebased_to_ref, reason);
if (flatpak_decomposed_is_runtime (ref) && !rebased_to_ref)
{
gboolean is_extension;
g_autoptr(GPtrArray) apps = find_reverse_dep_apps (transaction, dir, ref, &is_extension);
if (apps && apps->len > 0)
{
if (is_extension)
g_print (_("Info: applications using this extension:\n"));
else
g_print (_("Info: applications using this runtime:\n"));
g_print (" ");
for (guint i = 0; i < apps->len; i++)
{
FlatpakDecomposed *app_ref = g_ptr_array_index (apps, i);
g_autofree char *id = flatpak_decomposed_dup_id (app_ref);
if (i != 0)
g_print (", ");
g_print ("%s", id);
}
g_print ("\n");
}
}
if (rebased_to_ref && remote)
{
/* The context for this prompt is in print_eol_info_message() */
if (self->disable_interaction ||
flatpak_yes_no_prompt (TRUE, _("Replace?")))
{
if (self->disable_interaction)
g_print (_("Updating to rebased version\n"));
action = EOL_REBASE;
}
else
action = EOL_NO_REBASE;
}
}
else
{
g_info ("%s is end-of-life, using action from parent ref", name);
}
/* Cache for later comparison and reuse */
g_hash_table_insert (self->eol_actions, flatpak_decomposed_ref (ref), GUINT_TO_POINTER (action));
if (action == EOL_REBASE)
{
g_autoptr(GError) error = NULL;
if (!flatpak_transaction_add_rebase_and_uninstall (transaction, remote, rebased_to_ref, ref_str, NULL, previous_ids, &error))
{
g_propagate_prefixed_error (&self->first_operation_error,
g_error_copy (error),
_("Failed to rebase %s to %s: "),
name, rebased_to_ref);
return FALSE;
}
return TRUE; /* skip install/update op of end-of-life ref */
}
else /* IGNORE or NO_REBASE */
return FALSE;
}
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 (int idx,
GPtrArray *items,
int cols)
{
g_autoptr(GString) res = g_string_new (NULL);
g_autofree char *escaped_first_perm = NULL;
int i;
escaped_first_perm = flatpak_escape_string (items->pdata[0], FLATPAK_ESCAPE_DEFAULT);
g_string_append_printf (res, " [%d] %s", idx, escaped_first_perm);
for (i = 1; i < items->len; i++)
{
g_autofree char *escaped = flatpak_escape_string (items->pdata[i],
FLATPAK_ESCAPE_DEFAULT);
char *p;
int len;
p = strrchr (res->str, '\n');
if (!p)
p = res->str;
len = (res->str + strlen (res->str)) - p;
if (len + strlen (escaped) + 2 >= cols)
g_string_append_printf (res, ",\n %s", escaped);
else
g_string_append_printf (res, ", %s", escaped);
}
g_print ("%s\n", res->str);
}
static void
print_permissions (FlatpakCliTransaction *self,
const char *ref,
GKeyFile *metadata,
GKeyFile *old_metadata)
{
g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL);
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);
g_autoptr(FlatpakTablePrinter) printer = NULL;
int max_permission_width;
int n_permission_cols;
int i, j;
int rows, cols;
int table_rows, table_cols;
const char *on = "";
const char *off = "";
if (flatpak_fancy_output ())
{
on = FLATPAK_ANSI_BOLD_ON;
off = FLATPAK_ANSI_BOLD_OFF;
}
if (metadata == NULL)
return;
/* Only apps have permissions */
if (flatpak_ref_get_kind (rref) != FLATPAK_REF_KIND_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);
append_permissions (files, metadata, old_metadata, FLATPAK_METADATA_KEY_FILESYSTEMS);
append_bus (session_bus_talk, session_bus_own,
metadata, old_metadata, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY);
append_bus (system_bus_talk, system_bus_own,
metadata, old_metadata, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY);
append_tags (tags, metadata, old_metadata);
j = 1;
if (files->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("file access [%d]", j++));
if (session_bus_talk->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("dbus access [%d]", j++));
if (session_bus_own->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("bus ownership [%d]", j++));
if (system_bus_talk->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("system dbus access [%d]", j++));
if (system_bus_own->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("system bus ownership [%d]", j++));
if (tags->len > 0)
g_ptr_array_add (permissions, g_strdup_printf ("tags [%d]", j++));
/* Early exit if no (or no new) permissions */
if (permissions->len == 0)
return;
g_print ("\n");
if (old_metadata)
g_print (_("New %s%s%s permissions:"), on, flatpak_ref_get_name (rref), off);
else
g_print (_("%s%s%s permissions:"), on, flatpak_ref_get_name (rref), off);
g_print ("\n");
flatpak_get_window_size (&rows, &cols);
max_permission_width = 0;
for (i = 0; i < permissions->len; i++)
max_permission_width = MAX (max_permission_width, strlen (g_ptr_array_index (permissions, i)));
/* At least 4 columns, but more if we're guaranteed to fit */
n_permission_cols = MAX (4, cols / (max_permission_width + 4));
printer = flatpak_table_printer_new ();
for (i = 0; i < permissions->len; i++)
{
char *perm = g_ptr_array_index (permissions, i);
if (i % n_permission_cols == 0)
{
g_autofree char *text = NULL;
if (i > 0)
flatpak_table_printer_finish_row (printer);
text = g_strdup_printf (" %s", perm);
flatpak_table_printer_add_column (printer, text);
}
else
flatpak_table_printer_add_column (printer, perm);
}
flatpak_table_printer_finish_row (printer);
for (i = 0; i < n_permission_cols; i++)
flatpak_table_printer_set_column_expand (printer, i, TRUE);
flatpak_table_printer_print_full (printer, 0, cols, &table_rows, &table_cols);
g_print ("\n\n");
j = 1;
if (files->len > 0)
print_perm_line (j++, files, cols);
if (session_bus_talk->len > 0)
print_perm_line (j++, session_bus_talk, cols);
if (session_bus_own->len > 0)
print_perm_line (j++, session_bus_own, cols);
if (system_bus_talk->len > 0)
print_perm_line (j++, system_bus_talk, cols);
if (system_bus_own->len > 0)
print_perm_line (j++, system_bus_own, cols);
if (tags->len > 0)
print_perm_line (j++, tags, cols);
}
static void
message_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (user_data);
g_autofree char *text = NULL;
text = g_strconcat (_("Warning: "), message, NULL);
if (flatpak_fancy_output ())
{
flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text);
self->progress_row++;
flatpak_table_printer_add_span (self->printer, "");
flatpak_table_printer_finish_row (self->printer);
redraw (self);
}
else
g_print ("%s\n", text);
}
static gboolean
transaction_ready_pre_auth (FlatpakTransaction *transaction)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
GList *ops = flatpak_transaction_get_operations (transaction);
GList *l;
int i;
FlatpakTablePrinter *printer;
const char *op_shorthand[] = { "i", "u", "i", "r" };
/* These caches may no longer be valid once the transaction runs */
g_clear_pointer (&self->runtime_app_map, g_hash_table_unref);
g_clear_pointer (&self->extension_app_map, g_hash_table_unref);
if (ops == NULL)
return TRUE;
self->n_ops = g_list_length (ops);
for (l = ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
switch (type)
{
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
self->uninstalling = TRUE;
break;
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
self->installing = TRUE;
break;
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
self->updating = TRUE;
break;
default:;
}
}
/* first, show permissions */
for (l = ops; l != NULL; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
if (type == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE ||
type == FLATPAK_TRANSACTION_OPERATION_UPDATE)
{
const char *ref = flatpak_transaction_operation_get_ref (op);
GKeyFile *metadata = flatpak_transaction_operation_get_metadata (op);
GKeyFile *old_metadata = flatpak_transaction_operation_get_old_metadata (op);
print_permissions (self, ref, metadata, old_metadata);
}
}
g_print ("\n");
printer = self->printer = flatpak_table_printer_new ();
i = 0;
flatpak_table_printer_set_column_title (printer, i++, " ");
flatpak_table_printer_set_column_title (printer, i++, " ");
flatpak_table_printer_set_column_expand (printer, i, TRUE);
flatpak_table_printer_set_column_title (printer, i++, _("ID"));
flatpak_table_printer_set_column_expand (printer, i, TRUE);
if (!self->non_default_arch)
{
flatpak_table_printer_set_column_skip_unique (printer, i, TRUE);
flatpak_table_printer_set_column_skip_unique_string (printer, i, flatpak_get_arch ());
}
flatpak_table_printer_set_column_title (printer, i++, _("Arch"));
flatpak_table_printer_set_column_expand (printer, i, TRUE);
flatpak_table_printer_set_column_title (printer, i++, _("Branch"));
flatpak_table_printer_set_column_expand (printer, i, TRUE);
/* translators: This is short for operation, the title of a one-char column */
flatpak_table_printer_set_column_title (printer, i++, _("Op"));
if (self->installing || self->updating)
{
g_autofree char *text1 = NULL;
g_autofree char *text2 = NULL;
g_autofree char *text = NULL;
int size;
flatpak_table_printer_set_column_expand (printer, i, TRUE);
flatpak_table_printer_set_column_title (printer, i++, _("Remote"));
self->download_col = i;
/* Avoid resizing the download column too much,
* by making the title as long as typical content
*/
text1 = g_strdup_printf ("< 999.9 kB (%s)", _("partial"));
text2 = g_strdup_printf (" 123.4 MB / 999.9 MB");
size = MAX (strlen (text1), strlen (text2));
text = g_strdup_printf ("%-*s", size, _("Download"));
flatpak_table_printer_set_column_title (printer, i++, text);
}
for (l = ops, i = 1; l != NULL; l = l->next, i++)
{
FlatpakTransactionOperation *op = l->data;
FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op);
FlatpakDecomposed *ref = flatpak_transaction_operation_get_decomposed (op);
const char *remote = flatpak_transaction_operation_get_remote (op);
g_autofree char *id = flatpak_decomposed_dup_id (ref);
const char *branch = flatpak_decomposed_get_branch (ref);
g_autofree char *arch = flatpak_decomposed_dup_arch (ref);
g_autofree char *rownum = g_strdup_printf ("%2d.", i);
flatpak_table_printer_add_column (printer, rownum);
flatpak_table_printer_add_column (printer, " ");
flatpak_table_printer_add_column (printer, id);
flatpak_table_printer_add_column (printer, arch);
flatpak_table_printer_add_column (printer, branch);
flatpak_table_printer_add_column (printer, op_shorthand[type]);
if (type == FLATPAK_TRANSACTION_OPERATION_INSTALL ||
type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE ||
type == FLATPAK_TRANSACTION_OPERATION_UPDATE)
{
guint64 download_size;
g_autofree char *formatted = NULL;
g_autofree char *text = NULL;
const char *prefix;
download_size = flatpak_transaction_operation_get_download_size (op);
formatted = g_format_size (download_size);
if (download_size > 0)
prefix = "< ";
else
prefix = "";
flatpak_table_printer_add_column (printer, remote);
if (flatpak_transaction_operation_get_subpaths (op) != NULL)
text = g_strdup_printf ("%s%s (%s)", prefix, formatted, _("partial"));
else
text = g_strdup_printf ("%s%s", prefix, formatted);
flatpak_table_printer_add_decimal_column (printer, text);
}
g_object_set_data (G_OBJECT (op), "row", GINT_TO_POINTER (flatpak_table_printer_get_current_row (printer)));
flatpak_table_printer_finish_row (printer);
}
flatpak_get_window_size (&self->rows, &self->cols);
g_print ("\n");
flatpak_table_printer_print_full (printer, 0, self->cols,
&self->table_height, &self->table_width);
g_print ("\n");
if (!self->disable_interaction)
{
g_autoptr(FlatpakInstallation) installation = flatpak_transaction_get_installation (transaction);
const char *name;
const char *id;
gboolean ret;
g_print ("\n");
name = flatpak_installation_get_display_name (installation);
id = flatpak_installation_get_id (installation);
if (flatpak_installation_get_is_user (installation))
ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the user installation?"));
else if (g_strcmp0 (id, SYSTEM_DIR_DEFAULT_ID) == 0)
ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the system installation?"));
else
ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the %s?"), name);
if (!ret)
{
g_list_free_full (ops, g_object_unref);
return FALSE;
}
}
else
g_print ("\n\n");
self->did_interaction = FALSE;
return TRUE;
}
static gboolean
transaction_ready (FlatpakTransaction *transaction)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
GList *ops = flatpak_transaction_get_operations (transaction);
GList *l;
FlatpakTablePrinter *printer;
if (ops == NULL)
return TRUE;
printer = self->printer;
if (self->did_interaction)
{
/* We did some interaction since ready_pre_auth which messes up the formating, so re-print table */
flatpak_table_printer_print_full (printer, 0, self->cols,
&self->table_height, &self->table_width);
g_print ("\n\n");
}
for (l = ops; l; l = l->next)
{
FlatpakTransactionOperation *op = l->data;
set_op_progress (self, op, " ");
}
g_list_free_full (ops, g_object_unref);
flatpak_table_printer_add_span (printer, "");
flatpak_table_printer_finish_row (printer);
flatpak_table_printer_add_span (printer, "");
self->progress_row = flatpak_table_printer_get_current_row (printer);
flatpak_table_printer_finish_row (printer);
self->table_height += 3; /* 2 for the added lines and one for the newline from the user after the prompt */
if (flatpak_fancy_output ())
{
flatpak_hide_cursor ();
flatpak_enable_raw_mode ();
redraw (self);
}
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING, message_handler, transaction);
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->progress_msg);
g_hash_table_unref (self->eol_actions);
if (self->runtime_app_map)
g_hash_table_unref (self->runtime_app_map);
if (self->extension_app_map)
g_hash_table_unref (self->extension_app_map);
if (self->printer)
flatpak_table_printer_free (self->printer);
G_OBJECT_CLASS (flatpak_cli_transaction_parent_class)->finalize (object);
}
static void
flatpak_cli_transaction_init (FlatpakCliTransaction *self)
{
self->eol_actions = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal,
(GDestroyNotify)flatpak_decomposed_unref, NULL);
}
static gboolean flatpak_cli_transaction_run (FlatpakTransaction *transaction,
GCancellable *cancellable,
GError **error);
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->ready_pre_auth = transaction_ready_pre_auth;
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_with_rebase = end_of_lifed_with_rebase;
transaction_class->run = flatpak_cli_transaction_run;
transaction_class->webflow_start = webflow_start;
transaction_class->webflow_done = webflow_done;
transaction_class->basic_auth_start = basic_auth_start;
transaction_class->install_authenticator = install_authenticator;
}
FlatpakTransaction *
flatpak_cli_transaction_new (FlatpakDir *dir,
gboolean disable_interaction,
gboolean stop_on_first_error,
gboolean non_default_arch,
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->non_default_arch = non_default_arch;
flatpak_transaction_set_no_interaction (FLATPAK_TRANSACTION (self), disable_interaction);
flatpak_transaction_add_default_dependency_sources (FLATPAK_TRANSACTION (self));
return (FlatpakTransaction *) g_steal_pointer (&self);
}
static gboolean
flatpak_cli_transaction_run (FlatpakTransaction *transaction,
GCancellable *cancellable,
GError **error)
{
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
gboolean res;
res = FLATPAK_TRANSACTION_CLASS (flatpak_cli_transaction_parent_class)->run (transaction, cancellable, error);
if (flatpak_fancy_output ())
{
flatpak_disable_raw_mode ();
flatpak_show_cursor ();
}
if (res && self->n_ops > 0)
{
const char *text;
if (self->uninstalling + self->installing + self->updating > 1)
text = _("Changes complete.");
else if (self->uninstalling)
text = _("Uninstall complete.");
else if (self->installing)
text = _("Installation complete.");
else
text = _("Updates complete.");
if (flatpak_fancy_output ())
{
set_progress (self, text);
redraw (self);
}
else
g_print ("%s", text);
g_print ("\n");
}
if (self->first_operation_error)
{
g_clear_error (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"));
}
}
if (!res)
return FALSE;
return TRUE;
}