mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-23 23:28:14 -05:00
We were using the old ref here, instead of the new one, when asking whether to go ahead. Closes: https://github.com/flatpak/flatpak/issues/3139
1167 lines
38 KiB
C
1167 lines
38 KiB
C
/*
|
|
* Copyright © 2018 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors:
|
|
* Alexander Larsson <alexl@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "flatpak-cli-transaction.h"
|
|
#include "flatpak-transaction-private.h"
|
|
#include "flatpak-installation-private.h"
|
|
#include "flatpak-run-private.h"
|
|
#include "flatpak-table-printer.h"
|
|
#include "flatpak-utils-private.h"
|
|
#include "flatpak-error.h"
|
|
#include <glib/gi18n.h>
|
|
|
|
|
|
struct _FlatpakCliTransaction
|
|
{
|
|
FlatpakTransaction parent;
|
|
|
|
gboolean disable_interaction;
|
|
gboolean stop_on_first_error;
|
|
gboolean non_default_arch;
|
|
GError *first_operation_error;
|
|
|
|
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;
|
|
};
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
if (self->disable_interaction)
|
|
{
|
|
g_print (_("Configuring %s as new remote '%s'"), url, remote_name);
|
|
return TRUE;
|
|
}
|
|
|
|
if (reason == FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO)
|
|
{
|
|
if (flatpak_yes_no_prompt (TRUE, /* default to yes on Enter */
|
|
_("The remote '%s', 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 char *
|
|
op_type_to_string (FlatpakTransactionOperationType operation_type)
|
|
{
|
|
switch (operation_type)
|
|
{
|
|
case FLATPAK_TRANSACTION_OPERATION_INSTALL:
|
|
return _("install");
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_UPDATE:
|
|
return _("update");
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
|
|
return _("install bundle");
|
|
|
|
case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
|
|
return _("uninstall");
|
|
|
|
default:
|
|
return "Unknown type"; /* Should not happen */
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
formatted_max = g_format_size (max);
|
|
if (transferred < 1024) // avoid "bytes"
|
|
formatted = g_strdup ("1.0 kB");
|
|
else
|
|
formatted = g_format_size (transferred);
|
|
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 ("\r%s", 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 ("\r%-*s", self->table_width, 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_FREQUENCY);
|
|
}
|
|
|
|
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);
|
|
g_autofree char *msg = NULL;
|
|
gboolean non_fatal = (detail & FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL) != 0;
|
|
g_autofree char *text = NULL;
|
|
|
|
if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED))
|
|
{
|
|
set_op_progress (self, op, "⍻");
|
|
msg = 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, msg);
|
|
self->progress_row++;
|
|
flatpak_table_printer_add_span (self->printer, "");
|
|
flatpak_table_printer_finish_row (self->printer);
|
|
redraw (self);
|
|
}
|
|
else
|
|
g_print ("\r%-*s\n", self->table_width, msg); /* override progress, and go to next line */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
set_op_progress (self, op, "✗");
|
|
|
|
if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
|
|
msg = g_strdup_printf (_("%s already installed"), flatpak_ref_get_name (rref));
|
|
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
|
msg = g_strdup_printf (_("%s not installed"), flatpak_ref_get_name (rref));
|
|
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
|
msg = g_strdup_printf (_("%s not installed"), flatpak_ref_get_name (rref));
|
|
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NEED_NEW_FLATPAK))
|
|
msg = g_strdup_printf (_("%s needs a later flatpak version"), flatpak_ref_get_name (rref));
|
|
else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_OUT_OF_SPACE))
|
|
msg = g_strdup (_("Not enough disk space to complete this operation"));
|
|
else
|
|
msg = g_strdup (error->message);
|
|
|
|
if (!non_fatal && self->first_operation_error == NULL)
|
|
g_propagate_prefixed_error (&self->first_operation_error,
|
|
g_error_copy (error),
|
|
_("Failed to %s %s: "),
|
|
op_type_to_string (op_type), flatpak_ref_get_name (rref));
|
|
|
|
text = g_strconcat (non_fatal ? _("Warning:") : _("Error:"), " ", msg, 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 ("\r%-*s\n", self->table_width, text);
|
|
|
|
if (!non_fatal && self->stop_on_first_error)
|
|
return FALSE;
|
|
|
|
return TRUE; /* Continue */
|
|
}
|
|
|
|
static gboolean
|
|
end_of_lifed_with_rebase (FlatpakTransaction *transaction,
|
|
const char *remote,
|
|
const char *ref,
|
|
const char *reason,
|
|
const char *rebased_to_ref,
|
|
const char **previous_ids)
|
|
{
|
|
FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction);
|
|
g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL);
|
|
|
|
if (rebased_to_ref)
|
|
g_print (_("Info: %s is end-of-life, in preference of %s\n"), flatpak_ref_get_name (rref), rebased_to_ref);
|
|
else if (reason)
|
|
g_print (_("Info: %s is end-of-life, with reason: %s\n"), flatpak_ref_get_name (rref), reason);
|
|
|
|
if (rebased_to_ref && remote)
|
|
{
|
|
if (self->disable_interaction ||
|
|
flatpak_yes_no_prompt (TRUE, _("Replace it with %s?"), rebased_to_ref))
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
if (self->disable_interaction)
|
|
g_print (_("Updating to rebased version\n"));
|
|
|
|
if (!flatpak_transaction_add_uninstall (transaction, ref, &error) ||
|
|
!flatpak_transaction_add_rebase (transaction, remote, rebased_to_ref, NULL, previous_ids, &error))
|
|
{
|
|
g_propagate_prefixed_error (&self->first_operation_error,
|
|
g_error_copy (error),
|
|
_("Failed to rebase %s to %s: "),
|
|
flatpak_ref_get_name (rref), rebased_to_ref);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
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);
|
|
int i;
|
|
|
|
g_string_append_printf (res, " [%d] %s", idx, (char *) items->pdata[0]);
|
|
|
|
for (i = 1; i < items->len; i++)
|
|
{
|
|
char *p;
|
|
int len;
|
|
|
|
p = strrchr (res->str, '\n');
|
|
if (!p)
|
|
p = res->str;
|
|
|
|
len = (res->str + strlen (res->str)) - p;
|
|
if (len + strlen ((char *) items->pdata[i]) + 2 >= cols)
|
|
g_string_append_printf (res, ",\n %s", (char *) items->pdata[i]);
|
|
else
|
|
g_string_append_printf (res, ", %s", (char *) items->pdata[i]);
|
|
}
|
|
|
|
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);
|
|
FlatpakTablePrinter *printer;
|
|
int max_permission_width;
|
|
int n_permission_cols;
|
|
int i, j;
|
|
int rows, cols;
|
|
int table_rows, table_cols;
|
|
|
|
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 permissions:"), flatpak_ref_get_name (rref));
|
|
else
|
|
g_print (_("%s permissions:"), flatpak_ref_get_name (rref));
|
|
|
|
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);
|
|
flatpak_table_printer_free (printer);
|
|
|
|
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 ("\r%-*s\n", self->table_width, text);
|
|
}
|
|
|
|
static gboolean
|
|
transaction_ready (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" };
|
|
|
|
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_title (printer, i++, _("Arch"));
|
|
|
|
flatpak_table_printer_set_column_expand (printer, i, TRUE);
|
|
flatpak_table_printer_set_column_title (printer, i++, _("Branch"));
|
|
|
|
if (self->installing + self->updating + self->uninstalling > 1)
|
|
{
|
|
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);
|
|
const char *ref = flatpak_transaction_operation_get_ref (op);
|
|
const char *remote = flatpak_transaction_operation_get_remote (op);
|
|
g_auto(GStrv) parts = flatpak_decompose_ref (ref, NULL);
|
|
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, parts[1]);
|
|
flatpak_table_printer_add_column (printer, parts[2]);
|
|
flatpak_table_printer_add_column (printer, parts[3]);
|
|
|
|
if (self->installing + self->updating + self->uninstalling > 1)
|
|
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)
|
|
{
|
|
g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL);
|
|
guint64 download_size;
|
|
g_autofree char *formatted = NULL;
|
|
g_autofree char *text = NULL;
|
|
|
|
download_size = flatpak_transaction_operation_get_download_size (op);
|
|
formatted = g_format_size (download_size);
|
|
|
|
flatpak_table_printer_add_column (printer, remote);
|
|
if (g_str_has_suffix (flatpak_ref_get_name (rref), ".Locale"))
|
|
text = g_strdup_printf ("< %s (%s)", formatted, _("partial"));
|
|
else
|
|
text = g_strdup_printf ("< %s", 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");
|
|
|
|
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_OBJECT_CLASS (flatpak_cli_transaction_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
flatpak_cli_transaction_init (FlatpakCliTransaction *self)
|
|
{
|
|
}
|
|
|
|
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->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;
|
|
}
|
|
|
|
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;
|
|
|
|
flatpak_dir_set_no_interaction (dir, disable_interaction);
|
|
|
|
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_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 ("\r%-*s", self->table_width, 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;
|
|
}
|