/* * SPDX-FileCopyrightText: 2015 William Yu * * SPDX-License-Identifier: LGPL-2.1-only OR MPL-2.0 */ #ifdef HAVE_CONFIG_H #include #endif #include "i-cal-object.h" typedef struct _GlobalData { GType object_type; gpointer native; } GlobalData; static guint global_data_hash(gconstpointer ptr) { const GlobalData *gd = ptr; if (!gd) { return 0; } return g_direct_hash(GINT_TO_POINTER(gd->object_type)) ^ g_direct_hash(gd->native); } static gboolean global_data_equal(gconstpointer ptr1, gconstpointer ptr2) { const GlobalData *gd1 = ptr1, *gd2 = ptr2; if (!gd1 || !gd2) { return gd1 == gd2; } return gd1->object_type == gd2->object_type && gd1->native == gd2->native; } G_LOCK_DEFINE_STATIC(global_objects); static GHashTable *global_objects; /* GlobalData * ~> ICalObject * */ static void global_data_object_freed_cb(gpointer user_data, G_GNUC_UNUSED GObject *freed_object) { GlobalData *gd = user_data; G_LOCK(global_objects); if (global_objects) { if (g_hash_table_steal(global_objects, gd)) { g_free(gd); } if (!g_hash_table_size(global_objects)) { g_hash_table_destroy(global_objects); global_objects = NULL; } } G_UNLOCK(global_objects); } /** * i_cal_object_free_global_objects: * * Frees all global objects. Any references to them are invalidated * by this call, unless they had been g_object_ref()-ed manually. * * Since: 3.0.5 **/ void i_cal_object_free_global_objects(void) { GHashTable *objects; G_LOCK(global_objects); objects = global_objects; global_objects = NULL; G_UNLOCK(global_objects); if (objects) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, objects); while (g_hash_table_iter_next(&iter, &key, &value)) { g_object_weak_unref(value, global_data_object_freed_cb, key); } g_hash_table_destroy (objects); } } typedef struct { GMutex props_lock; /* to guard all the below members */ gpointer native; GDestroyNotify native_destroy_func; gboolean is_global_memory; gboolean always_destroy; GObject *owner; GSList *dependers; /* referenced GObject-s */ } ICalObjectPrivate; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(ICalObject, i_cal_object, G_TYPE_OBJECT) #define LOCK_PROPS(x) g_mutex_lock (&((x)->props_lock)) #define UNLOCK_PROPS(x) g_mutex_unlock (&((x)->props_lock)) enum { PROP_0, PROP_NATIVE, PROP_NATIVE_DESTROY_FUNC, PROP_IS_GLOBAL_MEMORY, PROP_ALWAYS_DESTROY, PROP_OWNER }; static void i_cal_object_set_property(GObject *object, guint property_id, const GValue * value, GParamSpec * pspec) { ICalObject *iobject = I_CAL_OBJECT(object); ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(object)); switch (property_id) { case PROP_NATIVE: /* no need for LOCK_PROPS() here, these can be set only during construction time */ g_return_if_fail(priv->native == NULL); priv->native = g_value_get_pointer(value); return; case PROP_NATIVE_DESTROY_FUNC: i_cal_object_set_native_destroy_func(iobject, g_value_get_pointer(value)); return; case PROP_IS_GLOBAL_MEMORY: /* no need for LOCK_PROPS() here, these can be set only during construction time */ priv->is_global_memory = g_value_get_boolean(value); return; case PROP_ALWAYS_DESTROY: i_cal_object_set_always_destroy(iobject, g_value_get_boolean(value)); return; case PROP_OWNER: i_cal_object_set_owner(iobject, g_value_get_object(value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } static void i_cal_object_get_property(GObject *object, guint property_id, GValue * value, GParamSpec * pspec) { ICalObject *iobject = I_CAL_OBJECT(object); g_return_if_fail(I_CAL_IS_OBJECT(object)); switch (property_id) { case PROP_NATIVE: g_value_set_pointer(value, i_cal_object_get_native(iobject)); return; case PROP_NATIVE_DESTROY_FUNC: g_value_set_pointer(value, i_cal_object_get_native_destroy_func(iobject)); return; case PROP_IS_GLOBAL_MEMORY: g_value_set_boolean(value, i_cal_object_get_is_global_memory(iobject)); return; case PROP_ALWAYS_DESTROY: g_value_set_boolean(value, i_cal_object_get_always_destroy(iobject)); return; case PROP_OWNER: g_value_take_object(value, i_cal_object_ref_owner(iobject)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } static void i_cal_object_finalize(GObject *object) { ICalObject *iobject = I_CAL_OBJECT(object); ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); if ((priv->always_destroy || !priv->owner) && !priv->is_global_memory && priv->native && priv->native_destroy_func) { g_clear_pointer(&priv->native, priv->native_destroy_func); } g_clear_object(&priv->owner); g_slist_free_full(priv->dependers, g_object_unref); g_mutex_clear(&priv->props_lock); /* Chain up to parent's method. */ G_OBJECT_CLASS(i_cal_object_parent_class)->finalize(object); } static void i_cal_object_class_init(ICalObjectClass * class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS(class); object_class->set_property = i_cal_object_set_property; object_class->get_property = i_cal_object_get_property; object_class->finalize = i_cal_object_finalize; /** * ICalObject:native: * * The native libical structure for this ICalObject. **/ g_object_class_install_property( object_class, PROP_NATIVE, g_param_spec_pointer( "native", "Native", "Native libical structure", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * ICalObject:native-destroy-func: * * GDestroyNotify function to use to destroy the native libical pointer. **/ g_object_class_install_property( object_class, PROP_NATIVE_DESTROY_FUNC, g_param_spec_pointer( "native-destroy-func", "Native-Destroy-Func", "GDestroyNotify function to use to destroy the native libical structure", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ICalObject:is-global-memory: * * Whether the native libical structure is from a global shared memory. * If TRUE, then it is not freed on #ICalObject's finalize. **/ g_object_class_install_property( object_class, PROP_IS_GLOBAL_MEMORY, g_param_spec_boolean( "is-global-memory", "Is-Global-Memory", "Whether the native libical structure is from a global shared memory", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * ICalObject:always-destroy: * * Whether free the native libical structure on #ICalObject's finalize even * if the object has set an owner. * * Since: 3.0.11 **/ g_object_class_install_property( object_class, PROP_ALWAYS_DESTROY, g_param_spec_boolean( "always-destroy", "Always-Destroy", "Whether the native libical structure is freed even when the owner is set", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * ICalObject:owner: * * Owner of the native libical structure. If set, then it is * responsible for a free of the native libical structure. **/ g_object_class_install_property( object_class, PROP_OWNER, g_param_spec_object( "owner", "Owner", "The native libical structure owner", G_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void i_cal_object_init(ICalObject *iobject) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_mutex_init(&priv->props_lock); } /** * i_cal_object_construct: (skip) * @object_type: a GType of an #ICalObject descendat to construct * @native: a native libical structure * @native_destroy_func: a function to be called on @native when it should be freed * @is_global_memory: whether @native is a global shared memory structure * @owner: (nullable): an owner of @native * * Creates an #ICalObject descendant of type @type and initialize private members * of it. The descendants should call this function in their _new() function, or use * corresponding properties during the construction time. This should not be mixed, * either use properties or this function. * * The @is_global_memory defines whether the returned object is a singleton, * in which case the object is owned by the libical-glib and should not be freed, * or, when %FALSE, the returned object is a newly created object and the caller * is responsible to free it with g_object_unref(). * * Returns: (transfer full): an #ICalObject descendant of type @type * * Since: 1.0 **/ gpointer i_cal_object_construct(GType object_type, gpointer native, GDestroyNotify native_destroy_func, gboolean is_global_memory, GObject *owner) { ICalObject *iobject; ICalObjectPrivate *priv; g_return_val_if_fail(object_type != G_TYPE_INVALID, NULL); g_return_val_if_fail(native != NULL, NULL); if (owner) g_return_val_if_fail(G_IS_OBJECT(owner), NULL); if (is_global_memory) { G_LOCK(global_objects); if (global_objects) { GlobalData tmp_gd; tmp_gd.object_type = object_type; tmp_gd.native = native; iobject = g_hash_table_lookup(global_objects, &tmp_gd); if (iobject) { G_UNLOCK(global_objects); return iobject; } } } iobject = g_object_new(object_type, NULL); priv = i_cal_object_get_instance_private(iobject); /* LOCK_PROPS (iobject); */ g_warn_if_fail(priv->native == NULL); priv->native = native; priv->native_destroy_func = native_destroy_func; priv->is_global_memory = is_global_memory; i_cal_object_set_owner(iobject, owner); /* UNLOCK_PROPS (iobject); */ if (is_global_memory) { GlobalData *gd; gd = g_new0(GlobalData, 1); gd->object_type = object_type; gd->native = native; g_object_weak_ref(G_OBJECT(iobject), global_data_object_freed_cb, gd); if (!global_objects) { global_objects = g_hash_table_new_full(global_data_hash, global_data_equal, g_free, g_object_unref); } g_hash_table_insert(global_objects, gd, iobject); G_UNLOCK(global_objects); } return iobject; } /** * i_cal_object_get_native: (skip) * @iobject: an #ICalObject * * Obtain native libical structure pointer associated with this @iobject. * * Returns: (transfer none): Native libical structure pointer associated with this @iobject. * * Since: 1.0 **/ gpointer i_cal_object_get_native(ICalObject *iobject) { gpointer native; ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL); LOCK_PROPS(priv); native = priv->native; UNLOCK_PROPS(priv); return native; } /** * i_cal_object_steal_native: * @iobject: an #ICalObject * * Obtain native libical structure pointer associated with this @iobject and sets the one * at @iobject to NULL, thus it's invalid since now on. * * Returns: (transfer full): Native libical structure pointer associated with this @iobject. * * Since: 1.0 **/ gpointer i_cal_object_steal_native(ICalObject *iobject) { gpointer native; ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL); LOCK_PROPS(priv); native = g_steal_pointer(&priv->native); UNLOCK_PROPS(priv); return native; } /** * i_cal_object_get_native_destroy_func: (skip) * @iobject: an #ICalObject * * Obtain a pointer to a function responsible to free the libical native structure. * * Returns: (transfer none): Pointer to a function responsible to free * the libical native structure. * * Since: 1.0 **/ GDestroyNotify i_cal_object_get_native_destroy_func(ICalObject *iobject) { GDestroyNotify func; ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL); LOCK_PROPS(priv); func = priv->native_destroy_func; UNLOCK_PROPS(priv); return func; } /** * i_cal_object_set_native_destroy_func: * @iobject: an #ICalObject * @native_destroy_func: Function to be used to destroy the native libical structure * * Sets a function to be used to destroy the native libical structure. * * Since: 1.0 **/ void i_cal_object_set_native_destroy_func(ICalObject *iobject, GDestroyNotify native_destroy_func) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(iobject)); LOCK_PROPS(priv); if (native_destroy_func == priv->native_destroy_func) { UNLOCK_PROPS(priv); return; } priv->native_destroy_func = native_destroy_func; UNLOCK_PROPS(priv); g_object_notify(G_OBJECT(iobject), "native-destroy-func"); } /** * i_cal_object_get_is_global_memory: * @iobject: an #ICalObject * * Obtains whether the native libical structure is a global shared memory, * thus should not be destroyed. This can be set only during construction time. * * Returns: Whether the native libical structure is a global shared memory. * * Since: 1.0 **/ gboolean i_cal_object_get_is_global_memory(ICalObject *iobject) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); gboolean is_global_memory; g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), FALSE); LOCK_PROPS(priv); is_global_memory = priv->is_global_memory; UNLOCK_PROPS(priv); return is_global_memory; } /** * i_cal_object_set_owner: * @iobject: an #ICalObject * @owner: Owner of the native libical structure * * Sets an owner of the native libical structure, that is an object responsible * for a destroy of the native libical structure. * * Since: 1.0 **/ void i_cal_object_set_owner(ICalObject *iobject, GObject *owner) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(iobject)); if (owner) g_return_if_fail(G_IS_OBJECT(owner)); LOCK_PROPS(priv); if (owner == priv->owner) { UNLOCK_PROPS(priv); return; } g_set_object(&priv->owner, owner); UNLOCK_PROPS(priv); g_object_notify(G_OBJECT(iobject), "owner"); } /** * i_cal_object_ref_owner: * @iobject: an #ICalObject * * Obtain current owner of the native libical structure. The returned pointer, * if not NULL, is referenced for thread safety. Unref it with g_object_unref * when done with it. * * Returns: (transfer full) (nullable): Current owner of the libical * native structure. Returns %NULL when there is no owner. * * Since: 1.0 **/ GObject *i_cal_object_ref_owner(ICalObject *iobject) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); GObject *owner; g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL); LOCK_PROPS(priv); owner = priv->owner; if (owner) g_object_ref(owner); UNLOCK_PROPS(priv); return owner; } /** * i_cal_object_remove_owner: * @iobject: an #ICalObject * * Unref and remove the owner. * * Since: 1.0 **/ void i_cal_object_remove_owner(ICalObject *iobject) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(iobject)); LOCK_PROPS(priv); g_clear_object(&priv->owner); UNLOCK_PROPS(priv); } /** * i_cal_object_add_depender: * @iobject: an #ICalObject * @depender: a #GObject depender * * Adds a @depender into the list of objects which should not be destroyed before * this @iobject. It's usually used for cases where the @iobject uses native libical * structure from the @depender. The @depender is referenced. It's illegal to try * to add one @depender multiple times. * * Since: 1.0 **/ void i_cal_object_add_depender(ICalObject *iobject, GObject *depender) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(iobject)); g_return_if_fail(G_IS_OBJECT(depender)); LOCK_PROPS(priv); if (g_slist_find(priv->dependers, depender)) { g_warn_if_reached(); UNLOCK_PROPS(priv); return; } priv->dependers = g_slist_prepend(priv->dependers, g_object_ref(depender)); UNLOCK_PROPS(priv); } /** * i_cal_object_remove_depender: * @iobject: an #ICalObject * @depender: a #GObject depender * * Removes a @depender from the list of objects which should not be destroyed before * this @iobject, previously added with i_cal_object_add_depender(). It's illegal to try * to remove the @depender which is not in the internal list. * * Since: 1.0 **/ void i_cal_object_remove_depender(ICalObject *iobject, GObject *depender) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); g_return_if_fail(I_CAL_IS_OBJECT(iobject)); g_return_if_fail(G_IS_OBJECT(depender)); LOCK_PROPS(priv); if (!g_slist_find(priv->dependers, depender)) { g_warn_if_reached(); UNLOCK_PROPS(priv); return; } priv->dependers = g_slist_remove(priv->dependers, depender); g_object_unref(depender); UNLOCK_PROPS(priv); } /** * i_cal_object_set_always_destroy: * @iobject: an #ICalObject * @value: value to set * * Sets the @ICalObject::always-destroy property value. When %TRUE, the native * libical structure is always freed, even when an owner of the @iobject is set. * * Since: 3.0.11 **/ void i_cal_object_set_always_destroy(ICalObject *iobject, gboolean value) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); gboolean changed; g_return_if_fail(I_CAL_IS_OBJECT(iobject)); LOCK_PROPS(priv); changed = (value ? 1 : 0) != (priv->always_destroy ? 1 : 0); if (changed) priv->always_destroy = value; UNLOCK_PROPS(priv); if (changed) g_object_notify(G_OBJECT(iobject), "always-destroy"); } /** * i_cal_object_get_always_destroy: * @iobject: an #ICalObject * * Obtain the @ICalObject::always-destroy property value. * * Returns: Whether the native libical structure is freed even when an owner is set. * * Since: 3.0.11 **/ gboolean i_cal_object_get_always_destroy(ICalObject *iobject) { ICalObjectPrivate *priv = i_cal_object_get_instance_private(iobject); gboolean value; g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), FALSE); LOCK_PROPS(priv); value = priv->always_destroy; UNLOCK_PROPS(priv); return value; }