[gnome-software: 6/8] gs-odrs-provider: Make refreshing asynchronous




commit 0fe8fffdb4ee87f03210fc067b08fa934864f7d4
Author: Philip Withnall <pwithnall endlessos org>
Date:   Tue Feb 22 16:58:16 2022 +0000

    gs-odrs-provider: Make refreshing asynchronous
    
    Change the refresh API to be asynchronous, and to no longer rely on a
    `GsPlugin` being passed in to provide progress updates.
    
    This will allow `GsOdrsProvider` to be used in contexts where a
    `GsPlugin` isn’t available, and it decouples progress updates from the
    locking semantics of setting properties on a `GsApp`.
    
    Signed-off-by: Philip Withnall <pwithnall endlessos org>
    
    Helps: #1472

 lib/gs-odrs-provider.c | 135 ++++++++++++++++++++++++++++++++++---------------
 lib/gs-odrs-provider.h |  13 +++--
 lib/gs-plugin-loader.c |  95 ++++++++++++++++++++++++++++++----
 3 files changed, 189 insertions(+), 54 deletions(-)
---
diff --git a/lib/gs-odrs-provider.c b/lib/gs-odrs-provider.c
index 138e7ed3e..0286ddea4 100644
--- a/lib/gs-odrs-provider.c
+++ b/lib/gs-odrs-provider.c
@@ -1268,77 +1268,130 @@ gs_odrs_provider_new (const gchar *review_server,
                             NULL);
 }
 
+static void download_ratings_cb (GObject      *source_object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data);
+
 /**
- * gs_odrs_provider_refresh:
+ * gs_odrs_provider_refresh_ratings_async:
  * @self: a #GsOdrsProvider
- * @plugin: the #GsPlugin running this operation
  * @cache_age_secs: cache age, in seconds, as passed to gs_plugin_refresh()
+ * @progress_callback: (nullable): callback to call with progress information
+ * @progress_user_data: (nullable) (closure progress_callback): data to pass
+ *   to @progress_callback
  * @cancellable: (nullable): a #GCancellable, or %NULL
- * @error: return location for a #GError
+ * @callback: function to call when the asynchronous operation is complete
+ * @user_data: data to pass to @callback
  *
- * Refresh the cached ODRS ratings and re-load them.
+ * Refresh the cached ODRS ratings and re-load them asynchronously.
  *
- * Returns: %TRUE on success, %FALSE otherwise
- * Since: 41
+ * Since: 42
  */
-gboolean
-gs_odrs_provider_refresh (GsOdrsProvider  *self,
-                          GsPlugin        *plugin,
-                          guint64          cache_age_secs,
-                          GCancellable    *cancellable,
-                          GError         **error)
+void
+gs_odrs_provider_refresh_ratings_async (GsOdrsProvider             *self,
+                                        guint64                     cache_age_secs,
+                                        GsDownloadProgressCallback  progress_callback,
+                                        gpointer                    progress_user_data,
+                                        GCancellable               *cancellable,
+                                        GAsyncReadyCallback         callback,
+                                        gpointer                    user_data)
 {
        g_autofree gchar *cache_filename = NULL;
+       g_autoptr(GFile) cache_file = NULL;
        g_autofree gchar *uri = NULL;
        g_autoptr(GError) error_local = NULL;
-       g_autoptr(GsApp) app_dl = NULL;
+       g_autoptr(GTask) task = NULL;
+
+       task = g_task_new (self, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_odrs_provider_refresh_ratings_async);
 
        /* check cache age */
        cache_filename = gs_utils_get_cache_filename ("odrs",
                                                      "ratings.json",
                                                      GS_UTILS_CACHE_FLAG_WRITEABLE |
                                                      GS_UTILS_CACHE_FLAG_CREATE_DIRECTORY,
-                                                     error);
-       if (cache_filename == NULL)
-               return FALSE;
+                                                     &error_local);
+       if (cache_filename == NULL) {
+               g_task_return_error (task, g_steal_pointer (&error_local));
+               return;
+       }
+
+       cache_file = g_file_new_for_path (cache_filename);
+       g_task_set_task_data (task, g_object_ref (cache_file), g_object_unref);
+
        if (cache_age_secs > 0) {
                guint64 tmp;
-               g_autoptr(GFile) file = NULL;
-               file = g_file_new_for_path (cache_filename);
-               tmp = gs_utils_get_file_age (file);
+
+               tmp = gs_utils_get_file_age (cache_file);
                if (tmp < cache_age_secs) {
                        g_debug ("%s is only %" G_GUINT64_FORMAT " seconds old, so ignoring refresh",
                                 cache_filename, tmp);
-                       return gs_odrs_provider_load_ratings (self, cache_filename, error);
+                       if (!gs_odrs_provider_load_ratings (self, cache_filename, &error_local))
+                               g_task_return_error (task, g_steal_pointer (&error_local));
+                       else
+                               g_task_return_boolean (task, TRUE);
+                       return;
                }
        }
 
-       app_dl = gs_app_new ("odrs");
-
        /* download the complete file */
        uri = g_strdup_printf ("%s/ratings", self->review_server);
        g_debug ("Updating ODRS cache from %s to %s", uri, cache_filename);
-       gs_app_set_summary_missing (app_dl,
-                                   /* TRANSLATORS: status text when downloading */
-                                   _("Downloading application ratings…"));
-       if (!gs_plugin_download_file (plugin, app_dl, uri, cache_filename, cancellable, &error_local)) {
-               g_autoptr(GsPluginEvent) event = NULL;
-
-               event = gs_plugin_event_new ("error", error_local,
-                                            "action", GS_PLUGIN_ACTION_DOWNLOAD,
-                                            "origin", self->cached_origin,
-                                            NULL);
-
-               if (gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE))
-                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INTERACTIVE);
-               else
-                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
-               gs_plugin_report_event (plugin, event);
 
-               /* don't fail updates if the ratings server is unavailable */
-               return TRUE;
+       gs_download_file_async (self->session, uri, cache_file, G_PRIORITY_LOW,
+                               progress_callback, progress_user_data,
+                               cancellable, download_ratings_cb, g_steal_pointer (&task));
+}
+
+static void
+download_ratings_cb (GObject      *source_object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+       SoupSession *soup_session = SOUP_SESSION (source_object);
+       g_autoptr(GTask) task = g_steal_pointer (&user_data);
+       GsOdrsProvider *self = g_task_get_source_object (task);
+       GFile *cache_file = g_task_get_task_data (task);
+       g_autoptr(GError) local_error = NULL;
+
+       if (!gs_download_file_finish (soup_session, result, &local_error)) {
+               g_task_return_new_error (task, GS_ODRS_PROVIDER_ERROR,
+                                        GS_ODRS_PROVIDER_ERROR_DOWNLOADING,
+                                        "%s", local_error->message);
+               return;
        }
-       return gs_odrs_provider_load_ratings (self, cache_filename, error);
+
+       if (!gs_odrs_provider_load_ratings (self, g_file_peek_path (cache_file), &local_error))
+               g_task_return_new_error (task, GS_ODRS_PROVIDER_ERROR,
+                                        GS_ODRS_PROVIDER_ERROR_PARSING_DATA,
+                                        "%s", local_error->message);
+       else
+               g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * gs_odrs_provider_refresh_ratings_finish:
+ * @self: a #GsOdrsProvider
+ * @result: result of the asynchronous operation
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous refresh operation started with
+ * gs_odrs_provider_refresh_ratings_async().
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 42
+ */
+gboolean
+gs_odrs_provider_refresh_ratings_finish (GsOdrsProvider  *self,
+                                         GAsyncResult    *result,
+                                         GError         **error)
+{
+       g_return_val_if_fail (GS_IS_ODRS_PROVIDER (self), FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+       g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == 
gs_odrs_provider_refresh_ratings_async, FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 /**
diff --git a/lib/gs-odrs-provider.h b/lib/gs-odrs-provider.h
index 90cc51b3d..2f045d2ed 100644
--- a/lib/gs-odrs-provider.h
+++ b/lib/gs-odrs-provider.h
@@ -14,7 +14,9 @@
 #include <glib-object.h>
 #include <libsoup/soup.h>
 
-#include <gs-plugin.h>
+#include "gs-app-list.h"
+#include "gs-download-utils.h"
+#include "gs-plugin-types.h"
 
 G_BEGIN_DECLS
 
@@ -50,10 +52,15 @@ GsOdrsProvider      *gs_odrs_provider_new                   (const gchar             
*review_server,
                                                         guint                    n_results_max,
                                                         SoupSession             *session);
 
-gboolean        gs_odrs_provider_refresh               (GsOdrsProvider          *self,
-                                                        GsPlugin                *plugin,
+void            gs_odrs_provider_refresh_ratings_async (GsOdrsProvider          *self,
                                                         guint64                  cache_age_secs,
+                                                        GsDownloadProgressCallback progress_callback,
+                                                        gpointer                 progress_user_data,
                                                         GCancellable            *cancellable,
+                                                        GAsyncReadyCallback      callback,
+                                                        gpointer                 user_data);
+gboolean        gs_odrs_provider_refresh_ratings_finish(GsOdrsProvider          *self,
+                                                        GAsyncResult            *result,
                                                         GError                 **error);
 
 gboolean        gs_odrs_provider_refine                (GsOdrsProvider          *self,
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 5b17e2677..4081a6910 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -83,6 +83,13 @@ struct _GsPluginLoader
 static void gs_plugin_loader_monitor_network (GsPluginLoader *plugin_loader);
 static void add_app_to_install_queue (GsPluginLoader *plugin_loader, GsApp *app);
 static void gs_plugin_loader_process_in_thread_pool_cb (gpointer data, gpointer user_data);
+static void gs_plugin_loader_status_changed_cb (GsPlugin       *plugin,
+                                                GsApp          *app,
+                                                GsPluginStatus  status,
+                                                GsPluginLoader *plugin_loader);
+static void async_result_cb (GObject      *source_object,
+                             GAsyncResult *result,
+                             gpointer      user_data);
 
 G_DEFINE_TYPE (GsPluginLoader, gs_plugin_loader, G_TYPE_OBJECT)
 
@@ -863,6 +870,32 @@ gs_plugin_loader_job_sorted_truncation (GsPluginLoaderHelper *helper)
        gs_app_list_truncate (list, max_results);
 }
 
+typedef struct {
+       GsPluginLoader *plugin_loader;  /* (not nullable) (unowned) */
+       GsApp *app;  /* (not nullable) (unowned) */
+} OdrsRefreshProgressData;
+
+static void
+odrs_refresh_progress_cb (gsize    bytes_downloaded,
+                          gsize    total_download_size,
+                          gpointer user_data)
+{
+       OdrsRefreshProgressData *data = user_data;
+       guint percentage;
+
+       if (total_download_size > 0)
+               percentage = (guint) ((100 * bytes_downloaded) / total_download_size);
+       else
+               percentage = 0;
+
+       g_debug ("%s progress: %u%%", gs_app_get_id (data->app), percentage);
+       gs_app_set_progress (data->app, percentage);
+
+       gs_plugin_loader_status_changed_cb (NULL, data->app,
+                                           GS_PLUGIN_STATUS_DOWNLOADING,
+                                           data->plugin_loader);
+}
+
 static gboolean
 gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
                              GCancellable *cancellable,
@@ -906,17 +939,59 @@ gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
 
        if (action == GS_PLUGIN_ACTION_REFRESH &&
            plugin_loader->odrs_provider != NULL) {
-               /* FIXME: Using plugin_loader->plugins->pdata[0] is a hack; the
-                * GsOdrsProvider needs access to a GsPlugin to access global
-                * state for gs_plugin_download_file(), but it doesn’t really
-                * matter which plugin it’s accessed through. In lieu of
-                * refactoring gs_plugin_download_file(), use the first plugin
-                * in the list for now. */
-               if (!gs_odrs_provider_refresh (plugin_loader->odrs_provider,
-                                              plugin_loader->plugins->pdata[0],
-                                              gs_plugin_job_get_age (helper->plugin_job),
-                                              cancellable, error))
+               g_autoptr(GsApp) app_dl = NULL;
+               OdrsRefreshProgressData progress_data;
+               g_autoptr(GAsyncResult) odrs_result = NULL;
+               g_autoptr(GError) local_error = NULL;
+
+               app_dl = gs_app_new ("odrs");
+               gs_app_set_summary_missing (app_dl,
+                                           /* TRANSLATORS: status text when downloading */
+                                           _("Downloading application ratings…"));
+
+               progress_data.plugin_loader = plugin_loader;
+               progress_data.app = app_dl;
+
+               gs_odrs_provider_refresh_ratings_async (plugin_loader->odrs_provider,
+                                                       gs_plugin_job_get_age (helper->plugin_job),
+                                                       odrs_refresh_progress_cb,
+                                                       &progress_data,
+                                                       cancellable,
+                                                       async_result_cb,
+                                                       &odrs_result);
+
+               /* FIXME: Make this sync until the enclosing function is
+                * refactored to be async. */
+               while (odrs_result == NULL)
+                       g_main_context_iteration (g_main_context_get_thread_default (), TRUE);
+
+               if (!gs_odrs_provider_refresh_ratings_finish (plugin_loader->odrs_provider,
+                                                             odrs_result,
+                                                             &local_error)) {
+                       /* Don’t fail updates if the ratings server is unavailable */
+                       if (g_error_matches (local_error, GS_ODRS_PROVIDER_ERROR,
+                                            GS_ODRS_PROVIDER_ERROR_DOWNLOADING) ||
+                           g_error_matches (local_error, GS_ODRS_PROVIDER_ERROR,
+                                            GS_ODRS_PROVIDER_ERROR_NO_NETWORK)) {
+                               g_autoptr(GsPluginEvent) event = NULL;
+
+                               event = gs_plugin_event_new ("error", local_error,
+                                                            "action", GS_PLUGIN_ACTION_DOWNLOAD,
+                                                            NULL);
+
+                               if (gs_plugin_job_get_interactive (helper->plugin_job))
+                                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INTERACTIVE);
+                               else
+                                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+                               gs_plugin_loader_add_event (plugin_loader, event);
+
+                               return TRUE;
+                       }
+
+                       g_propagate_error (error, g_steal_pointer (&local_error));
+
                        return FALSE;
+               }
        }
 
 #ifdef HAVE_SYSPROF


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