1/*
2 * Copyright (C) 2015 William Yu <williamyu@gnome.org>
3 *
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of version 2.1. of the GNU Lesser General Public License
6 * as published by the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17#ifdef HAVE_CONFIG_H
18#include <config.h>
19#endif
20
21#include "i-cal-object.h"
22
23#define LOCK_PROPS(x) g_mutex_lock (&((x)->priv->props_lock))
24#define UNLOCK_PROPS(x) g_mutex_unlock (&((x)->priv->props_lock))
25
26typedef struct _GlobalData {
27    GType object_type;
28    gpointer native;
29} GlobalData;
30
31static guint global_data_hash(gconstpointer ptr)
32{
33    const GlobalData *gd = ptr;
34
35    if (!gd) {
36        return 0;
37    }
38    return g_direct_hash(GINT_TO_POINTER(gd->object_type)) ^ g_direct_hash(gd->native);
39}
40
41static gboolean global_data_equal(gconstpointer ptr1, gconstpointer ptr2)
42{
43    const GlobalData *gd1 = ptr1, *gd2 = ptr2;
44
45    if (!gd1 || !gd2) {
46        return gd1 == gd2;
47    }
48    return gd1->object_type == gd2->object_type && gd1->native == gd2->native;
49}
50
51G_LOCK_DEFINE_STATIC(global_objects);
52static GHashTable *global_objects; /* GlobalData * ~> ICalObject * */
53
54static void global_data_object_freed_cb(gpointer user_data, G_GNUC_UNUSED GObject *freed_object)
55{
56    GlobalData *gd = user_data;
57
58    G_LOCK(global_objects);
59
60    if (global_objects) {
61        if (g_hash_table_steal(global_objects, gd)) {
62            g_free(gd);
63        }
64
65        if (!g_hash_table_size(global_objects)) {
66             g_hash_table_destroy(global_objects);
67            global_objects = NULL;
68        }
69    }
70
71    G_UNLOCK(global_objects);
72}
73
74/**
75 * i_cal_object_free_global_objects:
76 *
77 * Frees all global objects. Any references to them are invalidated
78 * by this call, unless they had been g_object_ref()-ed manually.
79 *
80 * Since: 3.0.5
81 **/
82void i_cal_object_free_global_objects(void)
83{
84    GHashTable *objects;
85
86    G_LOCK(global_objects);
87
88    objects = global_objects;
89    global_objects = NULL;
90
91    G_UNLOCK(global_objects);
92
93    if (objects) {
94        GHashTableIter iter;
95        gpointer key, value;
96
97        g_hash_table_iter_init(&iter, objects);
98        while (g_hash_table_iter_next(&iter, &key, &value)) {
99            g_object_weak_unref(value, global_data_object_freed_cb, key);
100        }
101
102        g_hash_table_destroy (objects);
103    }
104}
105
106struct _ICalObjectPrivate
107{
108    GMutex props_lock;  /* to guard all the below members */
109
110    gpointer native;
111    GDestroyNotify native_destroy_func;
112    gboolean is_global_memory;
113    GObject *owner;
114    GSList *dependers;  /* referenced GObject-s */
115};
116
117G_DEFINE_ABSTRACT_TYPE(ICalObject, i_cal_object, G_TYPE_OBJECT)
118
119enum
120{
121    PROP_0,
122    PROP_NATIVE,
123    PROP_NATIVE_DESTROY_FUNC,
124    PROP_IS_GLOBAL_MEMORY,
125    PROP_OWNER
126};
127
128static void i_cal_object_set_property(GObject *object, guint property_id,
129                                      const GValue * value, GParamSpec * pspec)
130{
131    ICalObject *iobject;
132
133    g_return_if_fail(I_CAL_IS_OBJECT(object));
134
135    iobject = I_CAL_OBJECT(object);
136
137    switch (property_id) {
138    case PROP_NATIVE:
139        /* no need for LOCK_PROPS() here, these can be set only during construction time */
140        g_return_if_fail(iobject->priv->native == NULL);
141        iobject->priv->native = g_value_get_pointer(value);
142        return;
143
144    case PROP_NATIVE_DESTROY_FUNC:
145        i_cal_object_set_native_destroy_func(iobject, g_value_get_pointer(value));
146        return;
147
148    case PROP_IS_GLOBAL_MEMORY:
149        /* no need for LOCK_PROPS() here, these can be set only during construction time */
150        iobject->priv->is_global_memory = g_value_get_boolean(value);
151        return;
152
153    case PROP_OWNER:
154        i_cal_object_set_owner(iobject, g_value_get_object(value));
155        return;
156    }
157
158    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
159}
160
161static void i_cal_object_get_property(GObject *object, guint property_id,
162                                      GValue * value, GParamSpec * pspec)
163{
164    ICalObject *iobject;
165
166    g_return_if_fail(I_CAL_IS_OBJECT(object));
167
168    iobject = I_CAL_OBJECT(object);
169
170    switch (property_id) {
171    case PROP_NATIVE:
172        g_value_set_pointer(value, i_cal_object_get_native(iobject));
173        return;
174
175    case PROP_NATIVE_DESTROY_FUNC:
176        g_value_set_pointer(value, i_cal_object_get_native_destroy_func(iobject));
177        return;
178
179    case PROP_IS_GLOBAL_MEMORY:
180        g_value_set_boolean(value, i_cal_object_get_is_global_memory(iobject));
181        return;
182
183    case PROP_OWNER:
184        g_value_take_object(value, i_cal_object_ref_owner(iobject));
185        return;
186    }
187
188    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
189}
190
191static void i_cal_object_finalize(GObject *object)
192{
193    ICalObject *iobject = I_CAL_OBJECT(object);
194
195    if (!iobject->priv->owner && !iobject->priv->is_global_memory &&
196        iobject->priv->native && iobject->priv->native_destroy_func) {
197        iobject->priv->native_destroy_func(iobject->priv->native);
198    }
199
200    if (iobject->priv->owner) {
201        g_object_unref(iobject->priv->owner);
202    }
203
204    g_slist_free_full(iobject->priv->dependers, g_object_unref);
205
206    g_mutex_clear(&iobject->priv->props_lock);
207
208    /* Chain up to parent's method. */
209    G_OBJECT_CLASS(i_cal_object_parent_class)->finalize(object);
210}
211
212static void i_cal_object_class_init(ICalObjectClass * class)
213{
214    GObjectClass *object_class;
215
216    g_type_class_add_private(class, sizeof(ICalObjectPrivate));
217
218    object_class = G_OBJECT_CLASS(class);
219    object_class->set_property = i_cal_object_set_property;
220    object_class->get_property = i_cal_object_get_property;
221    object_class->finalize = i_cal_object_finalize;
222
223    /**
224     * ICalObject:native:
225     *
226     * The native libical structure for this ICalObject.
227     **/
228    g_object_class_install_property(
229        object_class,
230        PROP_NATIVE,
231        g_param_spec_pointer(
232            "native",
233            "Native",
234            "Native libical structure",
235            G_PARAM_READWRITE |
236            G_PARAM_CONSTRUCT_ONLY |
237            G_PARAM_STATIC_STRINGS));
238
239    /**
240     * ICalObject:native-destroy-func:
241     *
242     * GDestroyNotify function to use to destroy the native libical pointer.
243     **/
244    g_object_class_install_property(
245        object_class,
246        PROP_NATIVE_DESTROY_FUNC,
247        g_param_spec_pointer(
248            "native-destroy-func",
249            "Native-Destroy-Func",
250            "GDestroyNotify function to use to destroy the native libical structure",
251            G_PARAM_READWRITE |
252            G_PARAM_STATIC_STRINGS));
253
254    /**
255     * ICalObject:is-global-memory:
256     *
257     * Whether the native libical structure is from a global shared memory.
258     * If TRUE, then it is not freed on #ICalObject's finalize.
259     **/
260    g_object_class_install_property(
261        object_class,
262        PROP_IS_GLOBAL_MEMORY,
263        g_param_spec_boolean(
264            "is-global-memory",
265            "Is-Global-Memory",
266            "Whether the native libical structure is from a global shared memory",
267            FALSE,
268            G_PARAM_READWRITE |
269            G_PARAM_CONSTRUCT_ONLY |
270            G_PARAM_STATIC_STRINGS));
271
272    /**
273     * ICalObject:owner:
274     *
275     * Owner of the native libical structure. If set, then it is
276     * responsible for a free of the native libical structure.
277     **/
278    g_object_class_install_property(
279        object_class,
280        PROP_OWNER,
281        g_param_spec_object(
282            "owner",
283            "Owner",
284            "The native libical structure owner",
285            G_TYPE_OBJECT,
286            G_PARAM_READWRITE |
287            G_PARAM_STATIC_STRINGS));
288}
289
290static void i_cal_object_init(ICalObject *iobject)
291{
292    iobject->priv = G_TYPE_INSTANCE_GET_PRIVATE(iobject, I_CAL_TYPE_OBJECT, ICalObjectPrivate);
293
294    g_mutex_init(&iobject->priv->props_lock);
295}
296
297/**
298 * i_cal_object_construct: (skip)
299 * @object_type: a GType of an #ICalObject descendat to construct
300 * @native: a native libical structure
301 * @native_destroy_func: a function to be called on @native when it should be freed
302 * @is_global_memory: whether @native is a global shared memory structure
303 * @owner: (allow-none): an owner of @native
304 *
305 * Creates an #ICalObject descendant of type @type and initialize private members
306 * of it. The descendants should call this function in their _new() function, or use
307 * corresponding properties during the construction time. This should not be mixed,
308 * either use properties or this function.
309 *
310 * The @is_global_memory defines whether the returned object is a singleton,
311 * in which case the object is owned by the libical-glib and should not be freed,
312 * or, when %FALSE, the returned object is a newly created object and the caller
313 * is responsible to free it with g_object_unref().
314 *
315 * Returns: (transfer full): an #ICalObject descendant of type @type
316 *
317 * Since: 1.0
318 **/
319gpointer
320i_cal_object_construct(GType object_type,
321                       gpointer native,
322                       GDestroyNotify native_destroy_func,
323                       gboolean is_global_memory,
324                       GObject *owner)
325{
326    ICalObject *iobject;
327
328    g_return_val_if_fail(object_type != G_TYPE_INVALID, NULL);
329    g_return_val_if_fail(native != NULL, NULL);
330    if (owner)
331        g_return_val_if_fail(G_IS_OBJECT(owner), NULL);
332
333    if (is_global_memory) {
334        G_LOCK(global_objects);
335
336        if (global_objects) {
337            GlobalData tmp_gd;
338
339            tmp_gd.object_type = object_type;
340            tmp_gd.native = native;
341
342            iobject = g_hash_table_lookup(global_objects, &tmp_gd);
343
344            if (iobject) {
345                G_UNLOCK(global_objects);
346                return iobject;
347            }
348        }
349    }
350
351    iobject = g_object_new(object_type, NULL);
352
353    /* LOCK_PROPS (iobject); */
354
355    g_warn_if_fail(iobject->priv->native == NULL);
356
357    iobject->priv->native = native;
358    iobject->priv->native_destroy_func = native_destroy_func;
359    iobject->priv->is_global_memory = is_global_memory;
360    i_cal_object_set_owner(iobject, owner);
361
362    /* UNLOCK_PROPS (iobject); */
363
364    if (is_global_memory) {
365        GlobalData *gd;
366
367        gd = g_new0(GlobalData, 1);
368        gd->object_type = object_type;
369        gd->native = native;
370
371        g_object_weak_ref(G_OBJECT(iobject), global_data_object_freed_cb, gd);
372
373        if (!global_objects) {
374            global_objects =
375                g_hash_table_new_full(global_data_hash, global_data_equal,
376                                      g_free, g_object_unref);
377        }
378
379        g_hash_table_insert(global_objects, gd, iobject);
380
381        G_UNLOCK(global_objects);
382    }
383
384    return iobject;
385}
386
387/**
388 * i_cal_object_get_native: (skip)
389 * @iobject: an #ICalObject
390 *
391 * Obtain native libical structure pointer associated with this @iobject.
392 *
393 * Returns: (transfer none): Native libical structure pointer associated with this @iobject.
394 *
395 * Since: 1.0
396 **/
397gpointer i_cal_object_get_native(ICalObject *iobject)
398{
399    gpointer native;
400
401    g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL);
402
403    LOCK_PROPS(iobject);
404
405    native = iobject->priv->native;
406
407    UNLOCK_PROPS(iobject);
408
409    return native;
410}
411
412/**
413 * i_cal_object_steal_native:
414 * @iobject: an #ICalObject
415 *
416 * Obtain native libical structure pointer associated with this @iobject and sets the one
417 * at @iobject to NULL, thus it's invalid since now on.
418 *
419 * Returns: (transfer full): Native libical structure pointer associated with this @iobject.
420 *
421 * Since: 1.0
422 **/
423gpointer i_cal_object_steal_native(ICalObject *iobject)
424{
425    gpointer native;
426
427    g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL);
428
429    LOCK_PROPS(iobject);
430
431    native = iobject->priv->native;
432    iobject->priv->native = NULL;
433
434    UNLOCK_PROPS(iobject);
435
436    return native;
437}
438
439/**
440 * i_cal_object_get_native_destroy_func: (skip)
441 * @iobject: an #ICalObject
442 *
443 * Obtain a pointer to a function responsible to free the libical native structure.
444 *
445 * Returns: (transfer none): Pointer to a function responsible to free
446 * the libical native structure.
447 *
448 * Since: 1.0
449 **/
450GDestroyNotify i_cal_object_get_native_destroy_func(ICalObject *iobject)
451{
452    GDestroyNotify func;
453
454    g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL);
455
456    LOCK_PROPS(iobject);
457
458    func = iobject->priv->native_destroy_func;
459
460    UNLOCK_PROPS(iobject);
461
462    return func;
463}
464
465/**
466 * i_cal_object_set_native_destroy_func:
467 * @iobject: an #ICalObject
468 * @native_destroy_func: Function to be used to destroy the native libical structure
469 *
470 * Sets a function to be used to destroy the native libical structure.
471 *
472 * Since: 1.0
473 **/
474void i_cal_object_set_native_destroy_func(ICalObject *iobject, GDestroyNotify native_destroy_func)
475{
476    g_return_if_fail(I_CAL_IS_OBJECT(iobject));
477
478    LOCK_PROPS(iobject);
479
480    if (native_destroy_func == iobject->priv->native_destroy_func) {
481        UNLOCK_PROPS(iobject);
482        return;
483    }
484
485    iobject->priv->native_destroy_func = native_destroy_func;
486
487    UNLOCK_PROPS(iobject);
488
489    g_object_notify(G_OBJECT(iobject), "native-destroy-func");
490}
491
492/**
493 * i_cal_object_get_is_global_memory:
494 * @iobject: an #ICalObject
495 *
496 * Obtains whether the native libical structure is a global shared memory,
497 * thus should not be destroyed. This can be set only during contruction time.
498 *
499 * Returns: Whether the native libical structure is a global shared memory.
500 *
501 * Since: 1.0
502 **/
503gboolean i_cal_object_get_is_global_memory(ICalObject *iobject)
504{
505    gboolean is_global_memory;
506
507    g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), FALSE);
508
509    LOCK_PROPS(iobject);
510
511    is_global_memory = iobject->priv->is_global_memory;
512
513    UNLOCK_PROPS(iobject);
514
515    return is_global_memory;
516}
517
518/**
519 * i_cal_object_set_owner:
520 * @iobject: an #ICalObject
521 * @owner: Owner of the native libical structure
522 *
523 * Sets an owner of the native libical structure, that is an object responsible
524 * for a destroy of the native libical structure.
525 *
526 * Since: 1.0
527 **/
528void i_cal_object_set_owner(ICalObject *iobject, GObject *owner)
529{
530    g_return_if_fail(I_CAL_IS_OBJECT(iobject));
531    if (owner)
532        g_return_if_fail(G_IS_OBJECT(owner));
533
534    LOCK_PROPS(iobject);
535
536    if (owner == iobject->priv->owner) {
537        UNLOCK_PROPS(iobject);
538        return;
539    }
540
541    if (owner) {
542        g_object_ref(owner);
543    }
544    g_clear_object(&iobject->priv->owner);
545    iobject->priv->owner = owner;
546
547    UNLOCK_PROPS(iobject);
548
549    g_object_notify(G_OBJECT(iobject), "owner");
550}
551
552/**
553 * i_cal_object_ref_owner:
554 * @iobject: an #ICalObject
555 *
556 * Obtain current owner of the native libical structure. The returned pointer,
557 * if not NULL, is referenced for thread safety. Unref it with g_object_unref
558 * when done with it.
559 *
560 * Returns: (transfer full) (allow-none): Current owner of the libical
561 *    native structure. returns NULL, when there is no owner.
562 *
563 * Since: 1.0
564 **/
565GObject *i_cal_object_ref_owner(ICalObject *iobject)
566{
567    GObject *owner;
568
569    g_return_val_if_fail(I_CAL_IS_OBJECT(iobject), NULL);
570
571    LOCK_PROPS(iobject);
572
573    owner = iobject->priv->owner;
574    if (owner)
575        g_object_ref(owner);
576
577    UNLOCK_PROPS(iobject);
578
579    return owner;
580}
581
582/**
583 * i_cal_object_remove_owner:
584 * @iobject: an #ICalObject
585 *
586 * Unref and remove the owner.
587 *
588 * Since: 1.0
589 **/
590void i_cal_object_remove_owner(ICalObject *iobject)
591{
592    GObject *owner;
593
594    g_return_if_fail(I_CAL_IS_OBJECT(iobject));
595
596    LOCK_PROPS(iobject);
597
598    owner = iobject->priv->owner;
599    if (owner) {
600        g_object_unref(owner);
601        iobject->priv->owner = NULL;
602    }
603
604    UNLOCK_PROPS(iobject);
605}
606
607/**
608 * i_cal_object_add_depender:
609 * @iobject: an #ICalObject
610 * @depender: a #GObject depender
611 *
612 * Adds a @depender into the list of objects which should not be destroyed before
613 * this @iobject. It's usually used for cases where the @iobject uses native libical
614 * structure from the @depender. The @depender is referenced. It's illegal to try
615 * to add one @depender multiple times.
616 *
617 * Since: 1.0
618 **/
619void i_cal_object_add_depender(ICalObject *iobject, GObject *depender)
620{
621    g_return_if_fail(I_CAL_IS_OBJECT(iobject));
622    g_return_if_fail(G_IS_OBJECT(depender));
623
624    LOCK_PROPS(iobject);
625
626    if (g_slist_find(iobject->priv->dependers, depender)) {
627        g_warn_if_reached();
628        UNLOCK_PROPS(iobject);
629        return;
630    }
631
632    iobject->priv->dependers = g_slist_prepend(iobject->priv->dependers, g_object_ref(depender));
633
634    UNLOCK_PROPS(iobject);
635}
636
637/**
638 * i_cal_object_remove_depender:
639 * @iobject: an #ICalObject
640 * @depender: a #GObject depender
641 *
642 * Removes a @depender from the list of objects which should not be destroyed before
643 * this @iobject, previously added with i_cal_object_add_depender(). It's illegal to try
644 * to remove the @depender which is not in the internal list.
645 *
646 * Since: 1.0
647 **/
648void i_cal_object_remove_depender(ICalObject *iobject, GObject *depender)
649{
650    g_return_if_fail(I_CAL_IS_OBJECT(iobject));
651    g_return_if_fail(G_IS_OBJECT(depender));
652
653    LOCK_PROPS(iobject);
654
655    if (!g_slist_find(iobject->priv->dependers, depender)) {
656        g_warn_if_reached();
657        UNLOCK_PROPS(iobject);
658        return;
659    }
660
661    iobject->priv->dependers = g_slist_remove(iobject->priv->dependers, depender);
662    g_object_unref(depender);
663
664    UNLOCK_PROPS(iobject);
665}
666