509 lines
15 KiB
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);
|
|
}
|