1 /*
2 * libInstPatch
3 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; version 2.1
8 * of the License only.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA or on the web at http://www.gnu.org.
19 */
20 /*
21 * IpatchContainer_notify.c - Container add/remove callback notify system
22 */
23 #include <string.h>
24 #include "IpatchContainer.h"
25 #include "ipatch_priv.h"
26
27 /* hash value used for IpatchContainer callbacks */
28 typedef struct
29 {
30 IpatchContainerCallback callback; /* callback function */
31 IpatchContainerDisconnect disconnect; /* called when callback is disconnected */
32 GDestroyNotify notify_func; /* destroy notify function (this or disconnect will be set but not both) */
33 gpointer user_data; /* user data to pass to function */
34 guint handler_id; /* unique handler ID */
35 } ContainerCallback;
36
37 static void
38 _ipatch_container_free_container_callback(ContainerCallback *cb);
39 static void
40 _ipatch_container_free_gslist(GSList *list);
41 static gboolean
42 _ipatch_container_callback_hash_free_value(gpointer key, gpointer value, gpointer user_data);
43
44 static guint
45 ipatch_container_real_add_connect(IpatchContainer *container,
46 IpatchContainerCallback callback,
47 IpatchContainerDisconnect disconnect,
48 GDestroyNotify notify_func,
49 gpointer user_data);
50 static guint
51 ipatch_container_real_remove_connect(IpatchContainer *container,
52 IpatchItem *child,
53 IpatchContainerCallback callback,
54 IpatchContainerDisconnect disconnect,
55 GDestroyNotify notify_func,
56 gpointer user_data);
57 static void
58 ipatch_container_real_disconnect(guint handler_id, IpatchContainer *container,
59 IpatchItem *child,
60 IpatchContainerCallback callback,
61 gpointer user_data, gboolean isadd);
62 static gboolean callback_hash_GHRFunc(gpointer key, gpointer value,
63 gpointer user_data);
64
65
66 /* lock for add_callback_next_id, add_callback_hash, and add_wild_callback_list */
67 G_LOCK_DEFINE_STATIC(add_callbacks);
68
69 static guint add_callback_next_id = 1; /* next container add handler ID */
70
71 /* hash of container add callbacks
72 (IpatchContainer -> GSList<PropCallback>) */
73 static GHashTable *add_callback_hash;
74 static GSList *add_wild_callback_list = NULL; /* container add wildcard cbs */
75
76 /* lock for remove_callback_next_id, remove_callback_hash and remove_wild_callback_list */
77 G_LOCK_DEFINE_STATIC(remove_callbacks);
78
79 static guint remove_callback_next_id = 1; /* next container remove handler ID */
80
81 /* container remove callbacks */
82 static GHashTable *remove_container_callback_hash; /* IpatchContainer -> GSList<ContainerCallback> */
83 static GHashTable *remove_child_callback_hash; /* IpatchItem -> GSList<ContainerCallback> */
84 static GSList *remove_wild_callback_list = NULL; /* container add wildcard cbs */
85
86 /* - Initialization/deinitialization of 'container add/remove notify system' */
87
88 /**
89 * _ipatch_container_notify_init: (skip)
90 */
_ipatch_container_notify_init(void)91 void _ipatch_container_notify_init (void)
92 {
93 /* one time hash init for container callbacks */
94
95 /* callback pool on action: add to container */
96 add_callback_next_id = 1;
97 add_callback_hash = g_hash_table_new (NULL, NULL);
98 add_wild_callback_list = NULL;
99
100 /* callback pool on action: remove out of container */
101 remove_callback_next_id = 1;
102 remove_container_callback_hash = g_hash_table_new (NULL, NULL);
103 remove_child_callback_hash = g_hash_table_new (NULL, NULL);
104 remove_wild_callback_list = NULL;
105 }
106
107 /* free calback pool of 'container add/remove notify system' */
_ipatch_container_notify_deinit(void)108 void _ipatch_container_notify_deinit(void)
109 {
110 /* free calback pool of 'container add notify system' */
111 g_hash_table_foreach_remove(add_callback_hash,
112 _ipatch_container_callback_hash_free_value,
113 NULL);
114 g_hash_table_destroy(add_callback_hash);
115 _ipatch_container_free_gslist(add_wild_callback_list);
116
117 /* free calback pool of 'container remove notify system' */
118 g_hash_table_foreach_remove(remove_container_callback_hash,
119 _ipatch_container_callback_hash_free_value,
120 NULL);
121 g_hash_table_destroy(remove_container_callback_hash);
122 g_hash_table_foreach_remove(remove_child_callback_hash,
123 _ipatch_container_callback_hash_free_value,
124 NULL);
125 g_hash_table_destroy(remove_child_callback_hash);
126 _ipatch_container_free_gslist(remove_wild_callback_list);
127 }
128
129 /* free hash value entry */
130 static gboolean
_ipatch_container_callback_hash_free_value(gpointer key,gpointer value,gpointer user_data)131 _ipatch_container_callback_hash_free_value(gpointer key, gpointer value, gpointer user_data)
132 {
133 _ipatch_container_free_gslist((GSList *)value);
134 return TRUE;
135 }
136
137 /* free GSList */
138 static void
_ipatch_container_free_gslist(GSList * list)139 _ipatch_container_free_gslist(GSList *list)
140 {
141 g_slist_free_full(list, (GDestroyNotify)_ipatch_container_free_container_callback);
142 }
143
144 static void
_ipatch_container_free_container_callback(ContainerCallback * cb)145 _ipatch_container_free_container_callback(ContainerCallback *cb)
146 {
147 g_slice_free(ContainerCallback, cb);
148 }
149
150 /* ----------- API of 'container add/remove notify system' ------------------*/
151
152 /**
153 * ipatch_container_add_notify:
154 * @container: Container item for which an item add occurred
155 * @child: Child which was added
156 *
157 * Notify that a child add has occurred to an #IpatchContainer object.
158 * Should be called after the add has occurred. This function is normally
159 * not needed except when using ipatch_container_insert_iter().
160 */
161 void
ipatch_container_add_notify(IpatchContainer * container,IpatchItem * child)162 ipatch_container_add_notify(IpatchContainer *container, IpatchItem *child)
163 {
164 /* dynamically adjustable max callbacks to allocate cbarray for */
165 static guint max_callbacks = 64;
166 ContainerCallback *cbarray; /* stack allocated callback array */
167 ContainerCallback *cb;
168 ContainerCallback *old_cbarray;
169 guint old_max_callbacks;
170 GSList *match_container, *wild_list;
171 guint cbindex = 0, i;
172
173 g_return_if_fail(IPATCH_IS_CONTAINER(container));
174 g_return_if_fail(IPATCH_IS_ITEM(child));
175
176 /* Container has changed */
177 ipatch_item_changed((IpatchItem *)container);
178
179 /* if hooks not active for container, just return */
180 if(!(ipatch_item_get_flags(container) & IPATCH_ITEM_HOOKS_ACTIVE))
181 {
182 return;
183 }
184
185 /* allocate callback array on stack (for performance) */
186 cbarray = g_alloca(max_callbacks * sizeof(ContainerCallback));
187
188 G_LOCK(add_callbacks);
189
190 /* lookup callback list for this container */
191 match_container = g_hash_table_lookup(add_callback_hash, container);
192
193 wild_list = add_wild_callback_list;
194
195 /* duplicate lists into array (since we will call them outside of lock) */
196
197 for(; match_container && cbindex < max_callbacks;
198 match_container = match_container->next, cbindex++)
199 {
200 cb = (ContainerCallback *)(match_container->data);
201 cbarray[cbindex].callback = cb->callback;
202 cbarray[cbindex].user_data = cb->user_data;
203 }
204
205 for(; wild_list && cbindex < max_callbacks;
206 wild_list = wild_list->next, cbindex++)
207 {
208 cb = (ContainerCallback *)(wild_list->data);
209 cbarray[cbindex].callback = cb->callback;
210 cbarray[cbindex].user_data = cb->user_data;
211 }
212
213 if(match_container || wild_list)
214 {
215 /* We exceeded max_callbacks (Bender just shit a brick) */
216 old_cbarray = cbarray;
217 old_max_callbacks = max_callbacks;
218
219 cbindex += g_slist_length(match_container);
220 cbindex += g_slist_length(wild_list);
221
222 max_callbacks = cbindex + 16; /* plus some for kicks */
223
224 cbarray = g_alloca(max_callbacks * sizeof(ContainerCallback));
225 memcpy(cbarray, old_cbarray, old_max_callbacks * sizeof(ContainerCallback));
226
227 cbindex = old_max_callbacks;
228
229 /* duplicate rest of the lists */
230 for(; match_container && cbindex < max_callbacks;
231 match_container = match_container->next, cbindex++)
232 {
233 cb = (ContainerCallback *)(match_container->data);
234 cbarray[cbindex].callback = cb->callback;
235 cbarray[cbindex].user_data = cb->user_data;
236 }
237
238 for(; wild_list && cbindex < max_callbacks;
239 wild_list = wild_list->next, cbindex++)
240 {
241 cb = (ContainerCallback *)(wild_list->data);
242 cbarray[cbindex].callback = cb->callback;
243 cbarray[cbindex].user_data = cb->user_data;
244 }
245 }
246
247 G_UNLOCK(add_callbacks);
248
249 /* call the callbacks */
250 for(i = 0; i < cbindex; i++)
251 {
252 cb = &cbarray[i];
253 cb->callback(container, child, cb->user_data);
254 }
255 }
256
257 /**
258 * ipatch_container_remove_notify:
259 * @container: Container item for which a remove will occur
260 * @child: Child which will be removed
261 *
262 * Notify that a container remove will occur to an #IpatchContainer object.
263 * Should be called before the remove occurs. This function is normally not
264 * needed, except when using ipatch_container_remove_iter().
265 */
266 void
ipatch_container_remove_notify(IpatchContainer * container,IpatchItem * child)267 ipatch_container_remove_notify(IpatchContainer *container, IpatchItem *child)
268 {
269 /* dynamically adjustable max callbacks to allocate cbarray for */
270 static guint max_callbacks = 64;
271 ContainerCallback *cbarray; /* stack allocated callback array */
272 ContainerCallback *cb;
273 ContainerCallback *old_cbarray;
274 guint old_max_callbacks;
275 GSList *match_container, *match_child, *wild_list;
276 guint cbindex = 0, i;
277
278 g_return_if_fail(IPATCH_IS_CONTAINER(container));
279 g_return_if_fail(IPATCH_IS_ITEM(child));
280
281 /* Container has changed */
282 ipatch_item_changed((IpatchItem *)container);
283
284 /* if hooks not active for container, just return */
285 if(!(ipatch_item_get_flags(container) & IPATCH_ITEM_HOOKS_ACTIVE))
286 {
287 return;
288 }
289
290 /* allocate callback array on stack (for performance) */
291 cbarray = g_alloca(max_callbacks * sizeof(ContainerCallback));
292
293 G_LOCK(remove_callbacks);
294
295 /* lookup callback list for container */
296 match_container = g_hash_table_lookup(remove_container_callback_hash, container);
297
298 /* lookup callback list for child */
299 match_child = g_hash_table_lookup(remove_child_callback_hash, child);
300
301 wild_list = remove_wild_callback_list;
302
303 /* duplicate lists into array (since we will call them outside of lock) */
304
305 for(; match_container && cbindex < max_callbacks;
306 match_container = match_container->next, cbindex++)
307 {
308 cb = (ContainerCallback *)(match_container->data);
309 cbarray[cbindex].callback = cb->callback;
310 cbarray[cbindex].user_data = cb->user_data;
311 }
312
313 for(; match_child && cbindex < max_callbacks;
314 match_child = match_child->next, cbindex++)
315 {
316 cb = (ContainerCallback *)(match_child->data);
317 cbarray[cbindex].callback = cb->callback;
318 cbarray[cbindex].user_data = cb->user_data;
319 }
320
321 for(; wild_list && cbindex < max_callbacks;
322 wild_list = wild_list->next, cbindex++)
323 {
324 cb = (ContainerCallback *)(wild_list->data);
325 cbarray[cbindex].callback = cb->callback;
326 cbarray[cbindex].user_data = cb->user_data;
327 }
328
329 if(match_container || match_child || wild_list)
330 {
331 /* We exceeded max_callbacks (Bender just shit a brick) */
332 old_cbarray = cbarray;
333 old_max_callbacks = max_callbacks;
334
335 cbindex += g_slist_length(match_container);
336 cbindex += g_slist_length(match_child);
337 cbindex += g_slist_length(wild_list);
338
339 max_callbacks = cbindex + 16; /* plus some for kicks */
340
341 cbarray = g_alloca(max_callbacks * sizeof(ContainerCallback));
342 memcpy(cbarray, old_cbarray, old_max_callbacks * sizeof(ContainerCallback));
343
344 cbindex = old_max_callbacks;
345
346 /* duplicate rest of the lists */
347
348 for(; match_container && cbindex < max_callbacks;
349 match_container = match_container->next, cbindex++)
350 {
351 cb = (ContainerCallback *)(match_container->data);
352 cbarray[cbindex].callback = cb->callback;
353 cbarray[cbindex].user_data = cb->user_data;
354 }
355
356 for(; match_child && cbindex < max_callbacks;
357 match_child = match_child->next, cbindex++)
358 {
359 cb = (ContainerCallback *)(match_child->data);
360 cbarray[cbindex].callback = cb->callback;
361 cbarray[cbindex].user_data = cb->user_data;
362 }
363
364 for(; wild_list && cbindex < max_callbacks;
365 wild_list = wild_list->next, cbindex++)
366 {
367 cb = (ContainerCallback *)(wild_list->data);
368 cbarray[cbindex].callback = cb->callback;
369 cbarray[cbindex].user_data = cb->user_data;
370 }
371 }
372
373 G_UNLOCK(remove_callbacks);
374
375 /* call the callbacks */
376 for(i = 0; i < cbindex; i++)
377 {
378 cb = &cbarray[i];
379 cb->callback(container, child, cb->user_data);
380 }
381 }
382
383 /**
384 * ipatch_container_add_connect: (skip)
385 * @container: (nullable): Container to match (%NULL for wildcard)
386 * @callback: Callback function to call on match
387 * @disconnect: (nullable): Function to call when callback is disconnected or %NULL
388 * @user_data: User defined data pointer to pass to @callback and @disconnect
389 *
390 * Adds a callback which gets called when a container item add operation occurs
391 * and the container matches @container. When @container is %NULL, @callback
392 * will be called for every container add operation.
393 *
394 * Returns: Handler ID which can be used to disconnect the callback or
395 * 0 on error (only occurs on invalid function parameters).
396 */
397 guint
ipatch_container_add_connect(IpatchContainer * container,IpatchContainerCallback callback,IpatchContainerDisconnect disconnect,gpointer user_data)398 ipatch_container_add_connect(IpatchContainer *container,
399 IpatchContainerCallback callback,
400 IpatchContainerDisconnect disconnect,
401 gpointer user_data)
402 {
403 return (ipatch_container_real_add_connect(container, callback, disconnect, NULL, user_data));
404 }
405
406 /**
407 * ipatch_container_add_connect_notify: (rename-to ipatch_container_add_connect)
408 * @container: (nullable): Container to match (%NULL for wildcard)
409 * @callback: (scope notified) (closure user_data): Callback function to call on match
410 * @notify_func: (scope async) (closure user_data) (nullable): Callback destroy notify
411 * when callback is disconnected or %NULL
412 * @user_data: (nullable): User defined data pointer to pass to @callback and @disconnect
413 *
414 * Adds a callback which gets called when a container item add operation occurs
415 * and the container matches @container. When @container is %NULL, @callback
416 * will be called for every container add operation.
417 *
418 * Returns: Handler ID which can be used to disconnect the callback or
419 * 0 on error (only occurs on invalid function parameters).
420 *
421 * Since: 1.1.0
422 */
423 guint
ipatch_container_add_connect_notify(IpatchContainer * container,IpatchContainerCallback callback,GDestroyNotify notify_func,gpointer user_data)424 ipatch_container_add_connect_notify(IpatchContainer *container,
425 IpatchContainerCallback callback,
426 GDestroyNotify notify_func,
427 gpointer user_data)
428 {
429 return (ipatch_container_real_add_connect(container, callback, NULL, notify_func, user_data));
430 }
431
432 static guint
ipatch_container_real_add_connect(IpatchContainer * container,IpatchContainerCallback callback,IpatchContainerDisconnect disconnect,GDestroyNotify notify_func,gpointer user_data)433 ipatch_container_real_add_connect(IpatchContainer *container,
434 IpatchContainerCallback callback,
435 IpatchContainerDisconnect disconnect,
436 GDestroyNotify notify_func,
437 gpointer user_data)
438 {
439 ContainerCallback *cb;
440 GSList *cblist;
441 guint handler_id;
442
443 g_return_val_if_fail(!container || IPATCH_IS_CONTAINER(container), 0);
444 g_return_val_if_fail(callback != NULL, 0);
445
446 cb = g_slice_new(ContainerCallback);
447 cb->callback = callback;
448 cb->disconnect = disconnect;
449 cb->user_data = user_data;
450
451 G_LOCK(add_callbacks);
452
453 handler_id = add_callback_next_id++;
454 cb->handler_id = handler_id;
455
456 if(container)
457 {
458 /* get existing list for Container (if any) */
459 cblist = g_hash_table_lookup(add_callback_hash, container);
460
461 /* update the list in the hash table */
462 g_hash_table_insert(add_callback_hash, container,
463 g_slist_prepend(cblist, cb));
464 }
465 else /* callback is wildcard, just add to the wildcard list */
466 {
467 add_wild_callback_list = g_slist_prepend(add_wild_callback_list, cb);
468 }
469
470 G_UNLOCK(add_callbacks);
471
472 return (handler_id);
473 }
474
475 /**
476 * ipatch_container_remove_connect: (skip)
477 * @container: (nullable): Container to match (%NULL for wildcard)
478 * @child: (nullable): Child item to match (%NULL for wildcard)
479 * @callback: Callback function to call on match
480 * @disconnect: (nullable): Function to call when callback is disconnected or %NULL
481 * @user_data: (closure): User defined data pointer to pass to @callback and @disconnect
482 *
483 * Adds a callback which gets called when a container item remove operation
484 * occurs and the container matches @container and child item matches @child.
485 * The @container and/or @child parameters can be %NULL in which case they are
486 * wildcard. If both are %NULL then @callback will be called for every
487 * container remove operation. Note that specifying only @child or both
488 * @container and @child is the same, since a child belongs to only one container.
489 *
490 * Returns: Handler ID which can be used to disconnect the callback or
491 * 0 on error (only occurs on invalid function parameters).
492 */
493 guint
ipatch_container_remove_connect(IpatchContainer * container,IpatchItem * child,IpatchContainerCallback callback,IpatchContainerDisconnect disconnect,gpointer user_data)494 ipatch_container_remove_connect(IpatchContainer *container,
495 IpatchItem *child,
496 IpatchContainerCallback callback,
497 IpatchContainerDisconnect disconnect,
498 gpointer user_data)
499 {
500 return (ipatch_container_real_remove_connect(container, child, callback,
501 disconnect, NULL, user_data));
502 }
503
504 /**
505 * ipatch_container_remove_connect_notify: (rename-to ipatch_container_remove_connect)
506 * @container: (nullable): Container to match (%NULL for wildcard)
507 * @child: (nullable): Child item to match (%NULL for wildcard)
508 * @callback: (scope notified) (closure user_data): Callback function to call on match
509 * @notify_func: (scope async) (closure user_data) (nullable): Function to call
510 * when callback is disconnected or %NULL
511 * @user_data: (nullable): User defined data pointer to pass to @callback and @disconnect
512 *
513 * Adds a callback which gets called when a container item remove operation
514 * occurs and the container matches @container and child item matches @child.
515 * The @container and/or @child parameters can be %NULL in which case they are
516 * wildcard. If both are %NULL then @callback will be called for every
517 * container remove operation. Note that specifying only @child or both
518 * @container and @child is the same, since a child belongs to only one container.
519 *
520 * Returns: Handler ID which can be used to disconnect the callback or
521 * 0 on error (only occurs on invalid function parameters).
522 *
523 * Since: 1.1.0
524 */
525 guint
ipatch_container_remove_connect_notify(IpatchContainer * container,IpatchItem * child,IpatchContainerCallback callback,GDestroyNotify notify_func,gpointer user_data)526 ipatch_container_remove_connect_notify(IpatchContainer *container,
527 IpatchItem *child,
528 IpatchContainerCallback callback,
529 GDestroyNotify notify_func,
530 gpointer user_data)
531 {
532 return (ipatch_container_real_remove_connect(container, child, callback,
533 NULL, notify_func, user_data));
534 }
535
536 static guint
ipatch_container_real_remove_connect(IpatchContainer * container,IpatchItem * child,IpatchContainerCallback callback,IpatchContainerDisconnect disconnect,GDestroyNotify notify_func,gpointer user_data)537 ipatch_container_real_remove_connect(IpatchContainer *container,
538 IpatchItem *child,
539 IpatchContainerCallback callback,
540 IpatchContainerDisconnect disconnect,
541 GDestroyNotify notify_func,
542 gpointer user_data)
543 {
544 ContainerCallback *cb;
545 GSList *cblist;
546 guint handler_id;
547
548 g_return_val_if_fail(!container || IPATCH_IS_CONTAINER(container), 0);
549 g_return_val_if_fail(!child || IPATCH_IS_ITEM(child), 0);
550 g_return_val_if_fail(callback != NULL, 0);
551
552 cb = g_slice_new(ContainerCallback);
553 cb->callback = callback;
554 cb->disconnect = disconnect;
555 cb->notify_func = notify_func;
556 cb->user_data = user_data;
557
558 G_LOCK(remove_callbacks);
559
560 handler_id = remove_callback_next_id++;
561 cb->handler_id = handler_id;
562
563 if(child) /* child and container:child are equivalent (child has only 1 parent) */
564 {
565 /* get existing list for child (if any) */
566 cblist = g_hash_table_lookup(remove_child_callback_hash, child);
567
568 /* update new list head */
569 g_hash_table_insert(remove_child_callback_hash, child,
570 g_slist_prepend(cblist, cb));
571 }
572 else if(container)
573 {
574 /* get existing list for container (if any) */
575 cblist = g_hash_table_lookup(remove_container_callback_hash, container);
576
577 /* update new list head */
578 g_hash_table_insert(remove_container_callback_hash, container,
579 g_slist_prepend(cblist, cb));
580 }
581 else /* callback is completely wildcard, just add to the wildcard list */
582 {
583 remove_wild_callback_list = g_slist_prepend(remove_wild_callback_list, cb);
584 }
585
586 G_UNLOCK(remove_callbacks);
587
588 return (handler_id);
589 }
590
591 /**
592 * ipatch_container_add_disconnect:
593 * @handler_id: ID of callback handler
594 *
595 * Disconnects a container add callback previously connected with
596 * ipatch_container_add_connect() by handler ID. The
597 * ipatch_container_add_disconnect_matched() function can be used instead to
598 * disconnect by original callback criteria and is actually faster.
599 */
600 void
ipatch_container_add_disconnect(guint handler_id)601 ipatch_container_add_disconnect(guint handler_id)
602 {
603 g_return_if_fail(handler_id != 0);
604 ipatch_container_real_disconnect(handler_id, NULL, NULL, NULL, NULL, TRUE);
605 }
606
607 /**
608 * ipatch_container_add_disconnect_matched: (skip)
609 * @container: Container to match
610 * @callback: Callback function to match
611 * @user_data: User data to match
612 *
613 * Disconnects a container add callback previously connected with
614 * ipatch_container_add_connect() by match criteria.
615 */
616 void
ipatch_container_add_disconnect_matched(IpatchContainer * container,IpatchContainerCallback callback,gpointer user_data)617 ipatch_container_add_disconnect_matched(IpatchContainer *container,
618 IpatchContainerCallback callback,
619 gpointer user_data)
620 {
621 g_return_if_fail(callback != NULL);
622 ipatch_container_real_disconnect(0, container, NULL, callback, user_data, TRUE);
623 }
624
625 /**
626 * ipatch_container_remove_disconnect:
627 * @handler_id: ID of callback handler
628 *
629 * Disconnects a container remove callback previously connected with
630 * ipatch_container_remove_connect() by handler ID. The
631 * ipatch_container_remove_disconnect_matched() function can be used instead to
632 * disconnect by original callback criteria and is actually faster.
633 */
634 void
ipatch_container_remove_disconnect(guint handler_id)635 ipatch_container_remove_disconnect(guint handler_id)
636 {
637 g_return_if_fail(handler_id != 0);
638 ipatch_container_real_disconnect(handler_id, NULL, NULL, NULL, NULL, FALSE);
639 }
640
641 /**
642 * ipatch_container_remove_disconnect_matched: (skip)
643 * @container: (nullable): Container to match (can be %NULL if @child is set)
644 * @child: (nullable): Child item to match (can be %NULL if @container is set)
645 * @callback: Callback function to match
646 * @user_data: User data to match
647 *
648 * Disconnects a handler previously connected with
649 * ipatch_container_remove_connect() by match criteria.
650 */
651 void
ipatch_container_remove_disconnect_matched(IpatchContainer * container,IpatchItem * child,IpatchContainerCallback callback,gpointer user_data)652 ipatch_container_remove_disconnect_matched(IpatchContainer *container,
653 IpatchItem *child,
654 IpatchContainerCallback callback,
655 gpointer user_data)
656 {
657 g_return_if_fail(callback != NULL);
658 ipatch_container_real_disconnect(0, container, child, callback, user_data, FALSE);
659 }
660
661 /* a bag used in ipatch_container_real_disconnect */
662 typedef struct
663 {
664 gboolean found; /* Set to TRUE if handler found */
665 IpatchItem *match; /* container or child - in: (match only), out */
666 ContainerCallback cb; /* in: handler_id, out: disconnect, user_data */
667 gpointer update_key; /* out: key of list root requiring update */
668 GSList *update_list; /* out: new root of list to update */
669 gboolean update_needed; /* out: set when a list root needs updating */
670 gboolean isadd; /* same value as function parameter */
671 } DisconnectBag;
672
673 /* function for removing a callback using match criteria (hash table lookup).
674 * Faster than iterating over the hash (required when searching by ID). */
675 static void
disconnect_matched(GHashTable * hash,DisconnectBag * bag)676 disconnect_matched(GHashTable *hash, DisconnectBag *bag)
677 {
678 ContainerCallback *cb;
679 GSList *list, *newroot, *p;
680
681 list = g_hash_table_lookup(hash, bag->match);
682
683 if(!list)
684 {
685 return;
686 }
687
688 for(p = list; p; p = p->next) /* loop over callbacks in list */
689 {
690 cb = (ContainerCallback *)(p->data);
691
692 /* matches criteria? */
693 if(cb->callback == bag->cb.callback && cb->user_data == bag->cb.user_data)
694 {
695 /* return callback's disconnect func */
696 bag->found = TRUE;
697 bag->cb.disconnect = cb->disconnect;
698
699 g_slice_free(ContainerCallback, cb);
700 newroot = g_slist_delete_link(list, p);
701
702 if(!newroot) /* no more list? - remove hash entry */
703 {
704 g_hash_table_remove(hash, bag->match);
705 }
706 else if(newroot != list) /* root of list changed? - update hash entry */
707 {
708 g_hash_table_insert(hash, bag->match, newroot);
709 }
710
711 break;
712 }
713 }
714 }
715
716 /* Used by disconnect functions.
717 * Either handler_id should be set to a non-zero value or the other
718 * parameters should be assigned but not both.
719 * isadd specifies if the handler is an add callback (TRUE) or remove
720 * callback (FALSE).
721 */
722 static void
ipatch_container_real_disconnect(guint handler_id,IpatchContainer * container,IpatchItem * child,IpatchContainerCallback callback,gpointer user_data,gboolean isadd)723 ipatch_container_real_disconnect(guint handler_id, IpatchContainer *container,
724 IpatchItem *child,
725 IpatchContainerCallback callback,
726 gpointer user_data, gboolean isadd)
727 {
728 ContainerCallback *cb;
729 DisconnectBag bag = { 0 };
730 gboolean isfoundchild = FALSE;
731 GSList *p;
732
733 g_return_if_fail(handler_id != 0 || callback != 0);
734 g_return_if_fail(handler_id == 0 || callback == 0);
735
736 if(!handler_id) /* find by match criteria? */
737 {
738 bag.match = child ? child : (IpatchItem *)container;
739 bag.cb.callback = callback;
740 bag.cb.user_data = user_data;
741 }
742 else
743 {
744 bag.cb.handler_id = handler_id; /* find by handler ID */
745 }
746
747 bag.isadd = isadd;
748
749 if(isadd) /* add callback search? */
750 {
751 G_LOCK(add_callbacks);
752
753 if(handler_id) /* search by ID? */
754 {
755 /* scan every list in add callback hash and remove if found */
756 g_hash_table_foreach_remove(add_callback_hash, callback_hash_GHRFunc,
757 &bag);
758
759 if(bag.update_needed) /* update root of list if needed (can't do that in GHRFunc) */
760 {
761 g_hash_table_insert(add_callback_hash, bag.update_key, bag.update_list);
762 }
763 }
764 else
765 {
766 disconnect_matched(add_callback_hash, &bag); /* lookup by match and remove if found */
767 }
768
769 /* if not found, check wildcard list (if search by handler ID
770 * or NULL container) */
771 if(!bag.found && (handler_id || !container))
772 {
773 for(p = add_wild_callback_list; p; p = p->next)
774 {
775 cb = (ContainerCallback *)(p->data);
776
777 if((handler_id && cb->handler_id == handler_id)
778 || (!handler_id && cb->callback == callback
779 && cb->user_data == user_data))
780 {
781 bag.found = TRUE;
782 bag.cb.disconnect = cb->disconnect;
783 bag.cb.user_data = cb->user_data;
784 g_slice_free(ContainerCallback, cb);
785
786 add_wild_callback_list
787 = g_slist_delete_link(add_wild_callback_list, p);
788 break;
789 }
790 }
791 }
792
793 G_UNLOCK(add_callbacks);
794 }
795 else /* remove callback search */
796 {
797 G_LOCK(remove_callbacks);
798
799 /* check child remove callback list if search by ID or child is set */
800 if(handler_id) /* search by ID? */
801 {
802 g_hash_table_foreach_remove(remove_child_callback_hash,
803 callback_hash_GHRFunc, &bag);
804
805 if(bag.update_needed) /* update root of list if needed (can't do that in GHRFunc) */
806 g_hash_table_insert(remove_child_callback_hash, bag.update_key,
807 bag.update_list);
808 }
809 else if(child) /* match by child? */
810 {
811 disconnect_matched(remove_child_callback_hash, &bag);
812 }
813
814 if(bag.found)
815 {
816 isfoundchild = TRUE; /* indicate it is a child callback */
817 }
818
819 if(!bag.found) /* not yet found? */
820 {
821 /* check container remove callback list if search by ID or container is set */
822 if(handler_id) /* search by ID? */
823 {
824 g_hash_table_foreach_remove(remove_container_callback_hash,
825 callback_hash_GHRFunc, &bag);
826
827 if(bag.update_needed) /* update root of list if needed (can't do that in GHRFunc) */
828 g_hash_table_insert(remove_container_callback_hash, bag.update_key,
829 bag.update_list);
830 }
831 else if(container) /* match by container? */
832 {
833 disconnect_matched(remove_container_callback_hash, &bag);
834 }
835 }
836
837 /* if not yet found, check wildcard list (if search by handler ID
838 * or NULL container and child) */
839 if(!bag.found && (handler_id || (!container && !child)))
840 {
841 for(p = remove_wild_callback_list; p; p = p->next)
842 {
843 cb = (ContainerCallback *)(p->data);
844
845 if((handler_id && cb->handler_id == handler_id)
846 || (!handler_id && cb->callback == callback
847 && cb->user_data == user_data))
848 {
849 bag.found = TRUE;
850 bag.cb.disconnect = cb->disconnect;
851 bag.cb.user_data = cb->user_data;
852 g_slice_free(ContainerCallback, cb);
853
854 remove_wild_callback_list
855 = g_slist_delete_link(remove_wild_callback_list, p);
856 break;
857 }
858 }
859 }
860
861 G_UNLOCK(remove_callbacks);
862 }
863
864 if(!bag.found)
865 {
866 if(handler_id)
867 g_critical(G_STRLOC ": Failed to find %s container handler with ID '%d'",
868 isadd ? "add" : "remove", handler_id);
869 else
870 g_critical(G_STRLOC ": Failed to find %s container handler with criteria %p:%p:%p:%p",
871 isadd ? "add" : "remove", container, child, callback, user_data);
872 }
873
874 /* see if callback found and it had a disconnect func */
875 if(bag.cb.disconnect)
876 {
877 if(isfoundchild)
878 {
879 bag.cb.disconnect(NULL, bag.match, bag.cb.user_data);
880 }
881 else
882 {
883 bag.cb.disconnect((IpatchContainer *)bag.match, NULL, bag.cb.user_data);
884 }
885 }
886 }
887
888 /* finds a container add or remove handler by ID and removes it */
889 static gboolean
callback_hash_GHRFunc(gpointer key,gpointer value,gpointer user_data)890 callback_hash_GHRFunc(gpointer key, gpointer value, gpointer user_data)
891 {
892 DisconnectBag *bag = (DisconnectBag *)user_data;
893 GSList *cblist = (GSList *)value, *p, *newroot;
894 ContainerCallback *cb;
895
896 p = cblist;
897
898 while(p) /* loop over callbacks in callback list */
899 {
900 cb = (ContainerCallback *)(p->data);
901
902 /* matches criteria? (by ID or by match) */
903 if((bag->cb.handler_id && cb->handler_id == bag->cb.handler_id)
904 || (!bag->cb.handler_id && key == bag->match
905 && cb->callback == bag->cb.callback
906 && cb->user_data == bag->cb.user_data))
907 {
908 /* return callback's item, pspec, disconnect func and user_data */
909 bag->found = TRUE;
910 bag->cb.disconnect = cb->disconnect;
911 bag->cb.user_data = cb->user_data;
912 bag->match = key;
913
914 g_slice_free(ContainerCallback, cb);
915 newroot = g_slist_delete_link(cblist, p);
916
917 if(!newroot)
918 {
919 return (TRUE); /* no more list? Remove hash entry */
920 }
921
922 /* if root not the same, return update information
923 (can't be done in GHRFunc) */
924 if(newroot != cblist)
925 {
926 bag->update_key = key;
927 bag->update_list = newroot;
928 }
929
930 return (FALSE); /* don't remove entry (callback list not empty) */
931 }
932
933 p = g_slist_next(p);
934 }
935
936 return (FALSE); /* don't remove entry (item not found) */
937 }
938