/* * Copyright (C) 2013 Colin Walters * * SPDX-License-Identifier: LGPL-2.0+ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include "ostree-async-progress.h" #include "libglnx.h" /** * SECTION:ostree-async-progress * @title: Progress notification system for asynchronous operations * @short_description: Values representing progress * * For many asynchronous operations, it's desirable for callers to be * able to watch their status as they progress. For example, an user * interface calling an asynchronous download operation will want to * be able to see the total number of bytes downloaded. * * This class provides a mechanism for callees of asynchronous * operations to communicate back with callers. It transparently * handles thread safety, ensuring that the progress change * notification occurs in the thread-default context of the calling * operation. * * The ostree_async_progress_get_status() and ostree_async_progress_set_status() * methods get and set a well-known `status` key of type %G_VARIANT_TYPE_STRING. * This key may be accessed using the other #OstreeAsyncProgress methods, but it * must always have the correct type. */ enum { CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; struct OstreeAsyncProgress { GObject parent_instance; GMutex lock; GMainContext *maincontext; GSource *idle_source; GHashTable *values; /* (element-type uint GVariant) */ gboolean dead; }; G_DEFINE_TYPE (OstreeAsyncProgress, ostree_async_progress, G_TYPE_OBJECT) static void ostree_async_progress_finalize (GObject *object) { OstreeAsyncProgress *self; self = OSTREE_ASYNC_PROGRESS (object); g_mutex_clear (&self->lock); g_clear_pointer (&self->maincontext, g_main_context_unref); g_clear_pointer (&self->idle_source, g_source_unref); g_hash_table_unref (self->values); G_OBJECT_CLASS (ostree_async_progress_parent_class)->finalize (object); } static void ostree_async_progress_class_init (OstreeAsyncProgressClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = ostree_async_progress_finalize; /** * OstreeAsyncProgress::changed: * @self: Self * * Emitted when @self has been changed. **/ signals[CHANGED] = g_signal_new ("changed", OSTREE_TYPE_ASYNC_PROGRESS, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (OstreeAsyncProgressClass, changed), NULL, NULL, NULL, G_TYPE_NONE, 0); } static void ostree_async_progress_init (OstreeAsyncProgress *self) { g_mutex_init (&self->lock); self->maincontext = g_main_context_ref_thread_default (); self->values = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_variant_unref); } /** * ostree_async_progress_get_variant: * @self: an #OstreeAsyncProgress * @key: a key to look up * * Look up a key in the #OstreeAsyncProgress and return the #GVariant associated * with it. The lookup is thread-safe. * * Returns: (transfer full) (nullable): value for the given @key, or %NULL if * it was not set * Since: 2017.6 */ GVariant * ostree_async_progress_get_variant (OstreeAsyncProgress *self, const char *key) { GVariant *rval; g_return_val_if_fail (OSTREE_IS_ASYNC_PROGRESS (self), NULL); g_return_val_if_fail (key != NULL, NULL); g_mutex_lock (&self->lock); rval = g_hash_table_lookup (self->values, GUINT_TO_POINTER (g_quark_from_string (key))); if (rval != NULL) g_variant_ref (rval); g_mutex_unlock (&self->lock); return rval; } guint ostree_async_progress_get_uint (OstreeAsyncProgress *self, const char *key) { g_autoptr(GVariant) rval = ostree_async_progress_get_variant (self, key); return (rval != NULL) ? g_variant_get_uint32 (rval) : 0; } guint64 ostree_async_progress_get_uint64 (OstreeAsyncProgress *self, const char *key) { g_autoptr(GVariant) rval = ostree_async_progress_get_variant (self, key); return (rval != NULL) ? g_variant_get_uint64 (rval) : 0; } /** * ostree_async_progress_get: * @self: an #OstreeAsyncProgress * @...: key name, format string, #GVariant return locations, …, followed by %NULL * * Get the values corresponding to zero or more keys from the * #OstreeAsyncProgress. Each key is specified in @... as the key name, followed * by a #GVariant format string, followed by the necessary arguments for that * format string, just as for g_variant_get(). After those arguments is the * next key name. The varargs list must be %NULL-terminated. * * Each format string must make deep copies of its value, as the values stored * in the #OstreeAsyncProgress may be freed from another thread after this * function returns. * * This operation is thread-safe, and all the keys are queried atomically. * * |[ * guint32 outstanding_fetches; * guint64 bytes_received; * g_autofree gchar *status = NULL; * g_autoptr(GVariant) refs_variant = NULL; * * ostree_async_progress_get (progress, * "outstanding-fetches", "u", &outstanding_fetches, * "bytes-received", "t", &bytes_received, * "status", "s", &status, * "refs", "@a{ss}", &refs_variant, * NULL); * ]| * * Since: 2017.6 */ void ostree_async_progress_get (OstreeAsyncProgress *self, ...) { va_list ap; const char *key, *format_string; g_mutex_lock (&self->lock); va_start (ap, self); for (key = va_arg (ap, const char *), format_string = va_arg (ap, const char *); key != NULL; key = va_arg (ap, const char *), format_string = va_arg (ap, const char *)) { GVariant *variant; g_assert (format_string != NULL); variant = g_hash_table_lookup (self->values, GUINT_TO_POINTER (g_quark_from_string (key))); g_assert (variant != NULL); g_assert (g_variant_check_format_string (variant, format_string, TRUE)); g_variant_get_va (variant, format_string, NULL, &ap); } va_end (ap); g_mutex_unlock (&self->lock); } static gboolean idle_invoke_async_progress (gpointer user_data) { OstreeAsyncProgress *self = user_data; g_mutex_lock (&self->lock); g_clear_pointer (&self->idle_source, g_source_unref); g_mutex_unlock (&self->lock); g_signal_emit (self, signals[CHANGED], 0); return FALSE; } static void ensure_callback_locked (OstreeAsyncProgress *self) { if (self->idle_source) return; self->idle_source = g_idle_source_new (); g_source_set_callback (self->idle_source, idle_invoke_async_progress, self, NULL); g_source_attach (self->idle_source, self->maincontext); } /** * ostree_async_progress_set_status: * @self: an #OstreeAsyncProgress * @status: (nullable): new status string, or %NULL to clear the status * * Set the human-readable status string for the #OstreeAsyncProgress. This * operation is thread-safe. %NULL may be passed to clear the status. * * This is a convenience function to set the well-known `status` key. * * Since: 2017.6 */ void ostree_async_progress_set_status (OstreeAsyncProgress *self, const char *status) { ostree_async_progress_set_variant (self, "status", g_variant_new_string ((status != NULL) ? status : "")); } /** * ostree_async_progress_get_status: * @self: an #OstreeAsyncProgress * * Get the human-readable status string from the #OstreeAsyncProgress. This * operation is thread-safe. The retuned value may be %NULL if no status is * set. * * This is a convenience function to get the well-known `status` key. * * Returns: (transfer full) (nullable): the current status, or %NULL if none is set * Since: 2017.6 */ char * ostree_async_progress_get_status (OstreeAsyncProgress *self) { g_autoptr(GVariant) rval = ostree_async_progress_get_variant (self, "status"); const gchar *status = (rval != NULL) ? g_variant_get_string (rval, NULL) : NULL; if (status != NULL && *status == '\0') status = NULL; return g_strdup (status); } /** * ostree_async_progress_set: * @self: an #OstreeAsyncProgress * @...: key name, format string, #GVariant parameters, …, followed by %NULL * * Set the values for zero or more keys in the #OstreeAsyncProgress. Each key is * specified in @... as the key name, followed by a #GVariant format string, * followed by the necessary arguments for that format string, just as for * g_variant_new(). After those arguments is the next key name. The varargs list * must be %NULL-terminated. * * g_variant_ref_sink() will be called as appropriate on the #GVariant * parameters, so they may be floating. * * This operation is thread-safe, and all the keys are set atomically. * * |[ * guint32 outstanding_fetches = 15; * guint64 bytes_received = 1000; * * ostree_async_progress_set (progress, * "outstanding-fetches", "u", outstanding_fetches, * "bytes-received", "t", bytes_received, * "status", "s", "Updated status", * "refs", "@a{ss}", g_variant_new_parsed ("@a{ss} {}"), * NULL); * ]| * * Since: 2017.6 */ void ostree_async_progress_set (OstreeAsyncProgress *self, ...) { va_list ap; const char *key, *format_string; gboolean changed; g_mutex_lock (&self->lock); if (self->dead) goto out; changed = FALSE; va_start (ap, self); for (key = va_arg (ap, const char *), format_string = va_arg (ap, const char *); key != NULL; key = va_arg (ap, const char *), format_string = va_arg (ap, const char *)) { GVariant *orig_value; g_autoptr(GVariant) new_value = NULL; gpointer qkey = GUINT_TO_POINTER (g_quark_from_string (key)); new_value = g_variant_ref_sink (g_variant_new_va (format_string, NULL, &ap)); if (g_hash_table_lookup_extended (self->values, qkey, NULL, (gpointer *) &orig_value) && g_variant_equal (orig_value, new_value)) continue; g_hash_table_replace (self->values, qkey, g_steal_pointer (&new_value)); changed = TRUE; } va_end (ap); if (changed) ensure_callback_locked (self); out: g_mutex_unlock (&self->lock); } /** * ostree_async_progress_set_variant: * @self: an #OstreeAsyncProgress * @key: a key to set * @value: the value to assign to @key * * Assign a new @value to the given @key, replacing any existing value. The * operation is thread-safe. @value may be a floating reference; * g_variant_ref_sink() will be called on it. * * Any watchers of the #OstreeAsyncProgress will be notified of the change if * @value differs from the existing value for @key. * * Since: 2017.6 */ void ostree_async_progress_set_variant (OstreeAsyncProgress *self, const char *key, GVariant *value) { GVariant *orig_value; g_autoptr(GVariant) new_value = g_variant_ref_sink (value); gpointer qkey = GUINT_TO_POINTER (g_quark_from_string (key)); g_return_if_fail (OSTREE_IS_ASYNC_PROGRESS (self)); g_return_if_fail (key != NULL); g_return_if_fail (value != NULL); g_mutex_lock (&self->lock); if (self->dead) goto out; if (g_hash_table_lookup_extended (self->values, qkey, NULL, (gpointer *) &orig_value)) { if (g_variant_equal (orig_value, new_value)) goto out; } g_hash_table_replace (self->values, qkey, g_steal_pointer (&new_value)); ensure_callback_locked (self); out: g_mutex_unlock (&self->lock); } void ostree_async_progress_set_uint (OstreeAsyncProgress *self, const char *key, guint value) { ostree_async_progress_set_variant (self, key, g_variant_new_uint32 (value)); } void ostree_async_progress_set_uint64 (OstreeAsyncProgress *self, const char *key, guint64 value) { ostree_async_progress_set_variant (self, key, g_variant_new_uint64 (value)); } /** * ostree_async_progress_copy_state: * @self: An #OstreeAsyncProgress to copy from * @dest: An #OstreeAsyncProgress to copy to * * Atomically copies all the state from @self to @dest, without invoking the * callback. * This is used for proxying progress objects across different #GMainContexts. * * Since: 2019.6 */ void ostree_async_progress_copy_state (OstreeAsyncProgress *self, OstreeAsyncProgress *dest) { g_return_if_fail (OSTREE_IS_ASYNC_PROGRESS (self)); g_return_if_fail (OSTREE_IS_ASYNC_PROGRESS (dest)); g_mutex_lock (&self->lock); if (self->dead) goto out; GLNX_HASH_TABLE_FOREACH_KV (self->values, void *, key, GVariant *, value) { if (value) g_variant_ref (value); g_hash_table_replace (dest->values, key, value); } out: g_mutex_unlock (&self->lock); } /** * ostree_async_progress_new: * * Returns: (transfer full): A new progress object */ OstreeAsyncProgress * ostree_async_progress_new (void) { return (OstreeAsyncProgress*)g_object_new (OSTREE_TYPE_ASYNC_PROGRESS, NULL); } OstreeAsyncProgress * ostree_async_progress_new_and_connect (void (*changed) (OstreeAsyncProgress *self, gpointer user_data), gpointer user_data) { OstreeAsyncProgress *ret = ostree_async_progress_new (); g_signal_connect (ret, "changed", G_CALLBACK (changed), user_data); return ret; } /** * ostree_async_progress_finish: * @self: Self * * Process any pending signals, ensuring the main context is cleared * of sources used by this object. Also ensures that no further * events will be queued. */ void ostree_async_progress_finish (OstreeAsyncProgress *self) { gboolean emit_changed = FALSE; g_mutex_lock (&self->lock); if (!self->dead) { self->dead = TRUE; if (self->idle_source) { g_source_destroy (self->idle_source); g_clear_pointer (&self->idle_source, g_source_unref); emit_changed = TRUE; } } g_mutex_unlock (&self->lock); if (emit_changed) g_signal_emit (self, signals[CHANGED], 0); }