From 6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:57:27 +0200 Subject: Adding upstream version 43.5. Signed-off-by: Daniel Baumann --- lib/gs-metered.c | 312 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 lib/gs-metered.c (limited to 'lib/gs-metered.c') diff --git a/lib/gs-metered.c b/lib/gs-metered.c new file mode 100644 index 0000000..e6a82d9 --- /dev/null +++ b/lib/gs-metered.c @@ -0,0 +1,312 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2019 Endless Mobile, Inc. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * SECTION:gs-metered + * @title: Metered Data Utilities + * @include: gnome-software.h + * @stability: Unstable + * @short_description: Utility functions to help with metered data handling + * + * Metered data handling is provided by Mogwai, which implements a download + * scheduler to control when, and in which order, large downloads happen on + * the system. + * + * All large downloads from gs_plugin_download() or gs_plugin_download_app() + * calls should be scheduled using Mogwai, which will notify gnome-software + * when those downloads can start and stop, according to system policy. + * + * The functions in this file make interacting with the scheduling daemon a + * little simpler. Since all #GsPlugin method calls happen in worker threads, + * typically without a #GMainContext, all interaction with the scheduler should + * be blocking. libmogwai-schedule-client was designed to be asynchronous; so + * these helpers make it synchronous. + * + * Since: 3.34 + */ + +#include "config.h" + +#include + +#ifdef HAVE_MOGWAI +#include +#endif + +#include "gs-metered.h" +#include "gs-utils.h" + + +#ifdef HAVE_MOGWAI + +typedef struct +{ + gboolean *out_download_now; /* (unowned) */ + GMainContext *context; /* (unowned) */ +} DownloadNowData; + +static void +download_now_cb (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + DownloadNowData *data = user_data; + *data->out_download_now = mwsc_schedule_entry_get_download_now (MWSC_SCHEDULE_ENTRY (obj)); + g_main_context_wakeup (data->context); +} + +typedef struct +{ + GError **out_error; /* (unowned) */ + GMainContext *context; /* (unowned) */ +} InvalidatedData; + +static void +invalidated_cb (MwscScheduleEntry *entry, + const GError *error, + gpointer user_data) +{ + InvalidatedData *data = user_data; + *data->out_error = g_error_copy (error); + g_main_context_wakeup (data->context); +} + +#endif /* HAVE_MOGWAI */ + +/** + * gs_metered_block_on_download_scheduler: + * @parameters: (nullable): a #GVariant of type `a{sv}` specifying parameters + * for the schedule entry, or %NULL to pass no parameters + * @schedule_entry_handle_out: (out) (not optional): return location for a + * handle to the resulting schedule entry + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Create a schedule entry with the given @parameters, and block until + * permission is given to download. + * + * FIXME: This will currently ignore later revocations of that download + * permission, and does not support creating a schedule entry per app. + * The schedule entry must later be removed from the schedule by passing + * the handle from @schedule_entry_handle_out to + * gs_metered_remove_from_download_scheduler(), otherwise resources will leak. + * This is an opaque handle and should not be inspected. + * + * If a schedule entry cannot be created, or if @cancellable is cancelled, + * an error will be set and %FALSE returned. + * + * The keys understood by @parameters are listed in the documentation for + * mwsc_scheduler_schedule_async(). + * + * This function will likely be called from a #GsPluginLoader worker thread. + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 3.38 + */ +gboolean +gs_metered_block_on_download_scheduler (GVariant *parameters, + gpointer *schedule_entry_handle_out, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_MOGWAI + g_autoptr(MwscScheduler) scheduler = NULL; + g_autoptr(MwscScheduleEntry) schedule_entry = NULL; + g_autofree gchar *parameters_str = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GMainContextPusher) pusher = NULL; + + g_return_val_if_fail (schedule_entry_handle_out != NULL, FALSE); + + /* set this in case of error */ + *schedule_entry_handle_out = NULL; + + parameters_str = (parameters != NULL) ? g_variant_print (parameters, TRUE) : g_strdup ("(none)"); + g_debug ("%s: Waiting with parameters: %s", G_STRFUNC, parameters_str); + + /* Push the context early so that the #MwscScheduler is created to run within it. */ + context = g_main_context_new (); + pusher = g_main_context_pusher_new (context); + + /* Wait until the download can be scheduled. + * FIXME: In future, downloads could be split up by app, so they can all + * be scheduled separately and, for example, higher priority ones could + * be scheduled with a higher priority. This would have to be aware of + * dependencies. */ + scheduler = mwsc_scheduler_new (cancellable, error); + if (scheduler == NULL) + return FALSE; + + /* Create a schedule entry for the group of downloads. + * FIXME: The underlying OSTree code supports resuming downloads + * (at a granularity of individual objects), so it should be + * possible to plumb through here. */ + schedule_entry = mwsc_scheduler_schedule (scheduler, parameters, cancellable, + error); + if (schedule_entry == NULL) + return FALSE; + + /* Wait until the download is allowed to proceed. */ + if (!mwsc_schedule_entry_get_download_now (schedule_entry)) { + gboolean download_now = FALSE; + g_autoptr(GError) invalidated_error = NULL; + gulong notify_id, invalidated_id; + DownloadNowData download_now_data = { &download_now, context }; + InvalidatedData invalidated_data = { &invalidated_error, context }; + + notify_id = g_signal_connect (schedule_entry, "notify::download-now", + (GCallback) download_now_cb, &download_now_data); + invalidated_id = g_signal_connect (schedule_entry, "invalidated", + (GCallback) invalidated_cb, &invalidated_data); + + while (!download_now && invalidated_error == NULL && + !g_cancellable_is_cancelled (cancellable)) + g_main_context_iteration (context, TRUE); + + g_signal_handler_disconnect (schedule_entry, invalidated_id); + g_signal_handler_disconnect (schedule_entry, notify_id); + + if (!download_now && invalidated_error != NULL) { + /* no need to remove the schedule entry as it’s been + * invalidated */ + g_propagate_error (error, g_steal_pointer (&invalidated_error)); + return FALSE; + } else if (!download_now && g_cancellable_set_error_if_cancelled (cancellable, error)) { + /* remove the schedule entry and fail */ + gs_metered_remove_from_download_scheduler (schedule_entry, NULL, NULL); + return FALSE; + } + + g_assert (download_now); + } + + *schedule_entry_handle_out = g_object_ref (schedule_entry); + + g_debug ("%s: Allowed to download", G_STRFUNC); +#else /* if !HAVE_MOGWAI */ + g_debug ("%s: Allowed to download (Mogwai support compiled out)", G_STRFUNC); +#endif /* !HAVE_MOGWAI */ + + return TRUE; +} + +/** + * gs_metered_remove_from_download_scheduler: + * @schedule_entry_handle: (transfer full) (nullable): schedule entry handle as + * returned by gs_metered_block_on_download_scheduler() + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Remove a schedule entry previously created by + * gs_metered_block_on_download_scheduler(). This must be called after + * gs_metered_block_on_download_scheduler() has successfully returned, or + * resources will leak. It should be called once the corresponding download is + * complete. + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 3.38 + */ +gboolean +gs_metered_remove_from_download_scheduler (gpointer schedule_entry_handle, + GCancellable *cancellable, + GError **error) +{ +#ifdef HAVE_MOGWAI + g_autoptr(MwscScheduleEntry) schedule_entry = schedule_entry_handle; +#endif + + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + g_debug ("Removing schedule entry handle %p", schedule_entry_handle); + + if (schedule_entry_handle == NULL) + return TRUE; + +#ifdef HAVE_MOGWAI + return mwsc_schedule_entry_remove (schedule_entry, cancellable, error); +#else + return TRUE; +#endif +} + +/** + * gs_metered_block_app_on_download_scheduler: + * @app: a #GsApp to get the scheduler parameters from + * @schedule_entry_handle_out: (out) (not optional): return location for a + * handle to the resulting schedule entry + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Version of gs_metered_block_on_download_scheduler() which extracts the + * download parameters from the given @app. + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 3.38 + */ +gboolean +gs_metered_block_app_on_download_scheduler (GsApp *app, + gpointer *schedule_entry_handle_out, + GCancellable *cancellable, + GError **error) +{ + g_auto(GVariantDict) parameters_dict = G_VARIANT_DICT_INIT (NULL); + g_autoptr(GVariant) parameters = NULL; + guint64 download_size; + + /* Currently no plugins support resumable downloads. This may change in + * future, in which case this parameter should be refactored. */ + g_variant_dict_insert (¶meters_dict, "resumable", "b", FALSE); + + if (gs_app_get_size_download (app, &download_size) == GS_SIZE_TYPE_VALID) { + g_variant_dict_insert (¶meters_dict, "size-minimum", "t", download_size); + g_variant_dict_insert (¶meters_dict, "size-maximum", "t", download_size); + } + + parameters = g_variant_ref_sink (g_variant_dict_end (¶meters_dict)); + + return gs_metered_block_on_download_scheduler (parameters, schedule_entry_handle_out, cancellable, error); +} + +/** + * gs_metered_block_app_list_on_download_scheduler: + * @app_list: a #GsAppList to get the scheduler parameters from + * @schedule_entry_handle_out: (out) (not optional): return location for a + * handle to the resulting schedule entry + * @cancellable: a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Version of gs_metered_block_on_download_scheduler() which extracts the + * download parameters from the apps in the given @app_list. + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 3.38 + */ +gboolean +gs_metered_block_app_list_on_download_scheduler (GsAppList *app_list, + gpointer *schedule_entry_handle_out, + GCancellable *cancellable, + GError **error) +{ + g_auto(GVariantDict) parameters_dict = G_VARIANT_DICT_INIT (NULL); + g_autoptr(GVariant) parameters = NULL; + + /* Currently no plugins support resumable downloads. This may change in + * future, in which case this parameter should be refactored. */ + g_variant_dict_insert (¶meters_dict, "resumable", "b", FALSE); + + /* FIXME: Currently this creates a single Mogwai schedule entry for the + * entire app list. Eventually, we probably want one schedule entry per + * app being downloaded, so that they can be individually prioritised. + * However, that requires much deeper integration into the download + * code, and Mogwai does not currently support that level of + * prioritisation, so go with this simple implementation for now. */ + parameters = g_variant_ref_sink (g_variant_dict_end (¶meters_dict)); + + return gs_metered_block_on_download_scheduler (parameters, schedule_entry_handle_out, cancellable, error); +} -- cgit v1.2.3