changeset 1795:47699a09ceb3

Rework the hook system This hook system implementation should be more efficient and a little more flexible.
author Mikael Berthe <mikael@lilotux.net>
date Sat, 20 Mar 2010 14:38:17 +0100
parents 58d1390f28ca
children 5e2db25fdb17
files mcabber/mcabber/api.h mcabber/mcabber/hooks.c mcabber/mcabber/hooks.h mcabber/modules/beep/beep.c
diffstat 4 files changed, 195 insertions(+), 135 deletions(-) [+]
line wrap: on
line diff
--- 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 <mcabber/config.h> // 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;
--- 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
 
--- 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 <glib.h>
 
-#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,
--- 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 */