mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-24 07:38:15 -05:00
1744 lines
58 KiB
C
1744 lines
58 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
|
|
* Copyright © 2024 GNOME Foundation, 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>
|
|
* Hubert Figuière <hub@figuiere.net>
|
|
*/
|
|
|
|
#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;
|
|
g_autoptr(FlatpakInstallation) installation = flatpak_transaction_get_installation (old_transaction);
|
|
g_autoptr(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 (¤t_row, ¤t_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;
|
|
g_autoptr(FlatpakInstallation) installation = flatpak_transaction_get_installation (transaction);
|
|
g_autoptr(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_usb (GPtrArray *usb_array,
|
|
GKeyFile *metadata,
|
|
GKeyFile *old_metadata)
|
|
{
|
|
gsize size = 0;
|
|
g_auto(GStrv) hidden_devices = NULL;
|
|
g_auto(GStrv) old_hidden_devices = NULL;
|
|
g_auto(GStrv) old_enumerables = NULL;
|
|
g_auto(GStrv) enumerables = NULL;
|
|
|
|
enumerables = g_key_file_get_string_list (metadata,
|
|
FLATPAK_METADATA_GROUP_USB_DEVICES,
|
|
FLATPAK_METADATA_KEY_USB_ENUMERABLE_DEVICES,
|
|
&size, NULL);
|
|
|
|
if (old_metadata)
|
|
old_enumerables = g_key_file_get_string_list (old_metadata,
|
|
FLATPAK_METADATA_GROUP_USB_DEVICES,
|
|
FLATPAK_METADATA_KEY_USB_ENUMERABLE_DEVICES,
|
|
NULL, NULL);
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
{
|
|
const char *enumerable = enumerables[i];
|
|
if (old_enumerables == NULL || !g_strv_contains ((const char * const *) old_enumerables, enumerable))
|
|
g_ptr_array_add (usb_array, g_strdup (enumerable));
|
|
}
|
|
|
|
size = 0;
|
|
|
|
hidden_devices = g_key_file_get_string_list (metadata,
|
|
FLATPAK_METADATA_GROUP_USB_DEVICES,
|
|
FLATPAK_METADATA_KEY_USB_HIDDEN_DEVICES,
|
|
&size, NULL);
|
|
|
|
if (old_metadata)
|
|
old_hidden_devices = g_key_file_get_string_list (old_metadata,
|
|
FLATPAK_METADATA_GROUP_USB_DEVICES,
|
|
FLATPAK_METADATA_KEY_USB_HIDDEN_DEVICES,
|
|
NULL, NULL);
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
{
|
|
const char *hidden = hidden_devices[i];
|
|
if (old_hidden_devices == NULL || !g_strv_contains ((const char * const *) old_hidden_devices, hidden))
|
|
g_ptr_array_add (usb_array, g_strdup_printf ("!%s", hidden));
|
|
}
|
|
}
|
|
|
|
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) usb = 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_usb (usb, metadata, old_metadata);
|
|
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 (usb->len > 0)
|
|
g_ptr_array_add (permissions, g_strdup_printf ("USB portal access [%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 (usb->len > 0)
|
|
print_perm_line (j++, usb, 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);
|
|
g_autolist(FlatpakTransactionOperation) 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)
|
|
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;
|
|
}
|