Port Camel to GObject.
[platform/upstream/evolution-data-server.git] / camel / camel-object-bag.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008 Novell, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "camel-object-bag.h"
21
22 #include <glib-object.h>
23
24 typedef struct _KeyReservation KeyReservation;
25
26 struct _KeyReservation {
27         gpointer key;
28         gint waiters;
29         GThread *owner;
30         GCond *cond;
31 };
32
33 struct _CamelObjectBag {
34         GHashTable *key_table;
35         GHashTable *object_table;
36         GEqualFunc key_equal_func;
37         CamelCopyFunc key_copy_func;
38         GFreeFunc key_free_func;
39         GSList *reserved;  /* list of KeyReservations */
40         GMutex *mutex;
41 };
42
43 static KeyReservation *
44 key_reservation_new (CamelObjectBag *bag,
45                      gconstpointer key)
46 {
47         KeyReservation *reservation;
48
49         reservation = g_slice_new0 (KeyReservation);
50         reservation->key = bag->key_copy_func (key);
51         reservation->owner = g_thread_self ();
52         reservation->cond = g_cond_new ();
53
54         bag->reserved = g_slist_prepend (bag->reserved, reservation);
55
56         return reservation;
57 }
58
59 static KeyReservation *
60 key_reservation_lookup (CamelObjectBag *bag,
61                         gconstpointer key)
62 {
63         GSList *iter;
64
65         /* XXX Might be easier to use a GHashTable for reservations. */
66         for (iter = bag->reserved; iter != NULL; iter = iter->next) {
67                 KeyReservation *reservation = iter->data;
68                 if (bag->key_equal_func (reservation->key, key))
69                         return reservation;
70         }
71
72         return NULL;
73 }
74
75 static void
76 key_reservation_free (CamelObjectBag *bag,
77                       KeyReservation *reservation)
78 {
79         /* Make sure the reservation is actually in the object bag. */
80         g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
81
82         bag->reserved = g_slist_remove (bag->reserved, reservation);
83
84         bag->key_free_func (reservation->key);
85         g_cond_free (reservation->cond);
86         g_slice_free (KeyReservation, reservation);
87 }
88
89 static void
90 object_bag_notify (CamelObjectBag *bag,
91                    GObject *where_the_object_was)
92 {
93         gpointer key;
94
95         g_mutex_lock (bag->mutex);
96
97         key = g_hash_table_lookup (bag->key_table, where_the_object_was);
98         if (key != NULL) {
99                 g_hash_table_remove (bag->key_table, where_the_object_was);
100                 g_hash_table_remove (bag->object_table, key);
101         }
102
103         g_mutex_unlock (bag->mutex);
104 }
105
106 static void
107 object_bag_weak_unref (gpointer key,
108                        GObject *object,
109                        CamelObjectBag *bag)
110 {
111         g_object_weak_unref (object, (GWeakNotify) object_bag_notify, bag);
112 }
113
114 static void
115 object_bag_unreserve (CamelObjectBag *bag,
116                       gconstpointer key)
117 {
118         KeyReservation *reservation;
119
120         reservation = key_reservation_lookup (bag, key);
121         g_return_if_fail (reservation != NULL);
122         g_return_if_fail (reservation->owner == g_thread_self ());
123
124         if (reservation->waiters > 0) {
125                 reservation->owner = NULL;
126                 g_cond_signal (reservation->cond);
127         } else
128                 key_reservation_free (bag, reservation);
129 }
130
131 /**
132  * camel_object_bag_new:
133  * @key_hash_func: a hashing function for keys
134  * @key_equal_func: a comparison function for keys
135  * @key_copy_func: a function to copy keys
136  * @key_free_func: a function to free keys
137  *
138  * Returns a new object bag.  Object bags are keyed hash tables of objects
139  * that can be updated atomically using transaction semantics.  Use
140  * camel_object_bag_destroy() to free the object bag.
141  *
142  * Returns: a newly-allocated #CamelObjectBag
143  **/
144 CamelObjectBag *
145 camel_object_bag_new (GHashFunc key_hash_func,
146                       GEqualFunc key_equal_func,
147                       CamelCopyFunc key_copy_func,
148                       GFreeFunc key_free_func)
149 {
150         CamelObjectBag *bag;
151         GHashTable *key_table;
152         GHashTable *object_table;
153
154         g_return_val_if_fail (key_hash_func != NULL, NULL);
155         g_return_val_if_fail (key_equal_func != NULL, NULL);
156         g_return_val_if_fail (key_copy_func != NULL, NULL);
157         g_return_val_if_fail (key_free_func != NULL, NULL);
158
159         /* Each key is shared between both hash tables, so only one
160          * table needs to be responsible for destroying keys. */
161
162         key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
163
164         object_table = g_hash_table_new_full (
165                 key_hash_func, key_equal_func,
166                 (GDestroyNotify) key_free_func,
167                 (GDestroyNotify) NULL);
168
169         bag = g_slice_new0 (CamelObjectBag);
170         bag->key_table = key_table;
171         bag->object_table = object_table;
172         bag->key_equal_func = key_equal_func;
173         bag->key_copy_func = key_copy_func;
174         bag->key_free_func = key_free_func;
175         bag->mutex = g_mutex_new ();
176
177         return bag;
178 }
179
180 /**
181  * camel_object_bag_get:
182  * @bag: a #CamelObjectBag
183  * @key: a key
184  *
185  * Lookup an object by @key.  If the key is currently reserved, the function
186  * will block until another thread commits or aborts the reservation.  The
187  * caller owns the reference to the returned object.  Use g_object_unref ()
188  * to unreference it.
189  *
190  * Returns: the object corresponding to @key, or %NULL if not found
191  **/
192 gpointer
193 camel_object_bag_get (CamelObjectBag *bag,
194                       gconstpointer key)
195 {
196         KeyReservation *reservation;
197         gpointer object;
198
199         g_return_val_if_fail (bag != NULL, NULL);
200         g_return_val_if_fail (key != NULL, NULL);
201
202         g_mutex_lock (bag->mutex);
203
204         /* Look for the key in the bag. */
205         object = g_hash_table_lookup (bag->object_table, key);
206         if (object != NULL) {
207                 g_object_ref (object);
208                 g_mutex_unlock (bag->mutex);
209                 return object;
210         }
211
212         /* Check if the key has been reserved. */
213         reservation = key_reservation_lookup (bag, key);
214         if (reservation == NULL) {
215                 /* No such key, so return NULL. */
216                 g_mutex_unlock (bag->mutex);
217                 return NULL;
218         }
219
220         /* Wait for the key to be unreserved. */
221         reservation->waiters++;
222         while (reservation->owner != NULL)
223                 g_cond_wait (reservation->cond, bag->mutex);
224         reservation->waiters--;
225
226         /* Check if an object was added by another thread. */
227         object = g_hash_table_lookup (bag->object_table, key);
228         if (object != NULL)
229                 g_object_ref (object);
230
231         /* We're not reserving it. */
232         reservation->owner = g_thread_self ();
233         object_bag_unreserve (bag, key);
234
235         g_mutex_unlock (bag->mutex);
236
237         return object;
238 }
239
240 /**
241  * camel_object_bag_peek:
242  * @bag: a #CamelObjectBag
243  * @key: an unreserved key
244  *
245  * Returns the object for @key in @bag, ignoring any reservations.  If it
246  * isn't committed, then it isn't considered.  This should only be used
247  * where reliable transactional-based state is not required.
248  *
249  * Unlink other "peek" operations, the caller owns the returned object
250  * reference.  Use g_object_unref () to unreference it.
251  *
252  * Returns: the object for @key, or %NULL if @key is reserved or not found
253  **/
254 gpointer
255 camel_object_bag_peek (CamelObjectBag *bag,
256                        gconstpointer key)
257 {
258         gpointer object;
259
260         g_return_val_if_fail (bag != NULL, NULL);
261         g_return_val_if_fail (key != NULL, NULL);
262
263         g_mutex_lock (bag->mutex);
264
265         object = g_hash_table_lookup (bag->object_table, key);
266         if (object != NULL)
267                 g_object_ref (object);
268
269         g_mutex_unlock (bag->mutex);
270
271         return object;
272 }
273
274 /**
275  * camel_object_bag_reserve:
276  * @bag: a #CamelObjectBag
277  * @key: the key to reserve
278  *
279  * Reserves @key in @bag.  If @key is already reserved in another thread,
280  * then wait until the reservation has been committed.
281  *
282  * After reserving @key, you either get a reference to the object
283  * corresponding to @key (similar to camel_object_bag_get()) or you get
284  * %NULL, signifying that you MUST call either camel_object_bag_add() or
285  * camel_object_bag_abort().
286  *
287  * Returns: the object for @key, or %NULL if @key is not found
288  **/
289 gpointer
290 camel_object_bag_reserve (CamelObjectBag *bag,
291                           gconstpointer key)
292 {
293         KeyReservation *reservation;
294         gpointer object;
295
296         g_return_val_if_fail (bag != NULL, NULL);
297         g_return_val_if_fail (key != NULL, NULL);
298
299         g_mutex_lock (bag->mutex);
300
301         /* If object for key already exists, return it immediately. */
302         object = g_hash_table_lookup (bag->object_table, key);
303         if (object != NULL) {
304                 g_object_ref (object);
305                 g_mutex_unlock (bag->mutex);
306                 return object;
307         }
308
309         /* If no such key exists in the bag, create a reservation. */
310         reservation = key_reservation_lookup (bag, key);
311         if (reservation == NULL) {
312                 reservation = key_reservation_new (bag, key);
313                 g_mutex_unlock (bag->mutex);
314                 return NULL;
315         }
316
317         /* Wait for the reservation to be committed or aborted. */
318         reservation->waiters++;
319         while (reservation->owner != NULL)
320                 g_cond_wait (reservation->cond, bag->mutex);
321         reservation->owner = g_thread_self ();
322         reservation->waiters--;
323
324         /* Check if the object was added by another thread. */
325         object = g_hash_table_lookup (bag->object_table, key);
326         if (object != NULL) {
327                 /* We have an object; no need to reserve the key. */
328                 object_bag_unreserve (bag, key);
329                 g_object_ref (object);
330         }
331
332         g_mutex_unlock (bag->mutex);
333
334         return object;
335 }
336
337 /**
338  * camel_object_bag_add:
339  * @bag: a #CamelObjectBag
340  * @key: a reserved key
341  * @object: a #GObject
342  *
343  * Adds @object to @bag.  The @key MUST have been previously reserved using
344  * camel_object_bag_reserve().
345  **/
346 void
347 camel_object_bag_add (CamelObjectBag *bag,
348                       gconstpointer key,
349                       gpointer object)
350 {
351         g_return_if_fail (bag != NULL);
352         g_return_if_fail (key != NULL);
353         g_return_if_fail (G_IS_OBJECT (object));
354
355         g_mutex_lock (bag->mutex);
356
357         if (g_hash_table_lookup (bag->key_table, object) == NULL) {
358                 gpointer copied_key;
359
360                 copied_key = bag->key_copy_func (key);
361                 g_hash_table_insert (bag->key_table, object, copied_key);
362                 g_hash_table_insert (bag->object_table, copied_key, object);
363                 object_bag_unreserve (bag, key);
364
365                 g_object_weak_ref (
366                         G_OBJECT (object), (GWeakNotify)
367                         object_bag_notify, bag);
368         }
369
370         g_mutex_unlock (bag->mutex);
371 }
372
373 /**
374  * camel_object_bag_abort:
375  * @bag: a #CamelObjectBag
376  * @key: a reserved key
377  *
378  * Aborts a key reservation.
379  **/
380 void
381 camel_object_bag_abort (CamelObjectBag *bag,
382                         gconstpointer key)
383 {
384         g_return_if_fail (bag != NULL);
385         g_return_if_fail (key != NULL);
386
387         g_mutex_lock (bag->mutex);
388
389         object_bag_unreserve (bag, key);
390
391         g_mutex_unlock (bag->mutex);
392 }
393
394 /**
395  * camel_object_bag_rekey:
396  * @bag: a #CamelObjectBag
397  * @object: a #GObject
398  * @new_key: a new key for @object
399  *
400  * Changes the key for @object to @new_key, atomically.
401  *
402  * It is considered a programming error if @object is not found in @bag.
403  * In such case the function will emit a terminal warning and return.
404  **/
405 void
406 camel_object_bag_rekey (CamelObjectBag *bag,
407                         gpointer object,
408                         gconstpointer new_key)
409 {
410         gpointer key;
411
412         g_return_if_fail (bag != NULL);
413         g_return_if_fail (G_IS_OBJECT (object));
414         g_return_if_fail (new_key != NULL);
415
416         g_mutex_lock (bag->mutex);
417
418         key = g_hash_table_lookup (bag->key_table, object);
419         if (key != NULL) {
420                 /* Remove the old key. */
421                 g_hash_table_remove (bag->object_table, key);
422                 g_hash_table_remove (bag->key_table, object);
423
424                 /* Insert the new key. */
425                 key = bag->key_copy_func (new_key);
426                 g_hash_table_insert (bag->object_table, key, object);
427                 g_hash_table_insert (bag->key_table, object, key);
428         } else
429                 g_warn_if_reached ();
430
431         g_mutex_unlock (bag->mutex);
432 }
433
434 /**
435  * camel_object_bag_list:
436  * @bag: a #CamelObjectBag
437  *
438  * Returns a #GPtrArray of all the objects in the bag.  The caller owns
439  * both the array and the object references, so to free the array use:
440  *
441  * <informalexample>
442  *   <programlisting>
443  *     g_ptr_array_foreach (array, g_object_unref, NULL);
444  *     g_ptr_array_free (array, TRUE);
445  *   </programlisting>
446  * </informalexample>
447  *
448  * Returns: an array of objects in @bag
449  **/
450 GPtrArray *
451 camel_object_bag_list (CamelObjectBag *bag)
452 {
453         GPtrArray *array;
454         GList *values;
455
456         g_return_val_if_fail (bag != NULL, NULL);
457
458         /* XXX Too bad we're not returning a GList; this would be trivial. */
459
460         array = g_ptr_array_new ();
461
462         g_mutex_lock (bag->mutex);
463
464         values = g_hash_table_get_values (bag->object_table);
465         while (values != NULL) {
466                 g_ptr_array_add (array, g_object_ref (values->data));
467                 values = g_list_delete_link (values, values);
468         }
469
470         g_mutex_unlock (bag->mutex);
471
472         return array;
473 }
474
475 /**
476  * camel_object_bag_remove:
477  * @bag: a #CamelObjectBag
478  * @object: a #GObject
479  *
480  * Removes @object from @bag.
481  **/
482 void
483 camel_object_bag_remove (CamelObjectBag *bag,
484                          gpointer object)
485 {
486         gpointer key;
487
488         g_return_if_fail (bag != NULL);
489         g_return_if_fail (G_IS_OBJECT (object));
490
491         g_mutex_lock (bag->mutex);
492
493         key = g_hash_table_lookup (bag->key_table, object);
494         if (key != NULL) {
495                 object_bag_weak_unref (key, object, bag);
496                 g_hash_table_remove (bag->key_table, object);
497                 g_hash_table_remove (bag->object_table, key);
498         }
499
500         g_mutex_unlock (bag->mutex);
501 }
502
503 /**
504  * camel_object_bag_destroy:
505  * @bag: a #CamelObjectBag
506  *
507  * Frees @bag.  As a precaution, the function will emit a warning to standard
508  * error and return without freeing @bag if @bag still has reserved keys.
509  **/
510 void
511 camel_object_bag_destroy (CamelObjectBag *bag)
512 {
513         g_return_if_fail (bag != NULL);
514         g_return_if_fail (bag->reserved == NULL);
515
516         /* Drop remaining weak references. */
517         g_hash_table_foreach (
518                 bag->object_table, (GHFunc)
519                 object_bag_weak_unref, bag);
520
521         g_hash_table_destroy (bag->key_table);
522         g_hash_table_destroy (bag->object_table);
523         g_mutex_free (bag->mutex);
524         g_slice_free (CamelObjectBag, bag);
525 }