ostree/src/libostree/ostree-async-progress.c

509 lines
15 KiB
C

/*
* Copyright (C) 2013 Colin Walters <walters@verbum.org>
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#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.
*
* |[<!-- language="C" -->
* 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.
*
* |[<!-- language="C" -->
* 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);
}