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