# HG changeset patch # User Mikael Berthe # Date 1269092297 -3600 # Node ID 47699a09ceb3d2f964c4c72db6a0d692c9be161b # Parent 58d1390f28ca80e0d210a03d7c2b8a7be4c803ce Rework the hook system This hook system implementation should be more efficient and a little more flexible. diff -r 58d1390f28ca -r 47699a09ceb3 mcabber/mcabber/api.h --- a/mcabber/mcabber/api.h Mon Mar 15 17:58:32 2010 +0200 +++ b/mcabber/mcabber/api.h Sat Mar 20 14:38:17 2010 +0100 @@ -3,8 +3,8 @@ #include // For MCABBER_BRANCH -#define MCABBER_API_VERSION 7 -#define MCABBER_API_MIN 4 +#define MCABBER_API_VERSION 8 +#define MCABBER_API_MIN 8 extern const gchar *mcabber_branch; extern const guint mcabber_api_version; diff -r 58d1390f28ca -r 47699a09ceb3 mcabber/mcabber/hooks.c --- a/mcabber/mcabber/hooks.c Mon Mar 15 17:58:32 2010 +0200 +++ b/mcabber/mcabber/hooks.c Sat Mar 20 14:38:17 2010 +0100 @@ -41,39 +41,140 @@ typedef struct { hk_handler_t handler; - guint32 flags; - gpointer userdata; + gint priority; + gpointer userdata; + guint hid; } hook_list_data_t; -static GSList *hk_handler_queue = NULL; +static GHashTable *hk_handler_hash = NULL; -void hk_add_handler(hk_handler_t handler, guint32 flags, gpointer userdata) +// _new_hook_id() +// Return a unique Hook Id +static guint _new_hook_id(void) +{ + static guint hidcounter; + + return ++hidcounter; +} + +// _new_hook_queue(hookname) +// Create a new hash table entry with a GSList pointer for the specified hook +static GSList **_new_hook_queue(const gchar *hookname) { - hook_list_data_t *h = g_new(hook_list_data_t, 1); - h->handler = handler; - h->flags = flags; - h->userdata = userdata; - hk_handler_queue = g_slist_append(hk_handler_queue, h); + GSList **p; + // Create the hash table if needed. + if (!hk_handler_hash) { + hk_handler_hash = g_hash_table_new_full(&g_str_hash, &g_str_equal, + &g_free, &g_free); + if (!hk_handler_hash) { + scr_log_print(LPRINT_LOGNORM, "Couldn't create hook hash table!"); + return NULL; + } + } + + // Add a queue for the requested hook + p = g_new(GSList*, 1); + *p = NULL; + g_hash_table_insert(hk_handler_hash, g_strdup(hookname), p); + + return p; +} + +static gint _hk_compare_prio(hook_list_data_t *a, hook_list_data_t *b) +{ + if (a->priority > b->priority) + return 1; + return 0; } -static gint hk_queue_search_cb(hook_list_data_t *a, hook_list_data_t *b) +// hk_add_handler(handler, hookname, priority, userdata) +// Create a hook handler and a hook hash entry if needed. +// Return the handler id. +guint hk_add_handler(hk_handler_t handler, const gchar *hookname, + gint priority, gpointer userdata) { - if (a->handler == b->handler && a->userdata == b->userdata) + GSList **hqueue = NULL; + hook_list_data_t *h = g_new(hook_list_data_t, 1); + + h->handler = handler; + h->priority = priority; + h->userdata = userdata; + h->hid = _new_hook_id(); + + if (hk_handler_hash) + hqueue = g_hash_table_lookup(hk_handler_hash, hookname); + + if (!hqueue) + hqueue = _new_hook_queue(hookname); + + if (!hqueue) return 0; - else - return 1; + + *hqueue = g_slist_insert_sorted(*hqueue, h, (GCompareFunc)_hk_compare_prio); + + return h->hid; +} + +static gint _hk_queue_search_cb(hook_list_data_t *a, guint *hid) +{ + if (a->hid == *hid) + return 0; + return 1; } -void hk_del_handler(hk_handler_t handler, gpointer userdata) +// hk_del_handler(hookname, hook_id) +// Remove the handler with specified hook id from the hookname queue. +// The hash entry is removed if the queue is empty. +void hk_del_handler(const gchar *hookname, guint hid) { - hook_list_data_t h = { handler, 0, userdata }; - GSList *el = g_slist_find_custom(hk_handler_queue, &h, - (GCompareFunc) hk_queue_search_cb); + GSList **hqueue; + GSList *el; + + if (!hid) + return; + + hqueue = g_hash_table_lookup(hk_handler_hash, hookname); + + if (!hqueue) { + scr_log_print(LPRINT_LOGNORM, "*ERROR*: Couldn't remove hook handler!"); + return; + } + + el = g_slist_find_custom(*hqueue, &hid, + (GCompareFunc)_hk_queue_search_cb); if (el) { g_free(el->data); - hk_handler_queue = g_slist_delete_link(hk_handler_queue, el); + *hqueue = g_slist_delete_link(*hqueue, el); + // Remove hook hash table entry if the hook queue is empty + if (!*hqueue) + g_hash_table_remove(hk_handler_hash, hookname); } } + +// hk_run_handlers(hookname, args) +// Process all hooks for the "hookname" event. +// Note that the processing is interrupted as soon as one of the handlers +// do not return HOOK_HANDLER_RESULT_ALLOW_MORE_HOOKS (i.e. 0). +guint hk_run_handlers(const gchar *hookname, hk_arg_t *args) +{ + GSList **hqueue; + GSList *h; + guint ret = 0; + + if (!hk_handler_hash) + return 0; + + hqueue = g_hash_table_lookup(hk_handler_hash, hookname); + if (!hqueue) + return 0; // Should we use a special code? + + for (h = *hqueue; h; h = g_slist_next(h)) { + hook_list_data_t *data = h->data; + ret = (data->handler)(hookname, args, data->userdata); + if (ret) break; + } + return ret; +} #endif static char *extcmd; @@ -238,26 +339,16 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - // We can use a const array for keys/static values, so modules - // can do fast known to them args check by just comparing pointers... - hk_arg_t args[] = { - { "hook", "hook-message-in" }, - { "jid", bjid }, - { "resource", resname }, - { "message", wmsg }, - { "groupchat", is_groupchat ? "true" : "false" }, - { "urgent", urgent ? "true" : "false" }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MESSAGE_IN) - (data->handler) (HOOK_MESSAGE_IN, args, data->userdata); - h = g_slist_next(h); - } - } + hk_arg_t args[] = { + { "jid", bjid }, + { "resource", resname }, + { "message", wmsg }, + { "groupchat", is_groupchat ? "true" : "false" }, + { "urgent", urgent ? "true" : "false" }, + { NULL, NULL }, + }; + hk_run_handlers(HOOK_MESSAGE_IN, args); + // TODO: check (and use) return value } #endif @@ -342,21 +433,13 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - hk_arg_t args[] = { - { "hook", "hook-message-out" }, - { "jid", bjid }, - { "message", wmsg }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MESSAGE_OUT) - (data->handler) (HOOK_MESSAGE_OUT, args, data->userdata); - h = g_slist_next(h); - } - } + hk_arg_t args[] = { + { "jid", bjid }, + { "message", wmsg }, + { NULL, NULL }, + }; + hk_run_handlers(HOOK_MESSAGE_OUT, args); + // TODO: check (and use) return value } #endif @@ -433,28 +516,20 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - char os[2] = " \0"; - char ns[2] = " \0"; - hk_arg_t args[] = { - { "hook", "hook-status-change" }, - { "jid", bjid }, - { "resource", rn }, - { "old_status", os }, - { "new_status", ns }, - { "message", status_msg ? status_msg : "" }, - { NULL, NULL }, - }; - os[0] = imstatus2char[oldstat]; - ns[0] = imstatus2char[status]; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_STATUS_CHANGE) - (data->handler) (HOOK_STATUS_CHANGE, args, data->userdata); - h = g_slist_next(h); - } - } + char os[2] = " \0"; + char ns[2] = " \0"; + hk_arg_t args[] = { + { "jid", bjid }, + { "resource", rn }, + { "old_status", os }, + { "new_status", ns }, + { "message", status_msg ? status_msg : "" }, + { NULL, NULL }, + }; + os[0] = imstatus2char[oldstat]; + ns[0] = imstatus2char[status]; + + hk_run_handlers(HOOK_STATUS_CHANGE, args); } #endif @@ -471,23 +546,15 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - char ns[2] = " \0"; - hk_arg_t args[] = { - { "hook", "hook-my-status-change" }, - { "new_status", ns }, - { "message", msg ? msg : "" }, - { NULL, NULL }, - }; - ns[0] = imstatus2char[new_status]; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_MY_STATUS_CHANGE) - (data->handler) (HOOK_MY_STATUS_CHANGE, args, data->userdata); - h = g_slist_next(h); - } - } + char ns[2] = " \0"; + hk_arg_t args[] = { + { "new_status", ns }, + { "message", msg ? msg : "" }, + { NULL, NULL }, + }; + ns[0] = imstatus2char[new_status]; + + hk_run_handlers(HOOK_MY_STATUS_CHANGE, args); } #endif @@ -501,19 +568,10 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - hk_arg_t args[] = { - { "hook", "hook-post-connect" }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_POST_CONNECT) - (data->handler) (HOOK_POST_CONNECT, args, data->userdata); - h = g_slist_next(h); - } - } + hk_arg_t args[] = { + { NULL, NULL }, + }; + hk_run_handlers(HOOK_POST_CONNECT, args); } #endif @@ -537,19 +595,10 @@ #ifdef MODULES_ENABLE { - GSList *h = hk_handler_queue; - if (h) { - hk_arg_t args[] = { - { "hook", "hook-pre-disconnect" }, - { NULL, NULL }, - }; - while (h) { - hook_list_data_t *data = h->data; - if (data->flags & HOOK_PRE_DISCONNECT) - (data->handler) (HOOK_PRE_DISCONNECT, args, data->userdata); - h = g_slist_next(h); - } - } + hk_arg_t args[] = { + { NULL, NULL }, + }; + hk_run_handlers(HOOK_PRE_DISCONNECT, args); } #endif diff -r 58d1390f28ca -r 47699a09ceb3 mcabber/mcabber/hooks.h --- a/mcabber/mcabber/hooks.h Mon Mar 15 17:58:32 2010 +0200 +++ b/mcabber/mcabber/hooks.h Sat Mar 20 14:38:17 2010 +0100 @@ -13,23 +13,32 @@ #ifdef MODULES_ENABLE #include -#define HOOK_MESSAGE_IN ( 0x00000001 ) -#define HOOK_MESSAGE_OUT ( 0x00000002 ) -#define HOOK_STATUS_CHANGE ( 0x00000004 ) -#define HOOK_MY_STATUS_CHANGE ( 0x00000008 ) -#define HOOK_POST_CONNECT ( 0x00000010 ) -#define HOOK_PRE_DISCONNECT ( 0x00000020 ) -#define HOOK_INTERNAL ( HOOK_POST_CONNECT | HOOK_PRE_DISCONNECT ) +// Core hooks +#define HOOK_MESSAGE_IN "hook-message-in" +#define HOOK_MESSAGE_OUT "hook-message-out" +#define HOOK_STATUS_CHANGE "hook-status-change" +#define HOOK_MY_STATUS_CHANGE "hook-my-status-change" +#define HOOK_POST_CONNECT "hook-post-connect" +#define HOOK_PRE_DISCONNECT "hook-pre-disconnect" + +typedef enum { + HOOK_HANDLER_RESULT_ALLOW_MORE_HOOKS = 0, + HOOK_HANDLER_RESULT_NO_MORE_HOOK, + HOOK_HANDLER_RESULT_NO_MORE_HOOK_DROP_DATA, +} hk_handler_result; typedef struct { const char *name; const char *value; } hk_arg_t; -typedef void (*hk_handler_t) (guint32 flags, hk_arg_t *args, gpointer userdata); +typedef guint (*hk_handler_t) (const gchar *hookname, hk_arg_t *args, + gpointer userdata); -void hk_add_handler(hk_handler_t handler, guint32 flags, gpointer userdata); -void hk_del_handler(hk_handler_t handler, gpointer userdata); +guint hk_add_handler(hk_handler_t handler, const gchar *hookname, + gint priority, gpointer userdata); +void hk_del_handler(const gchar *hookname, guint hid); +guint hk_run_handlers(const gchar *hookname, hk_arg_t *args); #endif void hk_message_in(const char *bjid, const char *resname, diff -r 58d1390f28ca -r 47699a09ceb3 mcabber/modules/beep/beep.c --- a/mcabber/modules/beep/beep.c Mon Mar 15 17:58:32 2010 +0200 +++ b/mcabber/modules/beep/beep.c Sat Mar 20 14:38:17 2010 +0100 @@ -45,14 +45,15 @@ }; static guint beep_cid = 0; +static guint beep_hid; /* Event handler */ -static void beep_hh (guint32 hid, hk_arg_t *args, gpointer userdata) +static guint beep_hh (const gchar *hookname, hk_arg_t *args, gpointer userdata) { /* Check if beeping is enabled */ if (settings_opt_get_int ("beep_enable")) - /* *BEEP*! */ - scr_beep (); + scr_beep (); /* *BEEP*! */ + return HOOK_HANDLER_RESULT_ALLOW_MORE_HOOKS; } /* beep command handler */ @@ -97,14 +98,15 @@ /* Add handler * We are only interested in incoming message events */ - hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL); + beep_hid = hk_add_handler (beep_hh, HOOK_MESSAGE_IN, + G_PRIORITY_DEFAULT_IDLE, NULL); } /* Deinitialization */ static void beep_uninit (void) { /* Unregister event handler */ - hk_del_handler (beep_hh, NULL); + hk_del_handler (HOOK_MESSAGE_IN, beep_hid); /* Unregister command */ cmd_del ("beep"); /* Give back completion handle */