diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc index ee24d231b..08edf6327 100644 --- a/app/Makefile.am.inc +++ b/app/Makefile.am.inc @@ -5,6 +5,10 @@ bin_PROGRAMS += \ flatpak_SOURCES = \ app/flatpak-main.c \ app/flatpak-builtins.h \ + app/flatpak-builtins-utils.h \ + app/flatpak-builtins-utils.c \ + app/flatpak-transaction.h \ + app/flatpak-transaction.c \ app/flatpak-builtins-add-remote.c \ app/flatpak-builtins-delete-remote.c \ app/flatpak-builtins-list-remotes.c \ diff --git a/app/flatpak-builtins-install.c b/app/flatpak-builtins-install.c index 5c40de40f..9f0ee3549 100644 --- a/app/flatpak-builtins-install.c +++ b/app/flatpak-builtins-install.c @@ -32,6 +32,8 @@ #include "libglnx/libglnx.h" #include "flatpak-builtins.h" +#include "flatpak-builtins-utils.h" +#include "flatpak-transaction.h" #include "flatpak-utils.h" #include "lib/flatpak-error.h" #include "flatpak-chain-input-stream.h" @@ -53,7 +55,7 @@ static GOptionEntry options[] = { { "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only install from local cache"), NULL }, { "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL }, { "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't install related refs"), NULL }, - { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify runtime dependencies"), NULL }, + { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL }, { "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL }, { "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL }, { "bundle", 0, 0, G_OPTION_ARG_NONE, &opt_bundle, N_("Install from local bundle file"), NULL }, @@ -142,78 +144,6 @@ install_bundle (FlatpakDir *dir, return TRUE; } -static gboolean -do_install (FlatpakDir *dir, - gboolean no_pull, - gboolean no_deploy, - const char *ref, - const char *remote_name, - const char **opt_subpaths, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GPtrArray) related = NULL; - const char *slash; - int i; - - slash = strchr (ref, '/'); - g_print (_("Installing: %s\n"), slash + 1); - - if (!flatpak_dir_install (dir, - opt_no_pull, - opt_no_deploy, - ref, remote_name, - (const char **)opt_subpaths, - NULL, - cancellable, error)) - return FALSE; - - if (!opt_no_related) - { - g_autoptr(GError) local_error = NULL; - - if (opt_no_pull) - related = flatpak_dir_find_local_related (dir, ref, remote_name, NULL, &local_error); - else - related = flatpak_dir_find_remote_related (dir, ref, remote_name, NULL, &local_error); - if (related == NULL) - { - g_printerr (_("Warning: Problem looking for related refs: %s\n"), local_error->message); - g_clear_error (&local_error); - } - else - { - for (i = 0; i < related->len; i++) - { - FlatpakRelated *rel = g_ptr_array_index (related, i); - g_auto(GStrv) parts = NULL; - - if (!rel->download) - continue; - - parts = g_strsplit (rel->ref, "/", 0); - - g_print (_("Installing related: %s\n"), parts[1]); - - if (!flatpak_dir_install_or_update (dir, - opt_no_pull, - opt_no_deploy, - rel->ref, remote_name, - (const char **)rel->subpaths, - NULL, - cancellable, &local_error)) - { - if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED)) - g_printerr (_("Warning: Failed to install related ref %s: %s\n"), - rel->ref, local_error->message); - g_clear_error (&local_error); - } - } - } - } - - return TRUE; -} static gboolean install_from (FlatpakDir *dir, @@ -232,6 +162,7 @@ install_from (FlatpakDir *dir, g_auto(GStrv) parts = NULL; const char *slash; FlatpakDir *clone; + g_autoptr(FlatpakTransaction) transaction = NULL; if (argc < 2) return usage_error (context, _("Filename must be specified"), error); @@ -259,30 +190,13 @@ install_from (FlatpakDir *dir, slash = strchr (ref, '/'); g_print (_("Installing: %s\n"), slash + 1); - if (!do_install (clone, - opt_no_pull, - opt_no_deploy, - ref, remote, - (const char **)opt_subpaths, - cancellable, error)) + transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy, + !opt_no_deps, !opt_no_related); + + if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error)) return FALSE; - return TRUE; -} - -static gboolean -looks_like_branch (const char *branch) -{ - /* In particular, / is not a valid branch char, so - this lets us distinguish full or partial refs as - non-branches. */ - if (!flatpak_is_valid_branch (branch, NULL)) - return FALSE; - - /* Dots are allowed in branches, but not really used much, while - they are required for app ids, so thats a good check to - distinguish the two */ - if (strchr (branch, '.') != NULL) + if (!flatpak_transaction_run (transaction, TRUE, cancellable, error)) return FALSE; return TRUE; @@ -293,16 +207,17 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro { g_autoptr(GOptionContext) context = NULL; g_autoptr(FlatpakDir) dir = NULL; - const char *repository; + const char *remote; char **prefs = NULL; int i, n_prefs; g_autofree char *target_branch = NULL; g_autofree char *default_branch = NULL; FlatpakKinds kinds; + g_autoptr(FlatpakTransaction) transaction = NULL; g_autoptr(GPtrArray) refs = NULL; g_autoptr(GHashTable) refs_hash = NULL; - context = g_option_context_new (_("REPOSITORY REF... - Install applications or runtimes")); + context = g_option_context_new (_("REMOTE REF... - Install applications or runtimes")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); if (!flatpak_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) @@ -315,24 +230,25 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro return install_from (dir, context, argc, argv, cancellable, error); if (argc < 3) - return usage_error (context, _("REPOSITORY and REF must be specified"), error); + return usage_error (context, _("REMOTE and REF must be specified"), error); - repository = argv[1]; + remote = argv[1]; prefs = &argv[2]; n_prefs = argc - 2; - /* Backwards compat for old "REPOSITORY NAME [BRANCH]" argument version */ + /* Backwards compat for old "REMOTE NAME [BRANCH]" argument version */ if (argc == 4 && looks_like_branch (argv[3])) { target_branch = g_strdup (argv[3]); n_prefs = 1; } - default_branch = flatpak_dir_get_remote_default_branch (dir, repository); + default_branch = flatpak_dir_get_remote_default_branch (dir, remote); kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); - refs = g_ptr_array_new_with_free_func (g_free); - refs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy, + !opt_no_deps, !opt_no_related); + for (i = 0; i < n_prefs; i++) { const char *pref = prefs[i]; @@ -342,54 +258,24 @@ flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GErro g_autofree char *branch = NULL; FlatpakKinds kind; g_autofree char *ref = NULL; - g_autofree char *metadata = NULL; - g_autoptr(GKeyFile) metakey = NULL; + g_autofree char *runtime_ref = NULL; if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, target_branch, &matched_kinds, &id, &arch, &branch, error)) return FALSE; - ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, default_branch, arch, + ref = flatpak_dir_find_remote_ref (dir, remote, id, branch, default_branch, arch, matched_kinds, &kind, cancellable, error); if (ref == NULL) return FALSE; - metakey = g_key_file_new (); - if (!opt_no_deps && - flatpak_dir_fetch_ref_cache (dir, repository, ref, NULL, NULL, &metadata, cancellable, NULL) && - g_key_file_load_from_data (metakey, metadata, -1, 0, NULL)) - { - g_autofree char *runtime_ref = NULL; - g_autofree char *full_runtime_ref = NULL; - runtime_ref = g_key_file_get_string (metakey, "Application", "runtime", NULL); - full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL); - if (runtime_ref && !g_hash_table_lookup_extended (refs_hash, full_runtime_ref, NULL, NULL)) - { - g_autoptr(FlatpakDeploy) deploy = flatpak_find_deploy_for_ref (full_runtime_ref, NULL, NULL); - - if (deploy == NULL) - return flatpak_fail (error, - "The Application %s requires the runtime %s which is not installed", - id, runtime_ref); - } - } - - if (g_hash_table_insert (refs_hash, g_strdup (ref), NULL)) - g_ptr_array_add (refs, g_steal_pointer (&ref)); - } - - for (i = 0; i < refs->len; i++) - { - const char *ref = g_ptr_array_index (refs, i); - if (!do_install (dir, - opt_no_pull, - opt_no_deploy, - ref, repository, - (const char **)opt_subpaths, - cancellable, error)) + if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error)) return FALSE; } + if (!flatpak_transaction_run (transaction, TRUE, cancellable, error)) + return FALSE; + return TRUE; } diff --git a/app/flatpak-builtins-uninstall.c b/app/flatpak-builtins-uninstall.c index 2531411c8..1f6e804f1 100644 --- a/app/flatpak-builtins-uninstall.c +++ b/app/flatpak-builtins-uninstall.c @@ -30,6 +30,7 @@ #include "libglnx/libglnx.h" #include "flatpak-builtins.h" +#include "flatpak-builtins-utils.h" #include "flatpak-utils.h" static char *opt_arch; @@ -49,24 +50,6 @@ static GOptionEntry options[] = { { NULL } }; -static gboolean -looks_like_branch (const char *branch) -{ - /* In particular, / is not a valid branch char, so - this lets us distinguish full or partial refs as - non-branches. */ - if (!flatpak_is_valid_branch (branch, NULL)) - return FALSE; - - /* Dots are allowed in branches, but not really used much, while - they are required for app ids, so thats a good check to - distinguish the two */ - if (strchr (branch, '.') != NULL) - return FALSE; - - return TRUE; -} - gboolean flatpak_builtin_uninstall (int argc, char **argv, GCancellable *cancellable, GError **error) { diff --git a/app/flatpak-builtins-update.c b/app/flatpak-builtins-update.c index 1b18357ce..30acb70ed 100644 --- a/app/flatpak-builtins-update.c +++ b/app/flatpak-builtins-update.c @@ -30,6 +30,8 @@ #include "libglnx/libglnx.h" #include "flatpak-builtins.h" +#include "flatpak-builtins-utils.h" +#include "flatpak-transaction.h" #include "flatpak-utils.h" #include "flatpak-error.h" @@ -40,6 +42,7 @@ static gboolean opt_force_remove; static gboolean opt_no_pull; static gboolean opt_no_deploy; static gboolean opt_no_related; +static gboolean opt_no_deps; static gboolean opt_runtime; static gboolean opt_app; static gboolean opt_appstream; @@ -51,6 +54,7 @@ static GOptionEntry options[] = { { "no-pull", 0, 0, G_OPTION_ARG_NONE, &opt_no_pull, N_("Don't pull, only update from local cache"), NULL }, { "no-deploy", 0, 0, G_OPTION_ARG_NONE, &opt_no_deploy, N_("Don't deploy, only download to local cache"), NULL }, { "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't update related refs"), NULL}, + { "no-deps", 0, 0, G_OPTION_ARG_NONE, &opt_no_deps, N_("Don't verify/install runtime dependencies"), NULL }, { "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL }, { "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL }, { "appstream", 0, 0, G_OPTION_ARG_NONE, &opt_appstream, N_("Update appstream for remote"), NULL }, @@ -96,138 +100,6 @@ update_appstream (FlatpakDir *dir, const char *remote, GCancellable *cancellable return TRUE; } -static gboolean -do_update (FlatpakDir * dir, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_auto(GStrv) parts = flatpak_decompose_ref (ref, error); - g_autofree char *repository = NULL; - g_autoptr(GPtrArray) related = NULL; - g_autoptr(GError) update_error = NULL; - int i; - - if (parts == NULL) - return FALSE; - - repository = flatpak_dir_get_origin (dir, ref, cancellable, error); - if (repository == NULL) - return FALSE; - - if (strcmp (parts[0], "app") == 0) - g_print (_("Updating application %s, branch %s\n"), parts[1], parts[3]); - else - g_print (_("Updating runtime %s, branch %s\n"), parts[1], parts[3]); - - if (flatpak_dir_get_remote_disabled (dir, repository)) - { - g_print (_("Remote %s disabled\n"), repository); - return TRUE; - } - - if (!flatpak_dir_update (dir, - opt_no_pull, - opt_no_deploy, - ref, repository, opt_commit, (const char **)opt_subpaths, - NULL, - cancellable, &update_error)) - { - if (g_error_matches (update_error, FLATPAK_ERROR, - FLATPAK_ERROR_ALREADY_INSTALLED)) - g_print (_("No updates.\n")); - else - g_printerr ("Error updating: %s\n", update_error->message); - } - else - { - g_autoptr(GVariant) deploy_data = NULL; - g_autofree char *commit = NULL; - deploy_data = flatpak_dir_get_deploy_data (dir, ref, NULL, NULL); - commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12); - g_print (_("Now at %s.\n"), commit); - } - - - if (!opt_no_related) - { - g_autoptr(GError) local_error = NULL; - - if (opt_no_pull) - related = flatpak_dir_find_local_related (dir, ref, repository, NULL, - &local_error); - else - related = flatpak_dir_find_remote_related (dir, ref, repository, NULL, - &local_error); - if (related == NULL) - { - g_printerr (_("Warning: Problem looking for related refs: %s\n"), - local_error->message); - g_clear_error (&local_error); - } - else - { - for (i = 0; i < related->len; i++) - { - FlatpakRelated *rel = g_ptr_array_index (related, i); - g_autoptr(GError) local_error = NULL; - g_auto(GStrv) parts = NULL; - - if (!rel->download) - continue; - - parts = g_strsplit (rel->ref, "/", 0); - - g_print (_("Updating related: %s\n"), parts[1]); - - if (!flatpak_dir_install_or_update (dir, - opt_no_pull, - opt_no_deploy, - rel->ref, repository, - (const char **)rel->subpaths, - NULL, - cancellable, &local_error)) - { - if (g_error_matches (local_error, FLATPAK_ERROR, - FLATPAK_ERROR_ALREADY_INSTALLED)) - g_print (_("No updates.\n")); - else - g_printerr ("Error updating: %s\n", local_error->message); - g_clear_error (&local_error); - } - else - { - g_autoptr(GVariant) deploy_data = NULL; - g_autofree char *commit = NULL; - deploy_data = flatpak_dir_get_deploy_data (dir, rel->ref, NULL, NULL); - commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12); - g_print (_("Now at %s.\n"), commit); - } - } - } - } - - return TRUE; -} - -static gboolean -looks_like_branch (const char *branch) -{ - /* In particular, / is not a valid branch char, so - this lets us distinguish full or partial refs as - non-branches. */ - if (!flatpak_is_valid_branch (branch, NULL)) - return FALSE; - - /* Dots are allowed in branches, but not really used much, while - they are required for app ids, so thats a good check to - distinguish the two */ - if (strchr (branch, '.') != NULL) - return FALSE; - - return TRUE; -} - gboolean flatpak_builtin_update (int argc, char **argv, @@ -239,10 +111,8 @@ flatpak_builtin_update (int argc, char **prefs = NULL; int i, j, n_prefs; const char *default_branch = NULL; - gboolean failed = FALSE; FlatpakKinds kinds; - g_autoptr(GHashTable) update_refs_hash = NULL; - g_autoptr(GPtrArray) update_refs = NULL; + g_autoptr(FlatpakTransaction) transaction = NULL; context = g_option_context_new (_("[REF...] - Update applications or runtimes")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); @@ -263,10 +133,12 @@ flatpak_builtin_update (int argc, n_prefs = 1; } - update_refs = g_ptr_array_new_with_free_func (g_free); - update_refs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + transaction = flatpak_transaction_new (dir, opt_no_pull, opt_no_deploy, + !opt_no_deps, !opt_no_related); kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); + g_print ("Looking for updates...\n"); + for (j = 0; j == 0 || j < n_prefs; j++) { const char *pref = NULL; @@ -313,8 +185,8 @@ flatpak_builtin_update (int argc, continue; found = TRUE; - if (g_hash_table_insert (update_refs_hash, g_strdup (refs[i]), NULL)) - g_ptr_array_add (update_refs, g_strdup (refs[i])); + if (!flatpak_transaction_add_update (transaction, refs[i], (const char **)opt_subpaths, error)) + return FALSE; } } @@ -344,8 +216,8 @@ flatpak_builtin_update (int argc, continue; found = TRUE; - if (g_hash_table_insert (update_refs_hash, g_strdup (refs[i]), NULL)) - g_ptr_array_add (update_refs, g_strdup (refs[i])); + if (!flatpak_transaction_add_update (transaction, refs[i], (const char **)opt_subpaths, error)) + return FALSE; } } @@ -357,19 +229,8 @@ flatpak_builtin_update (int argc, } } - for (i = 0; i < update_refs->len; i++) - { - const char *ref = (char *)g_ptr_array_index (update_refs, i); - g_autoptr(GError) local_error = NULL; - if (!do_update (dir, ref, cancellable, &local_error)) - { - g_printerr (_("Error updating: %s\n"), local_error->message); - failed = TRUE; - } - } - - if (failed) - return flatpak_fail (error, _("One or more updates failed")); + if (!flatpak_transaction_run (transaction, FALSE, cancellable, error)) + return FALSE; return TRUE; } diff --git a/app/flatpak-builtins-utils.c b/app/flatpak-builtins-utils.c new file mode 100644 index 000000000..7fa5e4858 --- /dev/null +++ b/app/flatpak-builtins-utils.c @@ -0,0 +1,45 @@ +/* + * Copyright © 2014 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 . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include + +#include "flatpak-builtins-utils.h" +#include "flatpak-utils.h" + + +gboolean +looks_like_branch (const char *branch) +{ + /* In particular, / is not a valid branch char, so + this lets us distinguish full or partial refs as + non-branches. */ + if (!flatpak_is_valid_branch (branch, NULL)) + return FALSE; + + /* Dots are allowed in branches, but not really used much, while + they are required for app ids, so thats a good check to + distinguish the two */ + if (strchr (branch, '.') != NULL) + return FALSE; + + return TRUE; +} diff --git a/app/flatpak-builtins-utils.h b/app/flatpak-builtins-utils.h new file mode 100644 index 000000000..11a90e214 --- /dev/null +++ b/app/flatpak-builtins-utils.h @@ -0,0 +1,29 @@ +/* + * Copyright © 2014 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 . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_BUILTINS_UTILS_H__ +#define __FLATPAK_BUILTINS_UTILS_H__ + +#include +#include "libglnx/libglnx.h" + +gboolean looks_like_branch (const char *branch); + +#endif /* __FLATPAK_BUILTINS_UTILS_H__ */ diff --git a/app/flatpak-transaction.c b/app/flatpak-transaction.c new file mode 100644 index 000000000..198e33a77 --- /dev/null +++ b/app/flatpak-transaction.c @@ -0,0 +1,546 @@ +/* + * Copyright © 2016 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 . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include + +#include "flatpak-transaction.h" +#include "flatpak-utils.h" +#include "flatpak-builtins-utils.h" +#include "flatpak-error.h" + +typedef struct FlatpakTransactionOp FlatpakTransactionOp; + +struct FlatpakTransactionOp { + char *remote; + char *ref; + char **subpaths; + char *commit; /* TODO: handle */ + gboolean update; + gboolean install; + gboolean non_fatal; +}; + +struct FlatpakTransaction { + FlatpakDir *dir; + GHashTable *refs; + GList *ops; + + gboolean no_pull; + gboolean no_deploy; + gboolean add_deps; + gboolean add_related; +}; + + +static gboolean +ref_is_installed (const char *ref) +{ + g_autoptr(FlatpakDeploy) deploy = NULL; + deploy = flatpak_find_deploy_for_ref (ref, NULL, NULL); + return deploy != NULL; +} + +static gboolean +dir_ref_is_installed (FlatpakDir *dir, const char *ref, char **remote_out) +{ + g_autoptr(GVariant) deploy_data = NULL; + + deploy_data = flatpak_dir_get_deploy_data (dir, ref, NULL, NULL); + if (deploy_data == NULL) + return FALSE; + + if (remote_out) + *remote_out = g_strdup (flatpak_deploy_data_get_origin (deploy_data)); + return TRUE; +} + +static char * +transaction_fetch_runtime_ref (FlatpakDir *dir, const char *remote, const char *ref) +{ + g_autofree char *metadata = NULL; + g_autoptr(GKeyFile) metakey = NULL; + g_autofree char *runtime_ref = NULL; + + if (!g_str_has_prefix (ref, "app/")) + return NULL; + + if (!flatpak_dir_fetch_ref_cache (dir, remote, ref, NULL, NULL, &metadata, NULL, NULL)) + return NULL; + + metakey = g_key_file_new (); + if (!g_key_file_load_from_data (metakey, metadata, -1, 0, NULL)) + return NULL; + + runtime_ref = g_key_file_get_string (metakey, "Application", "runtime", NULL); + + return g_steal_pointer (&runtime_ref); +} + + +static FlatpakTransactionOp * +flatpak_transaction_operation_new (const char *remote, + const char *ref, + const char **subpaths, + gboolean install, + gboolean update) +{ + FlatpakTransactionOp *self = g_new0 (FlatpakTransactionOp, 1); + + self->remote = g_strdup (remote); + self->ref = g_strdup (ref); + self->subpaths = g_strdupv ((char **)subpaths); + self->update = update; + self->install = install; + + return self; +} + +static void +flatpak_transaction_operation_free (FlatpakTransactionOp *self) +{ + g_free (self->remote); + g_free (self->ref); + g_free (self->commit); + g_strfreev (self->subpaths); + g_free (self); +} + +FlatpakTransaction * +flatpak_transaction_new (FlatpakDir *dir, + gboolean no_pull, + gboolean no_deploy, + gboolean add_deps, + gboolean add_related) +{ + FlatpakTransaction *t = g_new0 (FlatpakTransaction, 1); + + t->dir = g_object_ref (dir); + t->refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + t->no_pull = no_pull; + t->no_deploy = no_deploy; + t->add_deps = add_deps; + t->add_related = add_related; + return t; +} + +void +flatpak_transaction_free (FlatpakTransaction *self) +{ + g_hash_table_unref (self->refs); + g_list_free_full (self->ops, (GDestroyNotify)flatpak_transaction_operation_free); + g_object_unref (self->dir); + g_free (self); +} + +gboolean +flatpak_transaction_contains_ref (FlatpakTransaction *self, + const char *ref) +{ + FlatpakTransactionOp *op; + + op = g_hash_table_lookup (self->refs, ref); + + return op != NULL; +} + + +static char * +subpaths_to_string (const char **subpaths) +{ + GString *s = NULL; + int i; + + if (subpaths == NULL) + return g_strdup ("[$old]"); + + if (*subpaths == 0) + return g_strdup ("[*]"); + + s = g_string_new ("["); + for (i = 0; subpaths[i] != NULL; i++) + { + if (i != 0) + g_string_append (s, ", "); + g_string_append (s, subpaths[i]); + } + g_string_append (s, "]"); + + return g_string_free (s, FALSE); +} + +FlatpakTransactionOp * +flatpak_transaction_add_op (FlatpakTransaction *self, + const char *remote, + const char *ref, + const char **subpaths, + gboolean install, + gboolean update) +{ + FlatpakTransactionOp *op; + g_autofree char *subpaths_str = NULL; + const char *opname; + + if (install) + { + if (update) + opname = "install/update"; + else + opname = "install"; + } + else + opname = "update"; + subpaths_str = subpaths_to_string (subpaths); + g_debug ("Transaction: %s %s:%s%s", + opname, remote, ref, subpaths_str); + + op = g_hash_table_lookup (self->refs, ref); + if (op != NULL) + { + /* Only override subpaths if already specified, + we always want the un-subpathed to win if specified. */ + if (op->subpaths != NULL && op->subpaths[0] != NULL && subpaths != NULL) + { + g_strfreev (op->subpaths); + op->subpaths = g_strdupv ((char **)subpaths); + } + + return op; + } + + op = flatpak_transaction_operation_new (remote, ref, subpaths, install, update); + g_hash_table_insert (self->refs, g_strdup (ref), op); + self->ops = g_list_prepend (self->ops, op); + + return op; +} + +static char * +ask_for_remote (const char **remotes) +{ + int n_remotes = g_strv_length ((char **)remotes); + int chosen = 0; + int i; + + if (n_remotes == 1) + { + if (flatpak_yes_no_prompt (_("Found in remote %s, do you want to install it?"), remotes[0])) + chosen = 1; + } + else + { + g_print (_("Found in several remotes:\n")); + for (i = 0; remotes[i] != NULL; i++) + { + g_print ("%d) %s\n", i + 1, remotes[i]); + } + chosen = flatpak_number_prompt (0, n_remotes, _("Which do you want to install (0 to abort)?")); + } + + if (chosen == 0) + return NULL; + + return g_strdup (remotes[chosen-1]); +} + +static gboolean +add_related (FlatpakTransaction *self, + const char *remote, + const char *ref, + GError **error) +{ + g_autoptr(GPtrArray) related = NULL; + g_autoptr(GError) local_error = NULL; + int i; + + if (!self->add_related) + return TRUE; + + if (self->no_pull) + related = flatpak_dir_find_local_related (self->dir, ref, remote, NULL, &local_error); + else + related = flatpak_dir_find_remote_related (self->dir, ref, remote, NULL, &local_error); + if (related == NULL) + { + g_printerr (_("Warning: Problem looking for related refs: %s\n"), local_error->message); + g_clear_error (&local_error); + } + else + { + for (i = 0; i < related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (related, i); + FlatpakTransactionOp *op; + + if (!rel->download) + continue; + + op = flatpak_transaction_add_op (self, remote, rel->ref, + (const char **)rel->subpaths, + TRUE, TRUE); + op->non_fatal = TRUE; + } + } + + return TRUE; +} + +static gboolean +add_deps (FlatpakTransaction *self, + const char *remote, + const char *ref, + GError **error) +{ + g_autofree char *runtime_ref = NULL; + g_autofree char *full_runtime_ref = NULL; + g_autofree char *runtime_remote = NULL; + const char *pref; + + runtime_ref = transaction_fetch_runtime_ref (self->dir, remote, ref); + if (runtime_ref == NULL) + return TRUE; + + pref = strchr (ref, '/') + 1; + + full_runtime_ref = g_strconcat ("runtime/", runtime_ref, NULL); + + if (!flatpak_transaction_contains_ref (self, full_runtime_ref)) + { + if (!ref_is_installed (full_runtime_ref)) + { + g_auto(GStrv) remotes = NULL; + + g_print (_("Required runtime for %s (%s) is not installed, searching...\n"), + pref, runtime_ref); + + remotes = flatpak_dir_search_for_dependency (self->dir, full_runtime_ref, NULL, NULL); + if (remotes == NULL || *remotes == NULL) + { + g_print (_("The required runtime %s was not found in a configured remote.\n"), + runtime_ref); + } + else + { + runtime_remote = ask_for_remote ((const char **)remotes); + } + + if (runtime_remote == NULL) + return flatpak_fail (error, + "The Application %s requires the runtime %s which is not installed", + pref, runtime_ref); + + flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, TRUE, TRUE); + } + else + { + /* Update if in same dir */ + if (dir_ref_is_installed (self->dir, full_runtime_ref, &runtime_remote)) + { + flatpak_transaction_add_op (self, runtime_remote, full_runtime_ref, NULL, FALSE, TRUE); + } + } + } + + if (runtime_remote != NULL && + !add_related (self, runtime_remote, full_runtime_ref, error)) + return FALSE; + + return TRUE; +} + +static gboolean +flatpak_transaction_add_ref (FlatpakTransaction *self, + const char *remote, + const char *ref, + const char **subpaths, + gboolean is_update, + GError **error) +{ + g_autofree char *origin = NULL; + const char *pref; + + pref = strchr (ref, '/') + 1; + + if (is_update) + { + if (!dir_ref_is_installed (self->dir, ref, &origin)) + { + g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, + _("%s not installed"), pref); + return FALSE; + } + + if (flatpak_dir_get_remote_disabled (self->dir, origin)) + { + g_debug (_("Remote %s disabled, ignoring %s update"), origin, pref); + return TRUE; + } + remote = origin; + } + else + { + g_assert (remote != NULL); + if (dir_ref_is_installed (self->dir, ref, NULL)) + { + g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED, + _("%s already installed"), pref); + return FALSE; + } + } + + if (self->add_deps) + add_deps (self, remote, ref, error); + + flatpak_transaction_add_op (self, remote, ref, subpaths, !is_update, is_update); + + if (!add_related (self, remote, ref, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_transaction_add_install (FlatpakTransaction *self, + const char *remote, + const char *ref, + const char **subpaths, + GError **error) +{ + const char *all_paths[] = { NULL }; + + /* If we install with no special args pull all subpaths */ + if (subpaths == NULL) + subpaths = all_paths; + + return flatpak_transaction_add_ref (self, remote, ref, subpaths, FALSE, error); +} + +gboolean +flatpak_transaction_add_update (FlatpakTransaction *self, + const char *ref, + const char **subpaths, + GError **error) +{ + return flatpak_transaction_add_ref (self, NULL, ref, subpaths, TRUE, error); +} + + +gboolean +flatpak_transaction_run (FlatpakTransaction *self, + gboolean stop_on_first_error, + GCancellable *cancellable, + GError **error) +{ + GList *l; + gboolean succeeded = TRUE; + + self->ops = g_list_reverse (self->ops); + + for (l = self->ops; l != NULL; l = l->next) + { + FlatpakTransactionOp *op = l->data; + g_autoptr(GError) local_error = NULL; + gboolean res; + g_autofree char *subpaths_str = subpaths_to_string ((const char **)op->subpaths); + const char *pref; + const char *opname; + + if (op->install && op->update) + { + if (dir_ref_is_installed (self->dir, op->ref, NULL)) + op->install = FALSE; + else + op->update = FALSE; + } + + pref = strchr (op->ref, '/') + 1; + + if (op->install) + { + opname = _("install"); + g_print (_("Installing: %s from %s\n"), pref, op->remote); + res = TRUE; + res = flatpak_dir_install (self->dir, + self->no_pull, + self->no_deploy, + op->ref, op->remote, + (const char **)op->subpaths, + NULL, + cancellable, &local_error); + } + else /* update */ + { + opname = _("update"); + g_print (_("Updating: %s from %s\n"), pref, op->remote); + res = flatpak_dir_update (self->dir, + self->no_pull, + self->no_deploy, + op->ref, op->remote, op->commit, + (const char **)op->subpaths, + NULL, + cancellable, &local_error); + + if (res) + { + g_autoptr(GVariant) deploy_data = NULL; + g_autofree char *commit = NULL; + deploy_data = flatpak_dir_get_deploy_data (self->dir, op->ref, NULL, NULL); + commit = g_strndup (flatpak_deploy_data_get_commit (deploy_data), 12); + g_print (_("Now at %s.\n"), commit); + } + + /* Handle noop-updates */ + if (!res && g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED)) + { + g_print (_("No updates.\n")); + res = TRUE; + g_clear_error (&local_error); + } + } + + if (!res) + { + if (op->non_fatal) + { + g_printerr (_("Warning: Failed to %s %s: %s\n"), + opname, pref, local_error->message); + } + else if (!stop_on_first_error) + { + g_printerr (_("Error: Failed to %s %s: %s\n"), + opname, pref, local_error->message); + if (succeeded) + { + succeeded = FALSE; + flatpak_fail (error, _("One or more operations failed")); + } + } + else + { + g_propagate_error (error, local_error); + return FALSE; + } + } + } + + return succeeded; +} diff --git a/app/flatpak-transaction.h b/app/flatpak-transaction.h new file mode 100644 index 000000000..5ca22383b --- /dev/null +++ b/app/flatpak-transaction.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2016 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 . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_TRANSACTION_H__ +#define __FLATPAK_TRANSACTION_H__ + +#include +#include "libglnx/libglnx.h" + +#include "flatpak-dir.h" + +typedef struct FlatpakTransaction FlatpakTransaction; + +FlatpakTransaction *flatpak_transaction_new (FlatpakDir *dir, + gboolean no_pull, + gboolean no_deploy, + gboolean add_deps, + gboolean add_related); +void flatpak_transaction_free (FlatpakTransaction *self); +gboolean flatpak_transaction_run (FlatpakTransaction *self, + gboolean stop_on_first_errror, + GCancellable *cancellable, + GError **error); +gboolean flatpak_transaction_add_install (FlatpakTransaction *self, + const char *remote, + const char *ref, + const char **subpaths, + GError **error); +gboolean flatpak_transaction_add_update (FlatpakTransaction *self, + const char *ref, + const char **subpaths, + GError **error); + + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakTransaction, flatpak_transaction_free) + +#endif /* __FLATPAK_TRANSACTION_H__ */ diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 408aeb5ec..d32983211 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -4227,6 +4227,29 @@ flatpak_dir_remote_fetch_summary (FlatpakDir *self, return TRUE; } +gboolean +flatpak_dir_remote_has_ref (FlatpakDir *self, + const char *remote, + const char *ref) +{ + g_autoptr(GBytes) summary_bytes = NULL; + g_autoptr(GVariant) summary = NULL; + g_autoptr(GError) local_error = NULL; + + if (!flatpak_dir_remote_fetch_summary (self, remote, + &summary_bytes, + NULL, &local_error)) + { + g_debug ("Can't get summary for remote %s: %s\n", remote, local_error->message); + return FALSE; + } + + summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, + summary_bytes, FALSE)); + + return flatpak_summary_lookup_ref (summary, ref, NULL); +} + /* This duplicates ostree_repo_list_refs so it can use flatpak_dir_remote_fetch_summary and get caching */ static gboolean @@ -4978,6 +5001,66 @@ flatpak_dir_list_remotes (FlatpakDir *self, return res; } +char ** +flatpak_dir_list_enumerated_remotes (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free); + g_auto(GStrv) remotes = NULL; + int i; + + remotes = flatpak_dir_list_remotes (self, cancellable, error); + if (remotes == NULL) + return NULL; + + for (i = 0; remotes != NULL && remotes[i] != NULL; i++) + { + const char *remote = remotes[i]; + + if (flatpak_dir_get_remote_disabled (self, remote)) + continue; + + if (flatpak_dir_get_remote_noenumerate (self, remote)) + continue; + + g_ptr_array_add (res, g_strdup (remote)); + } + + g_ptr_array_add (res, NULL); + return (char **)g_ptr_array_free (g_steal_pointer (&res), FALSE); +} + +char ** +flatpak_dir_search_for_dependency (FlatpakDir *self, + const char *runtime_ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GPtrArray) found = g_ptr_array_new_with_free_func (g_free); + g_auto(GStrv) remotes = NULL; + int i; + + remotes = flatpak_dir_list_enumerated_remotes (self, cancellable, error); + if (remotes == NULL) + return NULL; + + for (i = 0; remotes != NULL && remotes[i] != NULL; i++) + { + const char *remote = remotes[i]; + + if (flatpak_dir_get_remote_nodeps (self, remote)) + continue; + + if (flatpak_dir_remote_has_ref (self, remote, runtime_ref)) + g_ptr_array_add (found, g_strdup (remote)); + } + + g_ptr_array_add (found, NULL); + + return (char **)g_ptr_array_free (g_steal_pointer (&found), FALSE); +} + gboolean flatpak_dir_remove_remote (FlatpakDir *self, gboolean force_remove, diff --git a/common/flatpak-dir.h b/common/flatpak-dir.h index 3d25fbf9c..4ce7082c1 100644 --- a/common/flatpak-dir.h +++ b/common/flatpak-dir.h @@ -153,6 +153,13 @@ GFile * flatpak_dir_get_if_deployed (FlatpakDir *self, const char *ref, const char *checksum, GCancellable *cancellable); +gboolean flatpak_dir_remote_has_ref (FlatpakDir *self, + const char *remote, + const char *ref); +char ** flatpak_dir_search_for_dependency (FlatpakDir *self, + const char *runtime_ref, + GCancellable *cancellable, + GError **error); char * flatpak_dir_find_remote_ref (FlatpakDir *self, const char *remote, const char *name, @@ -395,6 +402,9 @@ gboolean flatpak_dir_create_remote_for_ref_file (FlatpakDir *self, char **flatpak_dir_list_remotes (FlatpakDir *self, GCancellable *cancellable, GError **error); +char **flatpak_dir_list_enumerated_remotes (FlatpakDir *self, + GCancellable *cancellable, + GError **error); gboolean flatpak_dir_modify_remote (FlatpakDir *self, const char *remote_name, GKeyFile *config, diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index 30ed6b1c5..f6ab785ae 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -3823,6 +3823,75 @@ flatpak_allocate_tmpdir (int tmpdir_dfd, return TRUE; } +gboolean +flatpak_yes_no_prompt (const char *prompt, ...) +{ + char buf[512]; + va_list var_args; + gchar *s; + + va_start (var_args, prompt); + s = g_strdup_vprintf (prompt, var_args); + + while (TRUE) + { + g_print ("%s %s: ", s, "[y/n]"); + if (fgets (buf, sizeof (buf), stdin) == NULL) + return FALSE; + + g_strstrip (buf); + if (g_ascii_strcasecmp (buf, "y") == 0 || + g_ascii_strcasecmp (buf, "yes") == 0) + return TRUE; + + if (g_ascii_strcasecmp (buf, "n") == 0 || + g_ascii_strcasecmp (buf, "no") == 0) + return FALSE; + } +} + +static gboolean +is_number (const char *s) +{ + while (*s != 0) + { + if (!g_ascii_isdigit (*s)) + return FALSE; + s++; + } + + return TRUE; +} + +long +flatpak_number_prompt (int min, int max, const char *prompt, ...) +{ + char buf[512]; + va_list var_args; + gchar *s; + + va_start (var_args, prompt); + s = g_strdup_vprintf (prompt, var_args); + + while (TRUE) + { + g_print ("%s [%d-%d]: ", s, min, max); + if (fgets (buf, sizeof (buf), stdin) == NULL) + return FALSE; + + g_strstrip (buf); + + if (is_number (buf)) + { + long res = strtol (buf, NULL, 10); + + if (res >= min && res <= max) + return res; + } + } +} + + /* Uncomment to get debug traces in /tmp/flatpak-completion-debug.txt (nice * to not have it interfere with stdout/stderr) */ diff --git a/common/flatpak-utils.h b/common/flatpak-utils.h index 3b9bebe75..57bdc9fa5 100644 --- a/common/flatpak-utils.h +++ b/common/flatpak-utils.h @@ -467,6 +467,9 @@ gboolean flatpak_allocate_tmpdir (int tmpdir_dfd, GError **error); +gboolean flatpak_yes_no_prompt (const char *prompt, ...); +long flatpak_number_prompt (int min, int max, const char *prompt, ...); + typedef struct { char *shell_cur; char *cur; diff --git a/doc/flatpak-update.xml b/doc/flatpak-update.xml index 301981dda..e26c60b6f 100644 --- a/doc/flatpak-update.xml +++ b/doc/flatpak-update.xml @@ -157,6 +157,14 @@ + + + + + Don't update or install runtime dependencies when installing. + + +