extensions-tool: Add 'install' command

The ability to install unaudited extensions directly from a zip file
can be useful for testing and code review, so implement a corresponding
command that complements the previously added 'pack' command.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/1234
This commit is contained in:
Florian Müllner 2018-09-04 01:41:55 +02:00
parent f9357457bf
commit a429fdbd08
9 changed files with 268 additions and 24 deletions

View File

@ -0,0 +1,207 @@
/* command-install.c
*
* Copyright 2018 Florian Müllner <fmuellner@gnome.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gnome-autoar/gnome-autoar.h>
#include <json-glib/json-glib.h>
#include "commands.h"
#include "common.h"
#include "config.h"
static JsonObject *
load_metadata (GFile *dir,
GError **error)
{
g_autoptr (JsonParser) parser = NULL;
g_autoptr (GInputStream) stream = NULL;
g_autoptr (GFile) file = NULL;
file = g_file_get_child (dir, "metadata.json");
stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
if (stream == NULL)
return NULL;
parser = json_parser_new_immutable ();
if (!json_parser_load_from_stream (parser, stream, NULL, error))
return NULL;
return json_node_dup_object (json_parser_get_root (parser));
}
static void
on_error (AutoarExtractor *extractor,
GError *error,
gpointer data)
{
*((GError **)data) = g_error_copy (error);
}
static GFile *
on_decide_destination (AutoarExtractor *extractor,
GFile *dest,
GList *files,
gpointer data)
{
g_autofree char *dest_path = NULL;
GFile *new_dest;
int copy = 1;
dest_path = g_file_get_path (dest);
new_dest = g_object_ref (dest);
while (g_file_query_exists (new_dest, NULL))
{
g_autofree char *new_path = g_strdup_printf ("%s (%d)", dest_path, copy);
g_object_unref (new_dest);
new_dest = g_file_new_for_path (new_path);
copy++;
}
*((GFile **)data) = g_object_ref (new_dest);
return new_dest;
}
static int
install_extension (const char *bundle,
gboolean force)
{
g_autoptr (AutoarExtractor) extractor = NULL;
g_autoptr (JsonObject) metadata = NULL;
g_autoptr (GFile) cachedir = NULL;
g_autoptr (GFile) tmpdir = NULL;
g_autoptr (GFile) src = NULL;
g_autoptr (GFile) dst = NULL;
g_autoptr (GFile) dstdir = NULL;
g_autoptr (GError) error = NULL;
g_autofree char *cwd = NULL;
const char *uuid;
cwd = g_get_current_dir ();
src = g_file_new_for_commandline_arg_and_cwd (bundle, cwd);
cachedir = g_file_new_for_path (g_get_user_cache_dir ());
extractor = autoar_extractor_new (src, cachedir);
g_signal_connect (extractor, "error", G_CALLBACK (on_error), &error);
g_signal_connect (extractor, "decide-destination", G_CALLBACK (on_decide_destination), &tmpdir);
autoar_extractor_start (extractor, NULL);
if (error != NULL)
goto err;
metadata = load_metadata (tmpdir, &error);
if (metadata == NULL)
goto err;
dstdir = g_file_new_build_filename (g_get_user_data_dir (),
"gnome-shell", "extensions", NULL);
if (!g_file_make_directory_with_parents (dstdir, NULL, &error))
goto err;
uuid = json_object_get_string_member (metadata, "uuid");
dst = g_file_get_child (dstdir, uuid);
if (g_file_query_exists (dst, NULL))
{
if (!force)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_EXISTS,
"%s exists and --force was not specified", uuid);
goto err;
}
else if (!file_delete_recursively (dst, &error))
{
goto err;
}
}
if (!g_file_move (tmpdir, dst, G_FILE_COPY_NONE, NULL, NULL, NULL, &error))
goto err;
return 0;
err:
if (error != NULL)
g_printerr ("%s\n", error->message);
if (tmpdir != NULL)
file_delete_recursively (tmpdir, NULL);
return 2;
}
int
handle_install (int argc, char *argv[], gboolean do_help)
{
g_autoptr (GOptionContext) context = NULL;
g_autoptr (GError) error = NULL;
g_auto (GStrv) filenames = NULL;
gboolean force = FALSE;
GOptionEntry entries[] = {
{ .long_name = "force", .short_name = 'f',
.arg = G_OPTION_ARG_NONE, .arg_data = &force,
.description = _("Overwrite an existing extension") },
{ .long_name = G_OPTION_REMAINING,
.arg_description =_("EXTENSION_BUNDLE"),
.arg = G_OPTION_ARG_FILENAME_ARRAY, .arg_data = &filenames },
{ NULL }
};
g_set_prgname ("gnome-extensions install");
context = g_option_context_new (NULL);
g_option_context_set_help_enabled (context, FALSE);
g_option_context_set_summary (context, _("Install an extension bundle"));
g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
if (do_help)
{
show_help (context, NULL);
return 0;
}
if (!g_option_context_parse (context, &argc, &argv, &error))
{
show_help (context, error->message);
return 1;
}
if (filenames == NULL)
{
show_help (context, _("No extension bundle specified"));
return 1;
}
if (g_strv_length (filenames) > 1)
{
show_help (context, _("More than one extension bundle specified"));
return 1;
}
return install_extension (*filenames, force);
}

View File

@ -38,28 +38,6 @@ typedef struct _ExtensionPack {
static void extension_pack_free (ExtensionPack *);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ExtensionPack, extension_pack_free);
static void
delete_recursively (GFile *file)
{
g_autoptr (GFileEnumerator) file_enum = NULL;
GFile *child;
file_enum = g_file_enumerate_children (file, NULL, 0, NULL, NULL);
if (file_enum)
while (TRUE)
{
if (!g_file_enumerator_iterate (file_enum, NULL, &child, NULL, NULL))
return;
if (child == NULL)
break;
delete_recursively (child);
}
g_file_delete (file, NULL, NULL);
}
static ExtensionPack *
extension_pack_new (const char *srcdir)
{
@ -74,7 +52,7 @@ static void
extension_pack_free (ExtensionPack *pack)
{
if (pack->tmpdir)
delete_recursively (pack->tmpdir);
file_delete_recursively (pack->tmpdir, NULL);
g_clear_pointer (&pack->files, g_hash_table_destroy);
g_clear_pointer (&pack->metadata, json_object_unref);

View File

@ -30,5 +30,6 @@ int handle_list (int argc, char *argv[], gboolean do_help);
int handle_info (int argc, char *argv[], gboolean do_help);
int handle_create (int argc, char *argv[], gboolean do_help);
int handle_pack (int argc, char *argv[], gboolean do_help);
int handle_install (int argc, char *argv[], gboolean do_help);
G_END_DECLS

View File

@ -54,4 +54,7 @@ void print_extension_info (GVariantDict *info,
GDBusProxy *get_shell_proxy (GError **error);
GSettings *get_shell_settings (void);
gboolean file_delete_recursively (GFile *file,
GError **error);
G_END_DECLS

View File

@ -5,7 +5,7 @@
################################################################################
__gnome_extensions() {
local commands="version enable disable info show list create pack"
local commands="version enable disable info install show list create pack"
local COMMAND=${COMP_WORDS[1]}
_init_completion -s || return
@ -54,6 +54,13 @@ __gnome_extensions() {
;;
esac
;;
install)
if [[ $cur != -* ]]
then
_filedir zip
return 0
fi
;;
esac
# Stop if we are currently waiting for an option value

View File

@ -126,6 +126,31 @@ print_extension_info (GVariantDict *info,
g_print (" %s: %s\n", _("State"), extension_state_to_string (state));
}
gboolean
file_delete_recursively (GFile *file,
GError **error)
{
g_autoptr (GFileEnumerator) file_enum = NULL;
GFile *child;
file_enum = g_file_enumerate_children (file, NULL, 0, NULL, NULL);
if (file_enum)
while (TRUE)
{
if (!g_file_enumerator_iterate (file_enum, NULL, &child, NULL, error))
return FALSE;
if (child == NULL)
break;
if (!file_delete_recursively (child, error))
return FALSE;
}
return g_file_delete (file, NULL, error);
}
static int
handle_version (int argc, char *argv[], gboolean do_help)
{
@ -163,6 +188,7 @@ usage (void)
g_printerr (" show %s\n", _("Show extension info"));
g_printerr (" create %s\n", _("Create extension"));
g_printerr (" pack %s\n", _("Package extension"));
g_printerr (" install %s\n", _("Install extension bundle"));
g_printerr ("\n");
g_printerr (_("Use %s to get detailed help.\n"), "“gnome-extensions help COMMAND”");
}
@ -230,6 +256,8 @@ main (int argc, char *argv[])
return handle_create (argc, argv, do_help);
else if (g_str_equal (command, "pack"))
return handle_pack (argc, argv, do_help);
else if (g_str_equal (command, "install"))
return handle_install (argc, argv, do_help);
else
usage ();

View File

@ -29,6 +29,8 @@ SYNOPSIS
*gnome-extensions* pack ['OPTION'...]
*gnome-extensions* install ['OPTION'...] 'PACK'
DESCRIPTION
-----------
*gnome-extensions* is a utility that makes some common GNOME extensions
@ -147,6 +149,22 @@ the current directory otherwise.
*--out-dir*='DIRECTORY':::
The directory where the pack should be created
*install* ['OPTION'...] 'PACK'::
Installs an extension from the bundle 'PACK'.
+
The command unpacks the extension files and moves them to
the expected location in the user's *$HOME*, so that it
will be loaded in the next session.
+
It is mainly intended for testing, not as a replacement for
GNOME Software or the extension website. As extensions have
privileged access to the user's session, it is advised to
never load extensions from untrusted sources without carefully
reviewing their content.
+
.Options
*--force*:::
Override an existing extension
EXIT STATUS
-----------

View File

@ -4,6 +4,7 @@ sources = [
'command-disable.c',
'command-enable.c',
'command-info.c',
'command-install.c',
'command-list.c',
'command-pack.c',
'common.h',

View File

@ -13,6 +13,7 @@ sources = [
'command-disable.c',
'command-enable.c',
'command-info.c',
'command-install.c',
'command-list.c',
'command-pack.c',
'main.c'