[gnome-builder] flatpak: implement install RPC using transactions



commit 7396a3d8f212c480d10f3134a83d9c2fd611daf5
Author: Christian Hergert <chergert redhat com>
Date:   Mon May 3 13:44:23 2021 -0700

    flatpak: implement install RPC using transactions

 .../flatpak/daemon/ipc-flatpak-service-impl.c      | 234 ++++++++++++++++++++-
 src/plugins/flatpak/daemon/meson.build             |  10 +
 .../daemon/org.gnome.Builder.Flatpak.Service.xml   |   7 +-
 .../daemon/org.gnome.Builder.Flatpak.Transfer.xml  |   7 +-
 src/plugins/flatpak/daemon/test-install.c          | 133 ++++++++++++
 5 files changed, 375 insertions(+), 16 deletions(-)
---
diff --git a/src/plugins/flatpak/daemon/ipc-flatpak-service-impl.c 
b/src/plugins/flatpak/daemon/ipc-flatpak-service-impl.c
index 122d779a7..5c0d08499 100644
--- a/src/plugins/flatpak/daemon/ipc-flatpak-service-impl.c
+++ b/src/plugins/flatpak/daemon/ipc-flatpak-service-impl.c
@@ -23,8 +23,10 @@
 #include "config.h"
 
 #include <flatpak/flatpak.h>
+#include <glib/gi18n.h>
 
 #include "ipc-flatpak-service-impl.h"
+#include "ipc-flatpak-transfer.h"
 #include "ipc-flatpak-util.h"
 
 typedef struct
@@ -65,6 +67,7 @@ struct _IpcFlatpakServiceImpl
   IpcFlatpakServiceSkeleton parent;
   GHashTable *installs;
   GPtrArray *runtimes;
+  GPtrArray *installs_ordered;
 };
 
 static void      ipc_flatpak_service_impl_install_changed_cb (IpcFlatpakServiceImpl  *self,
@@ -196,6 +199,13 @@ add_runtime (IpcFlatpakServiceImpl *self,
   ipc_flatpak_service_emit_runtime_added (IPC_FLATPAK_SERVICE (self), variant);
 }
 
+static FlatpakInstallation *
+ipc_flatpak_service_impl_ref_user_installation (IpcFlatpakServiceImpl *self)
+{
+  Install *install = g_ptr_array_index (self->installs_ordered, 0);
+  return g_object_ref (install->installation);
+}
+
 static void
 runtime_free (Runtime *runtime)
 {
@@ -353,6 +363,7 @@ add_installation (IpcFlatpakServiceImpl  *self,
 
   install_reload (self, install);
 
+  g_ptr_array_add (self->installs_ordered, install);
   g_hash_table_insert (self->installs,
                        g_steal_pointer (&file),
                        g_steal_pointer (&install));
@@ -500,8 +511,6 @@ ipc_flatpak_service_impl_runtime_is_known (IpcFlatpakService     *service,
   const gchar *ref_name;
   const gchar *ref_arch;
   const gchar *ref_branch;
-  GHashTableIter iter;
-  Install *install;
   IsKnown *state;
 
   g_assert (IPC_IS_FLATPAK_SERVICE_IMPL (self));
@@ -544,10 +553,11 @@ ipc_flatpak_service_impl_runtime_is_known (IpcFlatpakService     *service,
   g_task_set_source_tag (task, ipc_flatpak_service_impl_runtime_is_known);
   g_task_set_task_data (task, state, (GDestroyNotify) is_known_free);
 
-  /* Now check remote refs */
-  g_hash_table_iter_init (&iter, self->installs);
-  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&install))
-    g_ptr_array_add (state->installs, g_object_ref (install->installation));
+  for (guint i = 0; i < self->installs_ordered->len; i++)
+    {
+      const Install *install = g_ptr_array_index (self->installs_ordered, i);
+      g_ptr_array_add (state->installs, g_object_ref (install->installation));
+    }
 
   g_task_run_in_thread (task, is_known_worker);
 
@@ -581,22 +591,226 @@ ipc_flatpak_service_impl_install_changed_cb (IpcFlatpakServiceImpl *self,
     }
 }
 
+typedef struct
+{
+  FlatpakInstallation   *installation;
+  GDBusMethodInvocation *invocation;
+  IpcFlatpakTransfer    *transfer;
+  char                  *remote;
+  char                  *ref;
+  char                  *parent_window;
+} InstallState;
+
+static void
+install_state_free (InstallState *state)
+{
+  g_clear_object (&state->installation);
+  g_clear_object (&state->invocation);
+  g_clear_object (&state->transfer);
+  g_clear_pointer (&state->remote, g_free);
+  g_clear_pointer (&state->ref, g_free);
+  g_clear_pointer (&state->parent_window, g_free);
+  g_slice_free (InstallState, state);
+}
+
+static void
+on_progress_changed (FlatpakTransactionProgress *progress,
+                     IpcFlatpakTransfer         *transfer)
+{
+  g_autofree char *message = NULL;
+  int val;
+
+  g_assert (FLATPAK_IS_TRANSACTION_PROGRESS (progress));
+  g_assert (IPC_IS_FLATPAK_TRANSFER (transfer));
+
+  val = flatpak_transaction_progress_get_progress (progress);
+  message = flatpak_transaction_progress_get_status (progress);
+
+  ipc_flatpak_transfer_set_message (transfer, message);
+  ipc_flatpak_transfer_set_fraction (transfer, (double)val / 100.0);
+}
+
+static void
+on_new_operation_cb (FlatpakTransaction          *transaction,
+                     FlatpakTransactionOperation *operation,
+                     FlatpakTransactionProgress  *progress,
+                     IpcFlatpakTransfer          *transfer)
+{
+  g_assert (FLATPAK_IS_TRANSACTION (transaction));
+  g_assert (FLATPAK_IS_TRANSACTION_OPERATION (operation));
+  g_assert (FLATPAK_IS_TRANSACTION_PROGRESS (progress));
+  g_assert (IPC_IS_FLATPAK_TRANSFER (transfer));
+
+  g_signal_connect_object (progress,
+                           "changed",
+                           G_CALLBACK (on_progress_changed),
+                           transfer,
+                           0);
+  on_progress_changed (progress, transfer);
+}
+
+static gboolean
+connect_signals (FlatpakTransaction  *transaction,
+                 IpcFlatpakTransfer  *transfer,
+                 GError             **error)
+{
+  g_signal_connect_object (transaction,
+                           "new-operation",
+                           G_CALLBACK (on_new_operation_cb),
+                           transfer,
+                           0);
+  return TRUE;
+}
+
+static void
+install_worker (GTask        *task,
+                gpointer      source_object,
+                gpointer      task_data,
+                GCancellable *cancellable)
+{
+  InstallState *state = task_data;
+  g_autoptr(FlatpakTransaction) transaction = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IPC_IS_FLATPAK_SERVICE_IMPL (source_object));
+  g_assert (state != NULL);
+  g_assert (state->remote != NULL);
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (state->invocation));
+  g_assert (IPC_IS_FLATPAK_TRANSFER (state->transfer));
+  g_assert (FLATPAK_IS_INSTALLATION (state->installation));
+
+  ipc_flatpak_transfer_set_fraction (state->transfer, 0.0);
+  ipc_flatpak_transfer_set_message (state->transfer, "");
+
+  if (!(transaction = flatpak_transaction_new_for_installation (state->installation, NULL, &error)) ||
+      !flatpak_transaction_add_install (transaction, state->remote, state->ref, NULL, &error) ||
+      !connect_signals (transaction, state->transfer, &error) ||
+      !flatpak_transaction_run (transaction, cancellable, &error))
+    {
+      ipc_flatpak_transfer_set_fraction (state->transfer, 1.0);
+      ipc_flatpak_transfer_set_message (state->transfer, _("Installation failed"));
+      complete_wrapped_error (g_steal_pointer (&state->invocation), g_steal_pointer (&error));
+    }
+  else
+    {
+      ipc_flatpak_transfer_set_fraction (state->transfer, 1.0);
+      ipc_flatpak_transfer_set_message (state->transfer, _("Installation complete"));
+      ipc_flatpak_service_complete_install (source_object, g_steal_pointer (&state->invocation));
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+refs_equal (FlatpakRef *a,
+            FlatpakRef *b)
+{
+  return (str_equal0 (flatpak_ref_get_name (a),
+                      flatpak_ref_get_name (b)) &&
+          str_equal0 (flatpak_ref_get_arch (a),
+                      flatpak_ref_get_arch (b)) &&
+          str_equal0 (flatpak_ref_get_branch (a),
+                      flatpak_ref_get_branch (b)));
+}
+
+static char *
+find_remote_for_ref (IpcFlatpakServiceImpl *self,
+                     FlatpakRef            *ref)
+{
+  g_assert (IPC_IS_FLATPAK_SERVICE_IMPL (self));
+  g_assert (FLATPAK_IS_REF (ref));
+
+  /* Someday we might want to prompt the user for which remote to install from,
+   * but for now we'll just take the first.
+   */
+
+  for (guint i = 0; i < self->installs_ordered->len; i++)
+    {
+      Install *install = g_ptr_array_index (self->installs_ordered, i);
+      g_autoptr(GPtrArray) remotes = flatpak_installation_list_remotes (install->installation, NULL, NULL);
+
+      if (remotes == NULL)
+        continue;
+
+      for (guint j = 0; j < remotes->len; j++)
+        {
+          FlatpakRemote *remote = g_ptr_array_index (remotes, j);
+          const char *name = flatpak_remote_get_name (remote);
+          g_autoptr(GPtrArray) refs = flatpak_installation_list_remote_refs_sync_full 
(install->installation, name,
+                                                                                       
FLATPAK_QUERY_FLAGS_ONLY_CACHED,
+                                                                                       NULL, NULL);
+
+          if (refs == NULL)
+            continue;
+
+          for (guint k = 0; k < refs->len; k++)
+            {
+              FlatpakRef *remote_ref = g_ptr_array_index (refs, k);
+
+              if (refs_equal (ref, remote_ref))
+                return g_strdup (name);
+            }
+        }
+    }
+
+  return NULL;
+}
+
 static gboolean
 ipc_flatpak_service_impl_install (IpcFlatpakService     *service,
                                   GDBusMethodInvocation *invocation,
-                                  const gchar           *full_ref_name)
+                                  const gchar           *full_ref_name,
+                                  const gchar           *transfer_path,
+                                  const gchar           *parent_window)
 {
+  IpcFlatpakServiceImpl *self = (IpcFlatpakServiceImpl *)service;
+  g_autoptr(IpcFlatpakTransfer) transfer = NULL;
   g_autoptr(FlatpakRef) ref = NULL;
   g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autofree char *remote = NULL;
+  GDBusConnection *connection;
+  InstallState *state;
 
-  g_assert (IPC_IS_FLATPAK_SERVICE_IMPL (service));
+  g_assert (IPC_IS_FLATPAK_SERVICE_IMPL (self));
   g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
   g_assert (full_ref_name != NULL);
+  g_assert (transfer_path != NULL);
+  g_assert (parent_window != NULL);
 
   if (!(ref = flatpak_ref_parse (full_ref_name, &error)))
     return complete_wrapped_error (invocation, error);
 
-  ipc_flatpak_service_complete_install (service, invocation, "");
+  connection = g_dbus_method_invocation_get_connection (invocation);
+  remote = find_remote_for_ref (self, ref);
+  transfer = ipc_flatpak_transfer_proxy_new_sync (connection,
+                                                  G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                                  NULL,
+                                                  transfer_path,
+                                                  NULL, NULL);
+
+  if (remote == NULL)
+    {
+      g_dbus_method_invocation_return_error (g_steal_pointer (&invocation),
+                                             G_DBUS_ERROR,
+                                             G_DBUS_ERROR_FAILED,
+                                             "No configured remote contains ref");
+      return TRUE;
+    }
+
+  state = g_slice_new0 (InstallState);
+  state->installation = ipc_flatpak_service_impl_ref_user_installation (self);
+  state->invocation = g_steal_pointer (&invocation);
+  state->ref = g_strdup (full_ref_name);
+  state->remote = g_steal_pointer (&remote);
+  state->parent_window = parent_window[0] ? g_strdup (parent_window) : NULL;
+  state->transfer = g_object_ref (transfer);
+
+  task = g_task_new (self, NULL, NULL, NULL);
+  g_task_set_source_tag (task, ipc_flatpak_service_impl_install);
+  g_task_set_task_data (task, state, (GDestroyNotify)install_state_free);
+  g_task_run_in_thread (task, install_worker);
 
   return TRUE;
 }
@@ -930,6 +1144,7 @@ ipc_flatpak_service_impl_finalize (GObject *object)
 {
   IpcFlatpakServiceImpl *self = (IpcFlatpakServiceImpl *)object;
 
+  g_clear_pointer (&self->installs_ordered, g_ptr_array_unref);
   g_clear_pointer (&self->installs, g_hash_table_unref);
   g_clear_pointer (&self->runtimes, g_ptr_array_unref);
 
@@ -948,6 +1163,7 @@ ipc_flatpak_service_impl_class_init (IpcFlatpakServiceImplClass *klass)
 static void
 ipc_flatpak_service_impl_init (IpcFlatpakServiceImpl *self)
 {
+  self->installs_ordered = g_ptr_array_new ();
   self->installs = g_hash_table_new_full (g_file_hash,
                                           (GEqualFunc) g_file_equal,
                                           g_object_unref,
diff --git a/src/plugins/flatpak/daemon/meson.build b/src/plugins/flatpak/daemon/meson.build
index 49e7217e5..14c95654e 100644
--- a/src/plugins/flatpak/daemon/meson.build
+++ b/src/plugins/flatpak/daemon/meson.build
@@ -36,6 +36,12 @@ test_flatpak_sources = [
   ipc_flatpak_transfer_src,
 ]
 
+test_install_sources = [
+  'test-install.c',
+  ipc_flatpak_service_src,
+  ipc_flatpak_transfer_src,
+]
+
 plugins_sources += [
   ipc_flatpak_service_src,
   ipc_flatpak_transfer_src,
@@ -44,3 +50,7 @@ plugins_sources += [
 test_flatpak = executable('test-flatpak', 'test-flatpak.c', test_flatpak_sources,
   dependencies: [ libgiounix_dep ],
 )
+
+test_install = executable('test-install', 'test-install.c', test_install_sources,
+  dependencies: [ libgiounix_dep ],
+)
diff --git a/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Service.xml 
b/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Service.xml
index 6ef120fdf..829852f42 100644
--- a/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Service.xml
+++ b/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Service.xml
@@ -64,12 +64,15 @@
     <!--
       Install:
       @full_ref_name: the full name of the target to install such as "runtime/org.gnome.Sdk/x86_64/master"
+      @transfer: the path of an org.gnome.Builder.Flatpak.Transfer object on the caller side
 
-      Installs a ref from a configured remote.
+      Begins the installation of a ref from a configured remote. The operation will not
+      complete until the installation has completed.
     -->
     <method name="Install">
       <arg name="full_ref_name" direction="in" type="s"/>
-      <arg name="transfer" direction="out" type="o"/>
+      <arg name="transfer" direction="in" type="o"/>
+      <arg name="parent_window" direction="in" type="s"/>
     </method>
     <!--
       ResolveExtension:
diff --git a/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Transfer.xml 
b/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Transfer.xml
index 5a321f3a1..4f5762c87 100644
--- a/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Transfer.xml
+++ b/src/plugins/flatpak/daemon/org.gnome.Builder.Flatpak.Transfer.xml
@@ -21,10 +21,7 @@
     SPDX-License-Identifier: GPL-3.0-or-later
   -->
   <interface name="org.gnome.Builder.Flatpak.Transfer">
-    <property name="Fraction" type="d" access="read"/>
-    <property name="Message" type="s" access="read"/>
-    <signal name="Done"/>
-    <method name="Begin"/>
-    <method name="Cancel"/>
+    <property name="Fraction" type="d" access="readwrite"/>
+    <property name="Message" type="s" access="readwrite"/>
   </interface>
 </node>
diff --git a/src/plugins/flatpak/daemon/test-install.c b/src/plugins/flatpak/daemon/test-install.c
new file mode 100644
index 000000000..e8cb52447
--- /dev/null
+++ b/src/plugins/flatpak/daemon/test-install.c
@@ -0,0 +1,133 @@
+/* test-install.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * 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-unix.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "ipc-flatpak-service.h"
+#include "ipc-flatpak-transfer.h"
+
+static void
+install_cb (GObject      *object,
+            GAsyncResult *result,
+            gpointer      user_data)
+{
+  IpcFlatpakService *service = (IpcFlatpakService *)object;
+  g_autoptr(GMainLoop) main_loop = user_data;
+  g_autoptr(GVariant) runtimes = NULL;
+  g_autoptr(GError) error = NULL;
+  gboolean ret;
+
+  ret = ipc_flatpak_service_call_install_finish (service, result, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+  g_message ("Installed.");
+
+  g_main_loop_quit (main_loop);
+}
+
+static void
+print_info (IpcFlatpakTransfer *transfer,
+            GParamSpec         *pspec,
+            gpointer            user_data)
+{
+  g_print ("%s: %lf\n",
+           ipc_flatpak_transfer_get_message (transfer) ?: "",
+           ipc_flatpak_transfer_get_fraction (transfer));
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  static const char *transfer_path = "/org/gnome/Builder/Flatpak/Transfer/0";
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GIOStream) stream = NULL;
+  g_autoptr(GInputStream) stdout_stream = NULL;
+  g_autoptr(GOutputStream) stdin_stream = NULL;
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(IpcFlatpakService) service = NULL;
+  g_autoptr(IpcFlatpakTransfer) transfer = NULL;
+  GMainLoop *main_loop;
+  gboolean ret;
+
+  if (argc != 2)
+    {
+      g_printerr ("usage: %s REF\n", argv[0]);
+      return EXIT_FAILURE;
+    }
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+  subprocess = g_subprocess_launcher_spawn (launcher, &error,
+#if 0
+                                            "valgrind", "--quiet",
+#endif
+                                            "./gnome-builder-flatpak", NULL);
+
+  if (subprocess == NULL)
+    g_error ("%s", error->message);
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  stdin_stream = g_subprocess_get_stdin_pipe (subprocess);
+  stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
+  stream = g_simple_io_stream_new (stdout_stream, stdin_stream);
+  connection = g_dbus_connection_new_sync (stream,
+                                           NULL,
+                                           G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+                                           NULL,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert_true (G_IS_DBUS_CONNECTION (connection));
+
+  g_dbus_connection_set_exit_on_close (connection, FALSE);
+  g_dbus_connection_start_message_processing (connection);
+
+  g_message ("Creating flatpak service proxy");
+  service = ipc_flatpak_service_proxy_new_sync (connection, 0, NULL, "/org/gnome/Builder/Flatpak", NULL, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (IPC_IS_FLATPAK_SERVICE (service));
+
+  transfer = ipc_flatpak_transfer_skeleton_new ();
+  g_signal_connect (transfer, "notify::message", G_CALLBACK (print_info), NULL);
+  g_signal_connect (transfer, "notify::fraction", G_CALLBACK (print_info), NULL);
+  ret = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (transfer), connection, transfer_path, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Installing %s\n", argv[1]);
+  ipc_flatpak_service_call_install (service,
+                                    argv[1],
+                                    transfer_path,
+                                    "",
+                                    NULL,
+                                    install_cb,
+                                    g_main_loop_ref (main_loop));
+
+  g_main_loop_run (main_loop);
+
+  return EXIT_SUCCESS;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]