diff --git a/app/xdg-app-builtins-install.c b/app/xdg-app-builtins-install.c index ea7b3ab5..f8fbefef 100644 --- a/app/xdg-app-builtins-install.c +++ b/app/xdg-app-builtins-install.c @@ -122,7 +122,6 @@ install_bundle (XdgAppDir *dir, OstreeRepo *repo; g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; g_autoptr(GVariant) metadata = NULL; - g_autoptr(GVariant) gpg_value = NULL; g_autofree char *basename = NULL; if (argc < 2) @@ -132,30 +131,16 @@ install_bundle (XdgAppDir *dir, repo = xdg_app_dir_get_repo (dir); - if (!xdg_app_supports_bundles (repo)) - return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles"); - file = g_file_new_for_commandline_arg (filename); - metadata = xdg_app_bundle_load (file, &to_checksum, error); + metadata = xdg_app_bundle_load (file, &to_checksum, + &ref, + &origin, + &gpg_data, + error); if (metadata == NULL) return FALSE; - if (!g_variant_lookup (metadata, "ref", "s", &ref)) - return xdg_app_fail (error, "Invalid bundle, no ref in metadata"); - - if (!g_variant_lookup (metadata, "origin", "s", &origin)) - origin = NULL; - - gpg_value = g_variant_lookup_value (metadata, "gpg-keys", G_VARIANT_TYPE("ay")); - if (gpg_value) - { - gsize n_elements; - const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); - - gpg_data = g_bytes_new (data, n_elements); - } - if (opt_gpg_file != NULL) { /* Override gpg_data from file */ diff --git a/common/xdg-app-dir.c b/common/xdg-app-dir.c index ce55bf24..1ab694b6 100644 --- a/common/xdg-app-dir.c +++ b/common/xdg-app-dir.c @@ -875,7 +875,7 @@ xdg_app_dir_pull_from_bundle (XdgAppDir *self, if (!xdg_app_supports_bundles (self->repo)) return xdg_app_fail (error, "Your version of ostree is too old to support single-file bundles"); - metadata = xdg_app_bundle_load (file, &to_checksum, error); + metadata = xdg_app_bundle_load (file, &to_checksum, NULL, NULL, NULL, error); if (metadata == NULL) return FALSE; diff --git a/common/xdg-app-utils.c b/common/xdg-app-utils.c index 767779bf..1dd6e8e3 100644 --- a/common/xdg-app-utils.c +++ b/common/xdg-app-utils.c @@ -2335,6 +2335,9 @@ xdg_app_xml_parse (GInputStream *in, GVariant * xdg_app_bundle_load (GFile *file, char **commit, + char **ref, + char **origin, + GBytes **gpg_keys, GError **error) { g_autoptr(GVariant) delta = NULL; @@ -2362,6 +2365,35 @@ xdg_app_bundle_load (GFile *file, metadata = g_variant_get_child_value (delta, 0); + if (ref != NULL) + { + if (!g_variant_lookup (metadata, "ref", "s", ref)) + { + xdg_app_fail (error, "Invalid bundle, no ref in metadata"); + return NULL; + } + } + + if (origin != NULL) + { + if (!g_variant_lookup (metadata, "origin", "s", origin)) + *origin = NULL; + } + + if (gpg_keys != NULL) + { + g_autoptr(GVariant) gpg_value = g_variant_lookup_value (metadata, "gpg-keys", + G_VARIANT_TYPE("ay")); + if (gpg_value) + { + gsize n_elements; + const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); + *gpg_keys = g_bytes_new (data, n_elements); + } + else + *gpg_keys = NULL; + } + /* Make a copy of the data so we can return it after freeing the file */ return g_variant_new_from_bytes (g_variant_get_type (metadata), g_bytes_new (g_variant_get_data (metadata), diff --git a/common/xdg-app-utils.h b/common/xdg-app-utils.h index 40d7297e..a419ccce 100644 --- a/common/xdg-app-utils.h +++ b/common/xdg-app-utils.h @@ -186,6 +186,9 @@ gboolean xdg_app_repo_generate_appstream (OstreeRepo *repo, GVariant * xdg_app_bundle_load (GFile *file, char **commit, + char **ref, + char **origin, + GBytes **gpg_keys, GError **error); diff --git a/lib/xdg-app-bundle-ref.c b/lib/xdg-app-bundle-ref.c index 571c722b..e370745f 100644 --- a/lib/xdg-app-bundle-ref.c +++ b/lib/xdg-app-bundle-ref.c @@ -163,16 +163,10 @@ xdg_app_bundle_ref_new (GFile *file, g_autofree char *full_ref = NULL; g_autofree char *metadata_contents = NULL; - metadata = xdg_app_bundle_load (file, &commit, error); + metadata = xdg_app_bundle_load (file, &commit, &full_ref, NULL, NULL, error); if (metadata == NULL) return NULL; - if (!g_variant_lookup (metadata, "ref", "s", &full_ref)) - { - xdg_app_fail (error, "Invalid bundle, no ref in metadata"); - return NULL; - } - parts = xdg_app_decompose_ref (full_ref, error); if (parts == NULL) return NULL; diff --git a/lib/xdg-app-installation.c b/lib/xdg-app-installation.c index 63ad4ec3..722c7f2c 100644 --- a/lib/xdg-app-installation.c +++ b/lib/xdg-app-installation.c @@ -715,6 +715,144 @@ progress_cb (OstreeAsyncProgress *progress, gpointer user_data) g_string_free (buf, TRUE); } +/** + * xdg_app_installation_install_bundle: + * @self: a #XdgAppInstallation + * @file: a #GFile that is an xdg-app bundle + * @progress: (scope call): progress callback + * @progress_data: user data passed to @progress + * @cancellable: (nullable): a #GCancellable + * @error: return location for a #GError + * + * Install a new ref from a bundle. + * + * Returns: (transfer full): The ref for the newly installed app or %NULL on failure + */ +XdgAppInstalledRef * +xdg_app_installation_install_bundle (XdgAppInstallation *self, + GFile *file, + XdgAppProgressCallback progress, + gpointer progress_data, + GCancellable *cancellable, + GError **error) +{ + XdgAppInstallationPrivate *priv = xdg_app_installation_get_instance_private (self); + g_autofree char *ref = NULL; + gboolean created_deploy_base = FALSE; + gboolean added_remote = FALSE; + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(XdgAppDir) dir_clone = NULL; + XdgAppInstalledRef *result = NULL; + g_autoptr(GError) local_error = NULL; + g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; + g_autoptr(GVariant) metadata = NULL; + g_autofree char *origin = NULL; + g_auto(GStrv) parts = NULL; + g_autofree char *basename = NULL; + g_autoptr(GBytes) gpg_data = NULL; + g_autofree char *to_checksum = NULL; + g_autofree char *remote = NULL; + + metadata = xdg_app_bundle_load (file, &to_checksum, + &ref, + &origin, + &gpg_data, + error); + if (metadata == NULL) + return FALSE; + + parts = xdg_app_decompose_ref (ref, error); + if (parts == NULL) + return FALSE; + + deploy_base = xdg_app_dir_get_deploy_dir (priv->dir, ref); + + if (g_file_query_exists (deploy_base, cancellable)) + { + g_set_error (error, + XDG_APP_ERROR, XDG_APP_ERROR_ALREADY_INSTALLED, + "%s branch %s already installed", parts[1], parts[3]); + return NULL; + } + + /* Add a remote for later updates */ + basename = g_file_get_basename (file); + remote = xdg_app_dir_create_origin_remote (priv->dir, + origin, + parts[1], + basename, + gpg_data, + cancellable, + error); + if (remote == NULL) + return FALSE; + + /* From here we need to goto out on error, to clean up */ + added_remote = TRUE; + + /* Pull, prune, etc are not threadsafe, so we work on a copy */ + dir_clone = xdg_app_dir_clone (priv->dir); + + if (!xdg_app_dir_pull_from_bundle (dir_clone, + file, + remote, + ref, + gpg_data != NULL, + cancellable, + error)) + goto out; + + if (!xdg_app_dir_lock (dir_clone, &lock, + cancellable, error)) + goto out; + + if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_set_error (error, + XDG_APP_ERROR, XDG_APP_ERROR_ALREADY_INSTALLED, + "%s branch %s already installed", parts[1], parts[3]); + else + g_propagate_error (error, g_steal_pointer (&local_error)); + goto out; + } + + created_deploy_base = TRUE; + + if (!xdg_app_dir_set_origin (dir_clone, ref, remote, cancellable, error)) + goto out; + + if (!xdg_app_dir_deploy (dir_clone, ref, NULL, cancellable, error)) + goto out; + + if (strcmp (parts[0], "app") == 0) + { + if (!xdg_app_dir_make_current_ref (dir_clone, ref, cancellable, error)) + goto out; + + if (!xdg_app_dir_update_exports (dir_clone, parts[1], cancellable, error)) + goto out; + } + + result = get_ref (self, ref, cancellable); + + glnx_release_lock_file (&lock); + + xdg_app_dir_cleanup_removed (dir_clone, cancellable, NULL); + + if (!xdg_app_dir_mark_changed (dir_clone, error)) + goto out; + + out: + if (created_deploy_base && result == NULL) + gs_shutil_rm_rf (deploy_base, cancellable, NULL); + + if (added_remote && result == NULL) + ostree_repo_remote_delete (xdg_app_dir_get_repo (priv->dir), remote, NULL, NULL); + + return result; +} + /** * xdg_app_installation_install: * @self: a #XdgAppInstallation diff --git a/lib/xdg-app-installation.h b/lib/xdg-app-installation.h index 09589745..343ecf75 100644 --- a/lib/xdg-app-installation.h +++ b/lib/xdg-app-installation.h @@ -142,6 +142,12 @@ XDG_APP_EXTERN XdgAppInstalledRef * xdg_app_installation_update gpointer progress_data, GCancellable *cancellable, GError **error); +XDG_APP_EXTERN XdgAppInstalledRef * xdg_app_installation_install_bundle (XdgAppInstallation *self, + GFile *file, + XdgAppProgressCallback progress, + gpointer progress_data, + GCancellable *cancellable, + GError **error); XDG_APP_EXTERN gboolean xdg_app_installation_uninstall (XdgAppInstallation *self, XdgAppRefKind kind, const char *name,