/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2013 Colin Walters * * 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 "libgsystem.h" /** * SECTION:libostree-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. */ #if GLIB_SIZEOF_VOID_P == 8 #define _OSTREE_HAVE_LP64 1 #else #define _OSTREE_HAVE_LP64 0 #endif enum { CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; struct OstreeAsyncProgress { GObject parent_instance; GMutex lock; GMainContext *maincontext; GSource *idle_source; GHashTable *uint_values; GHashTable *uint64_values; gboolean dead; char *status; }; 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->uint_values); g_hash_table_unref (self->uint64_values); g_free (self->status); 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->uint_values = g_hash_table_new (NULL, NULL); #if _OSTREE_HAVE_LP64 self->uint64_values = g_hash_table_new (NULL, NULL); #else self->uint64_values = g_hash_table_new_full (NULL, NULL, NULL, g_free); #endif } guint ostree_async_progress_get_uint (OstreeAsyncProgress *self, const char *key) { guint rval; g_mutex_lock (&self->lock); rval = GPOINTER_TO_UINT (g_hash_table_lookup (self->uint_values, GUINT_TO_POINTER (g_quark_from_string (key)))); g_mutex_unlock (&self->lock); return rval; } guint64 ostree_async_progress_get_uint64 (OstreeAsyncProgress *self, const char *key) { #if _OSTREE_HAVE_LP64 guint64 rval; g_mutex_lock (&self->lock); rval = (guint64) g_hash_table_lookup (self->uint64_values, GUINT_TO_POINTER (g_quark_from_string (key))); g_mutex_unlock (&self->lock); return rval; #else guint64 *rval; g_mutex_lock (&self->lock); rval = g_hash_table_lookup (self->uint64_values, (gpointer)g_quark_from_string (key)); g_mutex_unlock (&self->lock); if (rval) return *rval; return 0; #endif } static gboolean idle_invoke_async_progress (gpointer user_data) { OstreeAsyncProgress *self = user_data; g_mutex_lock (&self->lock); self->idle_source = NULL; 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); } void ostree_async_progress_set_status (OstreeAsyncProgress *self, const char *status) { g_mutex_lock (&self->lock); if (!self->dead) { g_free (self->status); self->status = g_strdup (status); ensure_callback_locked (self); } g_mutex_unlock (&self->lock); } char * ostree_async_progress_get_status (OstreeAsyncProgress *self) { char *ret; g_mutex_lock (&self->lock); ret = g_strdup (self->status); g_mutex_unlock (&self->lock); return ret; } static void update_key (OstreeAsyncProgress *self, GHashTable *hash, const char *key, gpointer value) { gpointer orig_value; gpointer qkey = GUINT_TO_POINTER (g_quark_from_string (key)); g_mutex_lock (&self->lock); if (self->dead) goto out; if (g_hash_table_lookup_extended (hash, qkey, NULL, &orig_value)) { if (orig_value == value) goto out; } g_hash_table_replace (hash, qkey, value); ensure_callback_locked (self); out: g_mutex_unlock (&self->lock); } void ostree_async_progress_set_uint (OstreeAsyncProgress *self, const char *key, guint value) { update_key (self, self->uint_values, key, GUINT_TO_POINTER (value)); } void ostree_async_progress_set_uint64 (OstreeAsyncProgress *self, const char *key, guint64 value) { gpointer valuep; #if _OSTREE_HAVE_LP64 valuep = (gpointer)value; #else { guint64 *boxed = g_malloc (sizeof (guint64)); *boxed = value; valuep = boxed; } #endif update_key (self, self->uint64_values, key, valuep); } /** * 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); self->idle_source = NULL; emit_changed = TRUE; } } g_mutex_unlock (&self->lock); if (emit_changed) g_signal_emit (self, signals[CHANGED], 0); }