From c92a2635a8cc9cbadfd63ea8ddf2d327b132bdcd Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 16 Jan 2015 18:33:46 -0500 Subject: [PATCH] Improve the uninstall commands Undeploy all deployed commits, remove the ref from the repo, and prune unowned objects from the repository. --- doc/xdg-app-uninstall-app.xml | 33 +++- doc/xdg-app-uninstall-runtime.xml | 33 +++- xdg-app-builtins-uninstall.c | 244 ++++++++++++++++++++---------- 3 files changed, 220 insertions(+), 90 deletions(-) diff --git a/doc/xdg-app-uninstall-app.xml b/doc/xdg-app-uninstall-app.xml index d62b018c..38e7a5bf 100644 --- a/doc/xdg-app-uninstall-app.xml +++ b/doc/xdg-app-uninstall-app.xml @@ -33,7 +33,7 @@ xdg-app uninstall-app OPTION APP - ARCH BRANCH + BRANCH @@ -41,10 +41,16 @@ Description - Uninstalls an application. APP must - name an existing application. If ARCH - or BRANCH are specified, only the - matching branches of the application are uninstalled. + Uninstalls an application. The APP, + ARCH and BRANCH + arguments must specify an installed application. + + + Normally, this command removes the ref for this application from the + local OSTree repository and purges and objects that are no longer + needed to free up disk space. If the same application is later + reinstalled, the objects will be pulled from the remote repository + again. The --keep-ref option can be used to prevent this. If all branches of the application are removed, this command @@ -68,6 +74,15 @@ + + + + + Keep the ref for the application and the objects belonging to it + in the local repository. + + + @@ -77,6 +92,14 @@ + + + + + The architecture to uninstall. + + + diff --git a/doc/xdg-app-uninstall-runtime.xml b/doc/xdg-app-uninstall-runtime.xml index eb70792d..d382ffa9 100644 --- a/doc/xdg-app-uninstall-runtime.xml +++ b/doc/xdg-app-uninstall-runtime.xml @@ -33,7 +33,7 @@ xdg-app uninstall-runtime OPTION RUNTIME - ARCH BRANCH + BRANCH @@ -41,10 +41,16 @@ Description - Uninstalls a runtime. RUNTIME must - name an existing runtime. If ARCH - or BRANCH are specified, only the - matching branches of the runtime are uninstalled. + Uninstalls a runtime. The RUNTIME, + ARCH and BRANCH + arguments must specify an installed runtime. + + + Normally, this command removes the ref for this runtime from the + local OSTree repository and purges and objects that are no longer + needed to free up disk space. If the same runtime is later reinstalled, + the objects will be pulled from the remote repository again. The + --keep-ref option can be used to prevent this. @@ -64,6 +70,15 @@ + + + + + Keep the ref for the runtime and the objects belonging to it + in the local repository. + + + @@ -73,6 +88,14 @@ + + + + + The architecture to uninstall. + + + diff --git a/xdg-app-builtins-uninstall.c b/xdg-app-builtins-uninstall.c index d1a5fe86..987a2d37 100644 --- a/xdg-app-builtins-uninstall.c +++ b/xdg-app-builtins-uninstall.c @@ -10,10 +10,45 @@ #include "xdg-app-builtins.h" #include "xdg-app-utils.h" +static char *opt_arch; +static gboolean opt_keep_ref; + static GOptionEntry options[] = { + { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, "Arch to uninstall", "ARCH" }, + { "keep-ref", 0, 0, G_OPTION_ARG_NONE, &opt_keep_ref, "Keep ref in local repository", NULL }, { NULL } }; +static gboolean +single_child_directory (GFile *dir, const char *name, GCancellable *cancellable) +{ + gboolean ret = FALSE; + gs_unref_object GFileEnumerator *dir_enum = NULL; + gs_unref_object GFileInfo *child_info = NULL; + + dir_enum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, NULL); + + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, NULL))) + { + if (strcmp (name, g_file_info_get_name (child_info)) == 0) + { + g_clear_object (&child_info); + continue; + } + goto out; + } + + ret = TRUE; + +out: + return ret; +} + gboolean xdg_app_builtin_uninstall_runtime (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -21,31 +56,40 @@ xdg_app_builtin_uninstall_runtime (int argc, char **argv, GCancellable *cancella GOptionContext *context; gs_unref_object XdgAppDir *dir = NULL; gs_unref_object GFile *deploy_base = NULL; - const char *runtime; - const char *arch = NULL; - const char *branch = NULL; + gs_unref_object GFile *arch_dir = NULL; + gs_unref_object GFile *top_dir = NULL; + gs_unref_object GFile *origin = NULL; + gs_unref_object OstreeRepo *repo = NULL; + const char *name; + const char *arch; + const char *branch; gs_free char *ref = NULL; + gs_free char *repository = NULL; + gs_strfreev char **deployed = NULL; + int i; + GError *temp_error = NULL; - context = g_option_context_new ("RUNTIME [ARCH [BRANCH]] - Uninstall a runtime"); + context = g_option_context_new ("RUNTIME BRANCH - Uninstall a runtime"); if (!xdg_app_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) goto out; - if (argc < 2) + if (argc < 3) { - usage_error (context, "RUNTIME must be specified", error); + usage_error (context, "RUNTIME and BRANCH must be specified", error); goto out; } - runtime = argv[1]; - if (argc >= 3) - arch = argv[2]; - if (argc >= 4) - branch = argv[3]; + name = argv[1]; + branch = argv[2]; + if (opt_arch) + arch = opt_arch; + else + arch = xdg_app_get_arch (); /* TODO: look for apps, require --force */ - ref = g_build_filename ("runtime", runtime, arch, branch, NULL); + ref = g_build_filename ("runtime", name, arch, branch, NULL); deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (!g_file_query_exists (deploy_base, cancellable)) @@ -54,32 +98,62 @@ xdg_app_builtin_uninstall_runtime (int argc, char **argv, GCancellable *cancella goto out; } + origin = g_file_get_child (deploy_base, "origin"); + if (!g_file_load_contents (origin, cancellable, &repository, NULL, NULL, error)) + goto out; + + g_debug ("dropping active ref"); + if (!xdg_app_dir_set_active (dir, ref, NULL, cancellable, error)) + goto out; + + if (!xdg_app_dir_list_deployed (dir, ref, &deployed, cancellable, error)) + goto out; + + for (i = 0; deployed[i] != NULL; i++) + { + g_debug ("undeploying %s", deployed[i]); + if (!xdg_app_dir_undeploy (dir, ref, deployed[i], cancellable, error)) + goto out; + } + + g_debug ("removing deploy base"); if (!gs_shutil_rm_rf (deploy_base, cancellable, error)) goto out; - if (branch) + g_debug ("cleaning up empty directories"); + arch_dir = g_file_get_parent (deploy_base); + if (!g_file_delete (arch_dir, cancellable, &temp_error)) { - gs_unref_object GFile *db; - - db = deploy_base; - deploy_base = g_file_get_parent (deploy_base); - - if (!g_file_delete (deploy_base, cancellable, NULL)) - goto done; + if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) + { + g_propagate_error (error, temp_error); + goto out; + } + g_clear_error (&temp_error); } - if (arch) + top_dir = g_file_get_parent (arch_dir); + if (!g_file_delete (top_dir, cancellable, &temp_error)) { - gs_unref_object GFile *db; - - db = deploy_base; - deploy_base = g_file_get_parent (deploy_base); - - if (!g_file_delete (deploy_base, cancellable, NULL)) - goto done; + if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) + { + g_propagate_error (error, temp_error); + goto out; + } + g_clear_error (&temp_error); + } + + if (!opt_keep_ref) + { + repo = xdg_app_dir_get_repo (dir); + + if (!ostree_repo_set_ref_immediate (repo, repository, ref, NULL, cancellable, error)) + goto out; + + if (!xdg_app_dir_prune (dir, cancellable, error)) + goto out; } - done: ret = TRUE; out: @@ -95,30 +169,38 @@ xdg_app_builtin_uninstall_app (int argc, char **argv, GCancellable *cancellable, GOptionContext *context; gs_unref_object XdgAppDir *dir = NULL; gs_unref_object GFile *deploy_base = NULL; - const char *app; - const char *arch = NULL; - const char *branch = NULL; + gs_unref_object GFile *arch_dir = NULL; + gs_unref_object GFile *top_dir = NULL; + gs_unref_object GFile *origin = NULL; + gs_unref_object OstreeRepo *repo = NULL; + const char *name; + const char *arch; + const char *branch; gs_free char *ref = NULL; + gs_free char *repository = NULL; + gs_strfreev char **deployed = NULL; + int i; GError *temp_error = NULL; - context = g_option_context_new ("APP [ARCH [BRANCH]] - Uninstall an application"); + context = g_option_context_new ("APP BRANCH - Uninstall an application"); if (!xdg_app_option_context_parse (context, options, &argc, &argv, 0, &dir, cancellable, error)) goto out; - if (argc < 2) + if (argc < 3) { - usage_error (context, "APP must be specified", error); + usage_error (context, "APP and BRANCH must be specified", error); goto out; } - app = argv[1]; - if (argc >= 3) - arch = argv[2]; - if (argc >= 4) - branch = argv[3]; + name = argv[1]; + branch = argv[2]; + if (opt_arch) + arch = opt_arch; + else + arch = xdg_app_get_arch (); - ref = g_build_filename ("app", app, arch, branch, NULL); + ref = g_build_filename ("app", name, arch, branch, NULL); deploy_base = xdg_app_dir_get_deploy_dir (dir, ref); if (!g_file_query_exists (deploy_base, cancellable)) @@ -127,59 +209,61 @@ xdg_app_builtin_uninstall_app (int argc, char **argv, GCancellable *cancellable, goto out; } + origin = g_file_get_child (deploy_base, "origin"); + if (!g_file_load_contents (origin, cancellable, &repository, NULL, NULL, error)) + goto out; + + g_debug ("dropping active ref"); + if (!xdg_app_dir_set_active (dir, ref, NULL, cancellable, error)) + goto out; + + if (!xdg_app_dir_list_deployed (dir, ref, &deployed, cancellable, error)) + goto out; + + for (i = 0; deployed[i] != NULL; i++) + { + g_debug ("undeploying %s", deployed[i]); + if (!xdg_app_dir_undeploy (dir, ref, deployed[i], cancellable, error)) + goto out; + } + + g_debug ("removing deploy base"); if (!gs_shutil_rm_rf (deploy_base, cancellable, error)) goto out; - if (branch) + g_debug ("cleaning up empty directories"); + arch_dir = g_file_get_parent (deploy_base); + if (!g_file_delete (arch_dir, cancellable, &temp_error)) { - gs_unref_object GFile *db; - - db = deploy_base; - deploy_base = g_file_get_parent (deploy_base); - - if (!g_file_delete (deploy_base, cancellable, NULL)) - goto done; - } - - if (arch) - { - gs_unref_object GFile *db; - gs_unref_object GFileEnumerator *dir_enum; - gs_unref_object GFileInfo *child_info; - - db = deploy_base; - deploy_base = g_file_get_parent (deploy_base); - - dir_enum = g_file_enumerate_children (deploy_base, G_FILE_ATTRIBUTE_STANDARD_NAME, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) + if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) { - const char *arch; - - arch = g_file_info_get_name (child_info); - if (strcmp (arch, "data") == 0) - continue; - - goto done; + g_propagate_error (error, temp_error); + goto out; } - if (temp_error != NULL) + g_clear_error (&temp_error); + } + + top_dir = g_file_get_parent (arch_dir); + if (single_child_directory (top_dir, "data", cancellable)) + { + if (!gs_shutil_rm_rf (top_dir, cancellable, error)) + goto out; + } + + if (!opt_keep_ref) + { + repo = xdg_app_dir_get_repo (dir); + + if (!ostree_repo_set_ref_immediate (repo, repository, ref, NULL, cancellable, error)) goto out; - if (!gs_shutil_rm_rf (deploy_base, cancellable, error)) + if (!xdg_app_dir_prune (dir, cancellable, error)) goto out; } - done: ret = TRUE; out: - if (temp_error != NULL) - g_propagate_error (error, temp_error); - if (context) g_option_context_free (context); return ret;