install: Support installing multiple apps at the same time

Instead of using "NAME [BRANCH]" as the command list we now
support REF..., where each REF can be partial. This is easiest
explained by examples. Here are some valid refs:

  org.test.App - only app id
  app/org.test.App/x86_64/stable - full ref
  org.test.App/x86_64/stable - full ref without prefix
  org.test.App - only app id
  org.test.App//stable - only branch
  org.test.App/x86_64 - only arch

If any parts are left out they are wildcarded. Such parts are filled
first by looking at other command line arguments like --arch and
--app/--runtime. And finally by looking at what is available in the
remote. If there are multiple matches the user is told the options
in an error message.
tingping/wmclass
Alexander Larsson 2016-10-14 15:45:16 +02:00
parent a5d1f6331b
commit 7018717ce2
4 changed files with 245 additions and 84 deletions

View File

@ -151,8 +151,12 @@ do_install (FlatpakDir *dir,
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,
@ -223,6 +227,7 @@ install_from (FlatpakDir *dir,
g_autofree char *remote = NULL;
g_autofree char *ref = NULL;
g_auto(GStrv) parts = NULL;
const char *slash;
FlatpakDir *clone;
if (argc < 2)
@ -248,8 +253,8 @@ install_from (FlatpakDir *dir,
if (!flatpak_dir_ensure_repo (clone, cancellable, error))
return FALSE;
parts = g_strsplit (ref, "/", 0);
g_print (_("Installing: %s\n"), parts[1]);
slash = strchr (ref, '/');
g_print (_("Installing: %s\n"), slash + 1);
if (!do_install (clone,
opt_no_pull,
@ -262,22 +267,37 @@ install_from (FlatpakDir *dir,
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_install (int argc, char **argv, GCancellable *cancellable, GError **error)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(FlatpakDir) dir = NULL;
const char *repository;
const char *pref = NULL;
char **prefs = NULL;
int i, n_prefs;
const char *default_branch = NULL;
g_autofree char *ref = NULL;
FlatpakKinds kinds;
FlatpakKinds kind;
g_autofree char *id = NULL;
g_autofree char *arch = NULL;
g_autofree char *branch = NULL;
g_autoptr(GPtrArray) refs = NULL;
context = g_option_context_new (_("REPOSITORY NAME [BRANCH] - Install an application or runtime"));
context = g_option_context_new (_("REPOSITORY 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))
@ -290,34 +310,58 @@ 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 NAME must be specified"), error);
return usage_error (context, _("REPOSITORY and REF must be specified"), error);
if (argc > 4)
return usage_error (context, _("Too many arguments"), error);
repository = argv[1];
pref = argv[2];
if (argc >= 4)
default_branch = argv[3];
prefs = &argv[2];
n_prefs = argc - 2;
/* Backwards compat for old "REPOSITORY NAME [BRANCH]" argument version */
if (argc == 4 && looks_like_branch (argv[3]))
{
default_branch = argv[3];
n_prefs = 1;
}
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, default_branch,
&kinds, &id, &arch, &branch, error))
return FALSE;
refs = g_ptr_array_new_with_free_func (g_free);
for (i = 0; i < n_prefs; i++)
{
const char *pref = prefs[i];
FlatpakKinds matched_kinds;
g_autofree char *id = NULL;
g_autofree char *arch = NULL;
g_autofree char *branch = NULL;
FlatpakKinds kind;
g_autofree char *ref = NULL;
ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, arch,
kinds, &kind, cancellable, error);
if (ref == NULL)
return FALSE;
if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, default_branch,
&matched_kinds, &id, &arch, &branch, error))
return FALSE;
if (!do_install (dir,
opt_no_pull,
opt_no_deploy,
ref, repository,
(const char **)opt_subpaths,
cancellable, error))
return FALSE;
ref = flatpak_dir_find_remote_ref (dir, repository, id, branch, arch,
matched_kinds, &kind, cancellable, error);
if (ref == NULL)
return FALSE;
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))
return FALSE;
}
return TRUE;
}
@ -327,8 +371,6 @@ flatpak_complete_install (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(FlatpakDir) dir = NULL;
g_autoptr(GError) error = NULL;
g_auto(GStrv) refs = NULL;
FlatpakKinds kinds;
int i;
@ -338,8 +380,6 @@ flatpak_complete_install (FlatpakCompletion *completion)
kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);
flatpak_completion_debug ("install argc %d", completion->argc);
switch (completion->argc)
{
case 0:
@ -358,37 +398,8 @@ flatpak_complete_install (FlatpakCompletion *completion)
break;
case 2: /* Name */
refs = flatpak_dir_find_remote_refs (dir, completion->argv[1], NULL, NULL,
opt_arch, kinds,
NULL, &error);
if (refs == NULL)
flatpak_completion_debug ("find remote refs error: %s", error->message);
for (i = 0; refs != NULL && refs[i] != NULL; i++)
{
g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL);
if (parts)
flatpak_complete_word (completion, "%s ", parts[1]);
}
break;
case 3: /* Branch */
refs = flatpak_dir_find_remote_refs (dir, completion->argv[1], completion->argv[2], NULL,
opt_arch, kinds,
NULL, &error);
if (refs == NULL)
flatpak_completion_debug ("find remote refs error: %s", error->message);
for (i = 0; refs != NULL && refs[i] != NULL; i++)
{
g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL);
if (parts)
flatpak_complete_word (completion, "%s ", parts[3]);
}
break;
default:
flatpak_complete_partial_remote_ref (completion, kinds, opt_arch, dir, completion->argv[1]);
break;
}

View File

@ -684,15 +684,16 @@ flatpak_kinds_from_bools (gboolean app, gboolean runtime)
}
gboolean
flatpak_split_partial_ref_arg (const char *partial_ref,
FlatpakKinds default_kinds,
const char *default_arch,
const char *default_branch,
FlatpakKinds *out_kinds,
char **out_id,
char **out_arch,
char **out_branch,
GError **error)
_flatpak_split_partial_ref_arg (const char *partial_ref,
gboolean validate,
FlatpakKinds default_kinds,
const char *default_arch,
const char *default_branch,
FlatpakKinds *out_kinds,
char **out_id,
char **out_arch,
char **out_branch,
GError **error)
{
const char *id_start = NULL;
const char *id_end = NULL;
@ -723,7 +724,7 @@ flatpak_split_partial_ref_arg (const char *partial_ref,
id_end = next_element (&partial_ref);
id = g_strndup (id_start, id_end - id_start);
if (!flatpak_is_valid_name (id, &local_error))
if (validate && !flatpak_is_valid_name (id, &local_error))
return flatpak_fail (error, "Invalid id %s: %s", id, local_error->message);
arch_start = partial_ref;
@ -740,7 +741,7 @@ flatpak_split_partial_ref_arg (const char *partial_ref,
else
branch = g_strdup (default_branch);
if (branch != NULL && !flatpak_is_valid_branch (branch, &local_error))
if (validate && branch != NULL && !flatpak_is_valid_branch (branch, &local_error))
return flatpak_fail (error, "Invalid branch %s: %s", branch, local_error->message);
if (out_kinds)
@ -755,6 +756,52 @@ flatpak_split_partial_ref_arg (const char *partial_ref,
return TRUE;
}
gboolean
flatpak_split_partial_ref_arg (const char *partial_ref,
FlatpakKinds default_kinds,
const char *default_arch,
const char *default_branch,
FlatpakKinds *out_kinds,
char **out_id,
char **out_arch,
char **out_branch,
GError **error)
{
return _flatpak_split_partial_ref_arg (partial_ref,
TRUE,
default_kinds,
default_arch,
default_branch,
out_kinds,
out_id,
out_arch,
out_branch,
error);
}
gboolean
flatpak_split_partial_ref_arg_novalidate (const char *partial_ref,
FlatpakKinds default_kinds,
const char *default_arch,
const char *default_branch,
FlatpakKinds *out_kinds,
char **out_id,
char **out_arch,
char **out_branch)
{
return _flatpak_split_partial_ref_arg (partial_ref,
FALSE,
default_kinds,
default_arch,
default_branch,
out_kinds,
out_id,
out_arch,
out_branch,
NULL);
}
char *
flatpak_compose_ref (gboolean app,
const char *name,
@ -3851,6 +3898,91 @@ flatpak_complete_ref (FlatpakCompletion *completion,
}
}
int
find_current_element (const char *str)
{
int count = 0;
if (g_str_has_prefix (str, "app/"))
str += strlen ("app/");
else if (g_str_has_prefix (str, "runtime/"))
str += strlen ("runtime/");
while (str != NULL && count <= 3)
{
str = strchr (str, '/');
count++;
if (str != NULL)
str = str + 1;
}
return count;
}
void
flatpak_complete_partial_remote_ref (FlatpakCompletion *completion,
FlatpakKinds kinds,
const char *only_arch,
FlatpakDir *dir,
const char *remote)
{
FlatpakKinds matched_kinds;
const char *pref;
g_autofree char *id = NULL;
g_autofree char *arch = NULL;
g_autofree char *branch = NULL;
g_auto(GStrv) refs = NULL;
int element;
const char *cur_parts[4] = { NULL };
g_autoptr(GError) error = NULL;
int i;
pref = completion->cur;
element = find_current_element (pref);
flatpak_split_partial_ref_arg_novalidate (pref, kinds,
NULL, NULL,
&matched_kinds, &id, &arch, &branch);
cur_parts[1] = id;
cur_parts[2] = arch ? arch : "";
cur_parts[3] = branch ? branch : "";
refs = flatpak_dir_find_remote_refs (dir, completion->argv[1],
(element > 1) ? id : NULL,
(element > 3) ? branch : NULL,
(element > 2 )? arch : only_arch,
matched_kinds, NULL, &error);
if (refs == NULL)
flatpak_completion_debug ("find remote refs error: %s", error->message);
for (i = 0; refs != NULL && refs[i] != NULL; i++)
{
int j;
g_autoptr(GString) comp = NULL;
g_auto(GStrv) parts = flatpak_decompose_ref (refs[i], NULL);
if (parts == NULL)
continue;
if (!g_str_has_prefix (parts[element], cur_parts[element]))
continue;
comp = g_string_new (pref);
g_string_append (comp, parts[element] + strlen (cur_parts[element]));
/* Only complete on the last part if the user explicitly adds a / */
if (element >= 2)
{
for (j = element + 1; j < 4; j++)
{
g_string_append (comp, "/");
g_string_append (comp, parts[j]);
}
}
flatpak_complete_word (completion, "%s", comp->str);
}
}
static gboolean
switch_already_in_line (FlatpakCompletion *completion,
GOptionEntry *entry)

View File

@ -100,6 +100,14 @@ gboolean flatpak_split_partial_ref_arg (const char *partial_ref,
char **out_arch,
char **out_branch,
GError **error);
gboolean flatpak_split_partial_ref_arg_novalidate (const char *partial_ref,
FlatpakKinds default_kinds,
const char *default_arch,
const char *default_branch,
FlatpakKinds *out_kinds,
char **out_id,
char **out_arch,
char **out_branch);
char * flatpak_compose_ref (gboolean app,
const char *name,
@ -478,6 +486,11 @@ void flatpak_complete_word (FlatpakCompletion *completion,
...);
void flatpak_complete_ref (FlatpakCompletion *completion,
OstreeRepo *repo);
void flatpak_complete_partial_remote_ref (FlatpakCompletion *completion,
FlatpakKinds kinds,
const char *only_arch,
FlatpakDir *dir,
const char *remote);
void flatpak_complete_file (FlatpakCompletion *completion);
void flatpak_complete_dir (FlatpakCompletion *completion);
void flatpak_complete_options (FlatpakCompletion *completion,

View File

@ -33,8 +33,7 @@
<command>flatpak install</command>
<arg choice="opt" rep="repeat">OPTION</arg>
<arg choice="plain">REMOTE</arg>
<arg choice="plain">NAME</arg>
<arg choice="opt">BRANCH</arg>
<arg choice="plain" rep="repeat">REF</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>flatpak install</command>
@ -52,14 +51,21 @@
<para>
Installs an application or runtime. <arg choice="plain">REMOTE</arg> must name
an existing remote and <arg choice="plain">NAME</arg> is the name of the
application or runtime to install. Optionally, <arg choice="plain">BRANCH</arg> can
be specified to install a branch other than the default branch. This required
if there are multiple matches in the selected remote.
an existing remote and <arg choice="plain">REF</arg> is a reference to the
application or runtime to install.
</para>
<para>
By default this looks for both apps and runtime with the given <arg choice="plain">NAME</arg> in
the specified <arg choice="plain">REMOTE</arg>, but you can limit this by using the --app or --runtime option.
Each <arg choice="plain">REF</arg> arguments is a full or partial indentifier in the
flatpak ref format, which looks like "(app|runtime)/ID/ARCH/BRANCH". All elements
except ID are optional and can be left out, including the slashes,
so most of the time you need only specify ID. Any part left out will be matched
against what is in the remote, and if there are multiple matches an error message
will list the alternatives.
</para>
<para>
By default this looks for both apps and runtime with the given <arg choice="plain">REF</arg> in
the specified <arg choice="plain">REMOTE</arg>, but you can limit this by using the --app or
--runtime option, or by supplying the initial element in the REF.
</para>
<para>
Note that flatpak allows one to have multiple branches of an application and runtimes
@ -134,7 +140,7 @@
<term><option>--arch=ARCH</option></term>
<listitem><para>
The architecture to install for.
The default architecture to install for, if not given explicitly in the <arg choice="plain">REF</arg>.
</para></listitem>
</varlistentry>
@ -175,15 +181,14 @@
<term><option>--app</option></term>
<listitem><para>
Only look for an app with the given name.
Assume that all <arg choice="plain">REF</arg>s are apps if not explicitly specified.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--runtime</option></term>
<listitem><para>
Only look for an runtime with the given name.
Assume that all <arg choice="plain">REF</arg>s are runtimes if not explicitly specified.
</para></listitem>
</varlistentry>