diff options
Diffstat (limited to 'dict/pdict.c')
-rw-r--r-- | dict/pdict.c | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/dict/pdict.c b/dict/pdict.c new file mode 100644 index 0000000..04ac84c --- /dev/null +++ b/dict/pdict.c @@ -0,0 +1,500 @@ +#include "../stdafx.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <regex.h> +#if defined(_WINDOWS) && !defined(_CYGWIN) +#include "wincompat.h" +#endif +#include "utils.h" +#include "plist.h" +#include "ptree.h" +#include "pdict-impl.h" + +typedef struct pdict_ent pdict_ent_t; +typedef int (*pdict_walk_int_func_t)(pdict_ent_t *pde, void *arg); + +static int _pdict_ent_add_persistent_change_listeners(pdict_t *pd, + pdict_ent_t *pde); +static int _pdict_ent_add_persistent_change_listener(pdict_ent_t *pde, + pdict_persistent_listener_t *pl); +static int _pdict_ent_remove_persistent_change_listener(pdict_ent_t *pde, + pdict_persistent_listener_t *pl); +static void _pdict_ent_notify(pdict_ent_t *pde, int reason, const char *ov); +static int _pdict_ent_add_change_listener(pdict_ent_t *pde, + pdl_notify_func_t notify, void *arg); +static int _pdict_walk_int(pdict_t *pd, pdict_walk_int_func_t w, void *arg); +static int _pdict_ent_remove_change_listener(pdict_ent_t *pde, + pdl_notify_func_t notify, void *a); + +pdict_t * +pdict_alloc(void) +{ + pdict_t *pd; + + if (!(pd = malloc(sizeof (*pd)))) + return 0; + memset(pd, 0, sizeof (*pd)); + + return pd; +} + +static int +pdecmp(const void *sv, const void *dv) +{ + return strcmp(*(char **)sv, *(char **)dv); +} + +static int +pdict_ent_remove_change_listeners_cb(const void *v, const void *v0, void *a) +{ + free((void *)v); v = NULL; + return (1); +} + +static int +pdict_ent_remove_change_listeners(pdict_ent_t *pde) +{ + plist_walk(pde->pde_listeners, pdict_ent_remove_change_listeners_cb, 0); + plist_clear(&pde->pde_listeners); + return 1; +} + +static int +pdict_ent_listeners_copy_cb(const void *v, const void *v0, void *a) +{ + pdict_listener_t *pdl = (pdict_listener_t *)v; + pdict_ent_t *pde_ent_copy = a; + + return _pdict_ent_add_change_listener(pde_ent_copy, pdl->pdl_notify, pdl->pdl_arg); +} + +static void +_pdict_ent_listeners_copy(pdict_ent_t *pde, pdict_ent_t *pde_copy) +{ + plist_walk(pde->pde_listeners, pdict_ent_listeners_copy_cb, pde_copy); +} + +/* + * Sets or resets an entry to the dictionary, returning 0 on failure. + */ +int +pdict_add(pdict_t *pd, const char *k, const char *v, const char **ovp) +{ + pdict_ent_t *n; + pdict_ent_t n_copy; + const char *ov; + + if (!(k = strdup(k))) + return 0; + if (!(v = strdup(v))) { + free((void *)k); k = NULL; + return 0; + } + memset(&n_copy, 0, sizeof(pdict_ent_t)); + if (ptree_contains((void *)&k, pd->pd_ents, pdecmp, (void *)&n)) { + free((void *)k); k = NULL; + ov = n->pde_val; + n->pde_val = v; + + if (ovp) + *ovp = ov; + else { + free((void *)ov); ov = NULL; + } + + //We copy n so that it's safe if it gets removed during a callback + if(n->pde_listeners) + { + n_copy.pde_key = strdup(n->pde_key); + n_copy.pde_val = strdup(n->pde_val); + _pdict_ent_listeners_copy(n, &n_copy); + _pdict_ent_notify(&n_copy, PDR_VALUE_CHANGED, ov); + pdict_ent_remove_change_listeners(&n_copy); + free((char *)n_copy.pde_key); + free((char *)n_copy.pde_val); + } + return 1; + } + if (!(n = malloc(sizeof (*n)))) { + free((void *)k); k = NULL; + free((void *)v); v = NULL; + return (0); + } + memset(n, 0, sizeof (*n)); + n->pde_key = k; + n->pde_val = v; + + //Add dict listeners to the dict entry object + if (!_pdict_ent_add_persistent_change_listeners(pd, n)) { + free((void *)k); k = NULL; + free((void *)v); v = NULL; + free(n); n = NULL; + return (0); + } + //Add the dict entry to the dict (replacing if there is a matching key). + if (!ptree_replace(n, &pd->pd_ents, pdecmp, NULL)) { + pdict_ent_remove_change_listeners(n); + free((void *)k); k = NULL; + free((void *)v); v = NULL; + free(n); n = NULL; + return (0); + } + //notify the listeners + if(n->pde_listeners) + { + n_copy.pde_key = strdup(n->pde_key); + n_copy.pde_val = strdup(n->pde_val); + _pdict_ent_listeners_copy(n, &n_copy); + _pdict_ent_notify(&n_copy, PDR_ENTRY_ADDED, n_copy.pde_val); + pdict_ent_remove_change_listeners(&n_copy); + free((char *)n_copy.pde_key); + free((char *)n_copy.pde_val); + } + + if (ovp) + *ovp = NULL; + + return 1; +} + +int +pdict_ent_remove(pdict_t *pd, const char *k, char **ovp) +{ + pdict_ent_t *n; + + pu_log(PUL_VERB, 0, "Removing in key pdict_ent_remove: %s", k); + + if (!ptree_remove((void *)&k, &pd->pd_ents, pdecmp, (void *)&n)) + { + //pu_log(PUL_INFO, 0, "Failed to remove key in pdict_ent_remove: %s", k); + return 0; + } + + _pdict_ent_notify(n, PDR_ENTRY_REMOVING, n->pde_val); + + if (ovp) + *ovp = (char *)n->pde_val; + else { + free((void *)n->pde_val); + } + free((void *)n->pde_key); + pdict_ent_remove_change_listeners(n); + free(n); + return 1; +} + +static int +pdict_ent_remove_persistent_change_listener_cb(const void *pl, const void *v, + void *a) +{ + _pdict_ent_remove_persistent_change_listener((pdict_ent_t *)a, + (pdict_persistent_listener_t *)pl); + return 1; +} + +static int +pdict_ent_add_persistent_change_listener_cb(const void *lid, const void *pl, + void *a) +{ + return _pdict_ent_add_persistent_change_listener((pdict_ent_t *)a, + (pdict_persistent_listener_t *)pl); +} + +static int +_pdict_ent_add_persistent_change_listeners(pdict_t *pd, pdict_ent_t *pde) +{ + if (!plist_walk(pd->pd_persistent_listeners, pdict_ent_add_persistent_change_listener_cb, pde)) { + plist_walk(pd->pd_persistent_listeners, pdict_ent_remove_persistent_change_listener_cb, pde); + pu_log(PUL_WARN, 0, "Failed to add persistent change listener in _pdict_ent_add_persistent_change_listeners."); + return 0; + } + return 1; +} + +static int +pdict_ent_remove_persistent_change_listener_dcb(pdict_ent_t *pde, void *pl) +{ + return _pdict_ent_remove_persistent_change_listener(pde, + (pdict_persistent_listener_t *)pl); +} + +static int +pdict_ent_add_persistent_change_listener_dcb(pdict_ent_t *pde, void *pl) +{ + return _pdict_ent_add_persistent_change_listener(pde, + (pdict_persistent_listener_t *)pl); +} + +static int +_pdict_ent_add_persistent_change_listener(pdict_ent_t *pde, + pdict_persistent_listener_t *pdpl) +{ + int res; + + if ((res = regexec(&pdpl->pdpl_regex, pde->pde_key, 0, NULL, 0)) != 0) + { + return res == REG_NOMATCH; + } + if (!_pdict_ent_add_change_listener(pde, pdpl->pdpl_l.pdl_notify, pdpl->pdpl_l.pdl_arg)) + { + pu_log(PUL_WARN, 0, "Failed to add persistent change listener in _pdict_ent_add_persistent_change_listener."); + return 0; + } + if (pdpl->pdpl_new) + pdpl->pdpl_l.pdl_notify(pde->pde_key, pde->pde_val, PDR_CURRENT_VALUE, NULL, pdpl->pdpl_l.pdl_arg); + return 1; +} + +static int +_pdict_ent_remove_persistent_change_listener(pdict_ent_t *pde, + pdict_persistent_listener_t *pl) +{ + _pdict_ent_remove_change_listener(pde, pl->pdpl_l.pdl_notify, + pl->pdpl_l.pdl_arg); + return 1; +} + +int +pdict_remove_persistent_change_listener(pdict_t *pd, int id) +{ + pdict_persistent_listener_t *pdpl; + + if (!plist_remove((void *)(size_t)id, &pd->pd_persistent_listeners, (void **)&pdpl) || !pdpl) + { + pu_log(PUL_WARN, 0, "Failed plist_remove in pdict_remove_persistent_change_listener."); + return 0; + } + if (!_pdict_walk_int(pd, pdict_ent_remove_persistent_change_listener_dcb, pdpl)) + { + pu_log(PUL_WARN, 0, "Failed _pdict_walk_int in pdict_remove_persistent_change_listener."); + return 0; + } + regfree(&pdpl->pdpl_regex); + free(pdpl); pdpl = NULL; + return 1; +} + +int +pdict_add_persistent_change_listener(pdict_t *pd, const char *kpat, + pdl_notify_func_t notify, void *arg) +{ + pdict_persistent_listener_t *pl; + static int lid = 1; + + if (!(pl = malloc(sizeof (*pl)))) + return 0; + memset(pl, 0, sizeof (*pl)); + pl->pdpl_l.pdl_notify = notify; + pl->pdpl_l.pdl_arg = arg; + if (regcomp(&pl->pdpl_regex, kpat, REG_EXTENDED | REG_NOSUB) != 0) { + // XXX todo: communicate error context is not libc + free(pl); pl = NULL; + pu_log(PUL_WARN, 0, "Failed regcomp in pdict_add_persistent_change_listener."); + return 0; + } + + plist_add((void *)(size_t)lid, pl, &pd->pd_persistent_listeners); + + pl->pdpl_new = 1; + if (!_pdict_walk_int(pd, + pdict_ent_add_persistent_change_listener_dcb, pl)) { + _pdict_walk_int(pd, + pdict_ent_remove_persistent_change_listener_dcb, pl); + plist_remove((void *)(size_t)lid, &pd->pd_persistent_listeners, NULL); + regfree(&pl->pdpl_regex); + free(pl); pl = NULL; + pu_log(PUL_WARN, 0, "Failed _pdict_walk_int in pdict_add_persistent_change_listener."); + return 0; + } + pl->pdpl_new = 0; + return lid++; +} + +/* + * Return whether a given key is in the dictionary. If the given + * entry pointer is non-NULL, set it to the entry. + */ +static int +_pdict_ent_lookup(pdict_t *pd, const char *k, pdict_ent_t **e) +{ + return ptree_contains((void *)&k, pd->pd_ents, pdecmp, (void **)e); +} + +int +pdict_ent_lookup(pdict_t *pd, const char *k, const char **v) +{ + pdict_ent_t *pde; + + if (_pdict_ent_lookup(pd, k, &pde)) { + if (v) + *v = strdup(pde->pde_val); + return 1; + } + return 0; +} + +static int +pdict_ent_remove_change_listener_cb(const void *k, const void *v, void *a) +{ + pdict_listener_t *l = (pdict_listener_t *)k; + void **arg = (void **)a; + + if (l->pdl_notify == (pdl_notify_func_t)arg[0] && l->pdl_arg == arg[1]) { + arg[2] = l; + return 0; + } + return 1; +} + +static int +_pdict_ent_remove_change_listener(pdict_ent_t *pde, pdl_notify_func_t notify, + void *a) +{ + void *arg[3]; + + arg[0] = (void *)notify; + arg[1] = a; + arg[2] = NULL; + plist_walk(pde->pde_listeners, pdict_ent_remove_change_listener_cb, arg); + if (arg[2]) { + plist_remove(arg[2], &pde->pde_listeners, NULL); + free(arg[2]); arg[2] = NULL; + return 1; + } + return 0; +} + +static int +_pdict_ent_add_change_listener(pdict_ent_t *pde, pdl_notify_func_t notify, + void *arg) +{ + pdict_listener_t *l; + + if (!(l = malloc(sizeof (*l)))) + return 0; + memset(l, 0, sizeof (*l)); + l->pdl_notify = notify; + l->pdl_arg = arg; + if (!plist_add(l, 0, &pde->pde_listeners)) { + free(l); l = NULL; + pu_log(PUL_WARN, 0, "Failed plist_add in _pdict_ent_add_change_listener."); + return 0; + } + + return 1; +} + +int +pdict_ent_add_change_listener(pdict_t *pd, const char *k, + pdl_notify_func_t notify, void *arg) +{ + pdict_ent_t *pde; + + if (!_pdict_ent_lookup(pd, k, &pde)) + return 0; + return _pdict_ent_add_change_listener(pde, notify, arg); +} + +typedef struct { + pdict_ent_t *penca_pde; + pdict_reason_t penca_reason; + const char *penca_ov; +} pdict_ent_notify_cb_args_t; + +static int +pdict_ent_notify_cb(const void *v, const void *v0, void *a) +{ + pdict_listener_t *pdl = (pdict_listener_t *)v; + pdict_ent_notify_cb_args_t *penca = a; + + pdl->pdl_notify(penca->penca_pde->pde_key, penca->penca_pde->pde_val, + penca->penca_reason, penca->penca_ov, pdl->pdl_arg); + return 1; +} + +static void +_pdict_ent_notify(pdict_ent_t *pde, int reason, const char *ov) +{ + pdict_ent_notify_cb_args_t penca = { pde, reason, ov }; + + plist_walk(pde->pde_listeners, pdict_ent_notify_cb, &penca); +} + +static ptree_walk_res_t +pdict_walk_int_cb(const void *v, int level, void *a, void *pwra) +{ + void **args = a; + + return ((pdict_walk_int_func_t)args[0])((pdict_ent_t *)v, args[1]); +} + +static ptree_walk_res_t +pdict_walk_cb(const void *v, int level, void *a, void *pwra) +{ + pdict_ent_t *pde = (pdict_ent_t *)v; + void **args = a; + + return ((pdict_walk_func_t)args[0])(pde->pde_key, pde->pde_val, + args[1]); +} + +static int +_pdict_walk_int(pdict_t *pd, pdict_walk_int_func_t w, void *arg) +{ + void *args[2] = { (void *)w, arg }; + + return ptree_walk(pd->pd_ents, PTREE_INORDER, pdict_walk_int_cb, pdecmp, args); +} + +int +pdict_walk(pdict_t *pd, pdict_walk_func_t w, void *arg) +{ + void *args[2] = { (void *)w, arg }; + + return ptree_walk(pd->pd_ents, PTREE_INORDER, pdict_walk_cb, NULL, args); +} + +int +pdict_ent_remove_change_listener(pdict_t *pd, const char *k, + pdl_notify_func_t nf, void *arg) +{ + pdict_ent_t *e; + int res; + + if (!(res = _pdict_ent_lookup(pd, k, &e))) + return 0; + return _pdict_ent_remove_change_listener(e, nf, arg); +} + +const char * +pdict_reason_str(pdict_reason_t r) +{ + switch (r) { + case PDR_VALUE_CHANGED: + return "changed"; + case PDR_ENTRY_ADDED: + return "added"; + case PDR_ENTRY_REMOVING: + return "removing"; + case PDR_CURRENT_VALUE: + return "current"; + default: + return "?"; + } +} + +pdict_reason_t +pdict_reason_from_str(const char *s) +{ + if (strcmp(s, "changed") == 0) + return PDR_VALUE_CHANGED; + if (strcmp(s, "current") == 0) + return PDR_CURRENT_VALUE; + if (strcmp(s, "added") == 0) + return PDR_ENTRY_ADDED; + if (strcmp(s, "removing") == 0) + return PDR_ENTRY_REMOVING; + return 0; +} |