[gnome-software/1744-software-remembers-past-install-intent] gs-plugin-loader: Improve handling of pending install queue



commit 5620c43dce1cb1cf78f7a2da8d4ad282dda64b40
Author: Milan Crha <mcrha redhat com>
Date:   Tue May 17 14:39:25 2022 +0200

    gs-plugin-loader: Improve handling of pending install queue
    
    These changes contain several closely related things:
    * use a GsAppList for the install queue - helps to avoid duplicated in the file
    * store more than just the app ID in the file - to install chosen app variant
    * install after start and make it interactive - thus there is not waited
      with the queue for the network change and the user is asked for the credentials
      if needed, without which the app installation just fails
    * store changes in the install queue more frequently - like when an install
      succeeds or fails, save the changes, to not try again.
    
    Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1744

 lib/gs-plugin-loader.c | 177 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 127 insertions(+), 50 deletions(-)
---
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index dffe7d762..df9108c84 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -11,6 +11,7 @@
 
 #include <locale.h>
 #include <glib/gi18n.h>
+#include <glib/gstdio.h>
 #include <appstream.h>
 #include <math.h>
 
@@ -52,7 +53,7 @@ struct _GsPluginLoader
        AsPool                  *as_pool;
 
        GMutex                   pending_apps_mutex;
-       GPtrArray               *pending_apps;
+       GsAppList               *pending_apps;
 
        GThreadPool             *queued_ops_pool;
 
@@ -1344,10 +1345,13 @@ gs_plugin_loader_pending_apps_add (GsPluginLoader *plugin_loader,
        GsAppList *list = gs_plugin_job_get_list (helper->plugin_job);
        g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin_loader->pending_apps_mutex);
 
+       if (plugin_loader->pending_apps == NULL)
+               plugin_loader->pending_apps = gs_app_list_new ();
+
        g_assert (gs_app_list_length (list) > 0);
        for (guint i = 0; i < gs_app_list_length (list); i++) {
                GsApp *app = gs_app_list_index (list, i);
-               g_ptr_array_add (plugin_loader->pending_apps, g_object_ref (app));
+               gs_app_list_add (plugin_loader->pending_apps, app);
                /* make sure the progress is properly initialized */
                gs_app_set_progress (app, GS_APP_PROGRESS_UNKNOWN);
        }
@@ -1364,7 +1368,8 @@ gs_plugin_loader_pending_apps_remove (GsPluginLoader *plugin_loader,
        g_assert (gs_app_list_length (list) > 0);
        for (guint i = 0; i < gs_app_list_length (list); i++) {
                GsApp *app = gs_app_list_index (list, i);
-               g_ptr_array_remove (plugin_loader->pending_apps, app);
+               if (plugin_loader->pending_apps != NULL)
+                       gs_app_list_remove (plugin_loader->pending_apps, app);
 
                /* check the app is not still in an action helper */
                switch (gs_app_get_state (app)) {
@@ -1422,10 +1427,17 @@ load_install_queue (GsPluginLoader  *plugin_loader,
        names = g_strsplit (contents, "\n", 0);
        for (guint i = 0; names[i] != NULL; i++) {
                g_autoptr(GsApp) app = NULL;
-               if (strlen (names[i]) == 0)
+               g_auto(GStrv) split = NULL;
+               if (names[i][0] == '\0')
+                       continue;
+               split = g_strsplit (names[i], "\t", -1);
+               if (split[0] == NULL || split[1] == NULL || split[2] == NULL) {
                        continue;
-               app = gs_app_new (names[i]);
+               }
+               app = gs_app_new (split[0]);
+               gs_app_set_from_unique_id (app, split[1], as_component_kind_from_string (split[2]));
                gs_app_set_state (app, GS_APP_STATE_QUEUED_FOR_INSTALL);
+               gs_app_add_quirk (app, GS_APP_QUIRK_IS_WILDCARD);
                gs_app_list_add (list, app);
        }
 
@@ -1434,7 +1446,9 @@ load_install_queue (GsPluginLoader  *plugin_loader,
        for (guint i = 0; i < gs_app_list_length (list); i++) {
                GsApp *app = gs_app_list_index (list, i);
                g_debug ("adding pending app %s", gs_app_get_unique_id (app));
-               g_ptr_array_add (plugin_loader->pending_apps, g_object_ref (app));
+               if (plugin_loader->pending_apps == NULL)
+                       plugin_loader->pending_apps = gs_app_list_new ();
+               gs_app_list_add (plugin_loader->pending_apps, app);
        }
        g_mutex_unlock (&plugin_loader->pending_apps_mutex);
 
@@ -1444,21 +1458,21 @@ load_install_queue (GsPluginLoader  *plugin_loader,
 static void
 save_install_queue (GsPluginLoader *plugin_loader)
 {
-       GPtrArray *pending_apps;
        gboolean ret;
-       gint i;
        g_autoptr(GError) error = NULL;
        g_autoptr(GString) s = NULL;
        g_autofree gchar *file = NULL;
 
        s = g_string_new ("");
-       pending_apps = plugin_loader->pending_apps;
        g_mutex_lock (&plugin_loader->pending_apps_mutex);
-       for (i = (gint) pending_apps->len - 1; i >= 0; i--) {
-               GsApp *app;
-               app = g_ptr_array_index (pending_apps, i);
+       for (guint i = 0; plugin_loader->pending_apps != NULL && i < gs_app_list_length 
(plugin_loader->pending_apps); i++) {
+               GsApp *app = gs_app_list_index (plugin_loader->pending_apps, i);
                if (gs_app_get_state (app) == GS_APP_STATE_QUEUED_FOR_INSTALL) {
                        g_string_append (s, gs_app_get_id (app));
+                       g_string_append_c (s, '\t');
+                       g_string_append (s, gs_app_get_unique_id (app));
+                       g_string_append_c (s, '\t');
+                       g_string_append (s, as_component_kind_to_string (gs_app_get_kind (app)));
                        g_string_append_c (s, '\n');
                }
        }
@@ -1469,6 +1483,15 @@ save_install_queue (GsPluginLoader *plugin_loader)
                                 "gnome-software",
                                 "install-queue",
                                 NULL);
+       if (s->len == 0) {
+               if (g_file_test (file, G_FILE_TEST_EXISTS) &&
+                   g_unlink (file) == -1) {
+                       gint errn = errno;
+                       g_warning ("Failed to unlink '%s': %s", file, g_strerror (errn));
+               }
+               return;
+       }
+
        if (!gs_mkdir_parent (file, &error)) {
                g_warning ("failed to create dir for %s: %s",
                           file, error->message);
@@ -1489,7 +1512,9 @@ add_app_to_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
 
        /* queue the app itself */
        g_mutex_lock (&plugin_loader->pending_apps_mutex);
-       g_ptr_array_add (plugin_loader->pending_apps, g_object_ref (app));
+       if (plugin_loader->pending_apps == NULL)
+               plugin_loader->pending_apps = gs_app_list_new ();
+       gs_app_list_add (plugin_loader->pending_apps, app);
        g_mutex_unlock (&plugin_loader->pending_apps_mutex);
 
        gs_app_set_state (app, GS_APP_STATE_QUEUED_FOR_INSTALL);
@@ -1515,7 +1540,7 @@ remove_app_from_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
        guint id;
 
        g_mutex_lock (&plugin_loader->pending_apps_mutex);
-       ret = g_ptr_array_remove (plugin_loader->pending_apps, app);
+       ret = gs_app_list_remove (plugin_loader->pending_apps, app) || plugin_loader->pending_apps != NULL;
        g_mutex_unlock (&plugin_loader->pending_apps_mutex);
 
        if (ret) {
@@ -1560,14 +1585,11 @@ GsAppList *
 gs_plugin_loader_get_pending (GsPluginLoader *plugin_loader)
 {
        GsAppList *array;
-       guint i;
 
        array = gs_app_list_new ();
        g_mutex_lock (&plugin_loader->pending_apps_mutex);
-       for (i = 0; i < plugin_loader->pending_apps->len; i++) {
-               GsApp *app = g_ptr_array_index (plugin_loader->pending_apps, i);
-               gs_app_list_add (array, app);
-       }
+       if (plugin_loader->pending_apps != NULL)
+               gs_app_list_add_list (array, plugin_loader->pending_apps);
        g_mutex_unlock (&plugin_loader->pending_apps_mutex);
 
        return array;
@@ -2588,7 +2610,9 @@ finish_setup_op (GTask *task)
        if (gs_app_list_length (install_queue) > 0) {
                g_autoptr(GsPluginJob) refine_job = NULL;
 
-               refine_job = gs_plugin_job_refine_new (install_queue, GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID | 
GS_PLUGIN_REFINE_FLAGS_DISABLE_FILTERING);
+               refine_job = gs_plugin_job_refine_new (install_queue, GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID |
+                                                                     GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
+                                                                     
GS_PLUGIN_REFINE_FLAGS_DISABLE_FILTERING);
                gs_plugin_loader_job_process_async (plugin_loader, refine_job,
                                                    cancellable,
                                                    finish_setup_install_queue_cb,
@@ -2598,6 +2622,8 @@ finish_setup_op (GTask *task)
        }
 }
 
+static void gs_plugin_loader_flush_pending_install_queue (GsPluginLoader *plugin_loader);
+
 static void
 finish_setup_install_queue_cb (GObject      *source_object,
                                GAsyncResult *result,
@@ -2609,10 +2635,47 @@ finish_setup_install_queue_cb (GObject      *source_object,
        g_autoptr(GError) local_error = NULL;
 
        new_list = gs_plugin_loader_job_process_finish (plugin_loader, result, &local_error);
-       if (new_list == NULL)
+       if (new_list == NULL) {
                g_task_return_error (task, g_steal_pointer (&local_error));
-       else
+       } else {
+               g_autoptr(GsAppList) old_pending_apps = NULL;
+               gboolean has_pending_apps = FALSE;
+               gboolean changed;
+               g_mutex_lock (&plugin_loader->pending_apps_mutex);
+               changed = plugin_loader->pending_apps != NULL;
+               old_pending_apps = g_steal_pointer (&plugin_loader->pending_apps);
+               if (old_pending_apps != NULL && gs_app_list_length (new_list) > 0) {
+                       g_autoptr(GHashTable) expected_unique_ids = g_hash_table_new (g_str_hash, 
g_str_equal);
+                       for (guint i = 0; i < gs_app_list_length (old_pending_apps); i++) {
+                               GsApp *app = gs_app_list_index (old_pending_apps, i);
+                               if (gs_app_get_unique_id (app) != NULL)
+                                       g_hash_table_add (expected_unique_ids, (gpointer) 
gs_app_get_unique_id (app));
+                       }
+                       for (guint i = 0; i < gs_app_list_length (new_list); i++) {
+                               GsApp *app = gs_app_list_index (new_list, i);
+                               if (gs_app_get_state (app) == GS_APP_STATE_AVAILABLE &&
+                                   gs_app_get_unique_id (app) != NULL &&
+                                   g_hash_table_contains (expected_unique_ids, gs_app_get_unique_id (app))) {
+                                       if (plugin_loader->pending_apps == NULL)
+                                               plugin_loader->pending_apps = gs_app_list_new ();
+                                       gs_app_set_state (app, GS_APP_STATE_QUEUED_FOR_INSTALL);
+                                       gs_app_list_add (plugin_loader->pending_apps, app);
+                               }
+                       }
+                       has_pending_apps = plugin_loader->pending_apps != NULL;
+                       changed = TRUE;
+               }
+               g_mutex_unlock (&plugin_loader->pending_apps_mutex);
                g_task_return_boolean (task, TRUE);
+
+               if (changed)
+                       save_install_queue (plugin_loader);
+               if (has_pending_apps &&
+                   gs_plugin_loader_get_network_available (plugin_loader) &&
+                   !gs_plugin_loader_get_network_metered (plugin_loader)) {
+                       gs_plugin_loader_flush_pending_install_queue (plugin_loader);
+               }
+       }
 }
 
 /**
@@ -2762,7 +2825,7 @@ gs_plugin_loader_dispose (GObject *object)
        }
        g_clear_object (&plugin_loader->network_monitor);
        g_clear_object (&plugin_loader->settings);
-       g_clear_pointer (&plugin_loader->pending_apps, g_ptr_array_unref);
+       g_clear_object (&plugin_loader->pending_apps);
        g_clear_object (&plugin_loader->category_manager);
        g_clear_object (&plugin_loader->odrs_provider);
        g_clear_object (&plugin_loader->setup_complete_cancellable);
@@ -2971,7 +3034,7 @@ gs_plugin_loader_init (GsPluginLoader *plugin_loader)
        plugin_loader->setup_complete_cancellable = g_cancellable_new ();
        plugin_loader->scale = 1;
        plugin_loader->plugins = g_ptr_array_new_with_free_func (g_object_unref);
-       plugin_loader->pending_apps = g_ptr_array_new_with_free_func (g_object_unref);
+       plugin_loader->pending_apps = NULL;
        plugin_loader->queued_ops_pool = g_thread_pool_new (gs_plugin_loader_process_in_thread_pool_cb,
                                                   NULL,
                                                   get_max_parallel_ops (),
@@ -3103,8 +3166,8 @@ gs_plugin_loader_app_installed_cb (GObject *source,
        ret = gs_plugin_loader_job_action_finish (plugin_loader,
                                                   res,
                                                   &error);
+       remove_app_from_install_queue (plugin_loader, app);
        if (!ret) {
-               remove_app_from_install_queue (plugin_loader, app);
                g_warning ("failed to install %s: %s",
                           gs_app_get_unique_id (app), error->message);
        }
@@ -3130,6 +3193,43 @@ gs_plugin_loader_get_network_metered (GsPluginLoader *plugin_loader)
        return g_network_monitor_get_network_metered (plugin_loader->network_monitor);
 }
 
+static void
+gs_plugin_loader_flush_pending_install_queue (GsPluginLoader *plugin_loader)
+{
+       g_autoptr(GsAppList) obsolete = NULL;
+       g_autoptr(GsAppList) queue = NULL;
+       queue = gs_app_list_new ();
+       obsolete = gs_app_list_new ();
+       g_mutex_lock (&plugin_loader->pending_apps_mutex);
+       for (guint i = 0; plugin_loader->pending_apps != NULL && i < gs_app_list_length 
(plugin_loader->pending_apps); i++) {
+               GsApp *app = gs_app_list_index (plugin_loader->pending_apps, i);
+               if (gs_app_get_state (app) == GS_APP_STATE_QUEUED_FOR_INSTALL) {
+                       gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+                       gs_app_list_add (queue, app);
+               } else {
+                       gs_app_list_add (obsolete, app);
+               }
+       }
+       g_mutex_unlock (&plugin_loader->pending_apps_mutex);
+       for (guint i = 0; i < gs_app_list_length (obsolete); i++) {
+               GsApp *app = gs_app_list_index (obsolete, i);
+               remove_app_from_install_queue (plugin_loader, app);
+       }
+       for (guint i = 0; i < gs_app_list_length (queue); i++) {
+               GsApp *app = gs_app_list_index (queue, i);
+               GsPluginAction action = gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY ? 
GS_PLUGIN_ACTION_INSTALL_REPO : GS_PLUGIN_ACTION_INSTALL;
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (action,
+                                                "app", app,
+                                                "interactive", TRUE, /* needed for credentials prompt, 
otherwise it just fails */
+                                                NULL);
+               gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
+                                                   NULL,
+                                                   gs_plugin_loader_app_installed_cb,
+                                                   g_object_ref (app));
+       }
+}
+
 static void
 gs_plugin_loader_network_changed_cb (GNetworkMonitor *monitor,
                                     gboolean available,
@@ -3144,31 +3244,8 @@ gs_plugin_loader_network_changed_cb (GNetworkMonitor *monitor,
        g_object_notify_by_pspec (G_OBJECT (plugin_loader), obj_props[PROP_NETWORK_AVAILABLE]);
        g_object_notify_by_pspec (G_OBJECT (plugin_loader), obj_props[PROP_NETWORK_METERED]);
 
-       if (available && !metered) {
-               g_autoptr(GsAppList) queue = NULL;
-               g_mutex_lock (&plugin_loader->pending_apps_mutex);
-               queue = gs_app_list_new ();
-               for (guint i = 0; i < plugin_loader->pending_apps->len; i++) {
-                       GsApp *app = g_ptr_array_index (plugin_loader->pending_apps, i);
-                       if (gs_app_get_state (app) == GS_APP_STATE_QUEUED_FOR_INSTALL) {
-                               gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
-                               gs_app_list_add (queue, app);
-                       }
-               }
-               g_mutex_unlock (&plugin_loader->pending_apps_mutex);
-               for (guint i = 0; i < gs_app_list_length (queue); i++) {
-                       GsApp *app = gs_app_list_index (queue, i);
-                       GsPluginAction action = gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY ? 
GS_PLUGIN_ACTION_INSTALL_REPO : GS_PLUGIN_ACTION_INSTALL;
-                       g_autoptr(GsPluginJob) plugin_job = NULL;
-                       plugin_job = gs_plugin_job_newv (action,
-                                                        "app", app,
-                                                        NULL);
-                       gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
-                                                           NULL,
-                                                           gs_plugin_loader_app_installed_cb,
-                                                           g_object_ref (app));
-               }
-       }
+       if (available && !metered)
+               gs_plugin_loader_flush_pending_install_queue (plugin_loader);
 }
 
 static void


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