]> jfr.im git - irc/quakenet/newserv.git/commitdiff
nickwatch: Make sure this works with large bursts.
authorGunnar Beutner <redacted>
Wed, 14 Aug 2013 09:51:32 +0000 (11:51 +0200)
committerGunnar Beutner <redacted>
Wed, 14 Aug 2013 09:51:32 +0000 (11:51 +0200)
newsearch/newsearch.c
newsearch/newsearch.h
newsearch/newsearch_ast.c
nickwatch/nickwatch.c

index 0b08bdfb73daa5b7c574e1149f39b6a5646fc66a..ccde9c57b2670c7f10e18adaa10a9f7df5a5d19f 100644 (file)
@@ -1,6 +1,7 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
+#include <assert.h>
 
 #include "../irc/irc_config.h"
 #include "../lib/irc_string.h"
 
 #include "../irc/irc_config.h"
 #include "../lib/irc_string.h"
@@ -457,7 +458,7 @@ int parseopts(int cargc, char **cargv, int *arg, int *limit, void **subset, void
   return CMD_OK;
 }
 
   return CMD_OK;
 }
 
-void newsearch_ctxinit(searchCtx *ctx, searchParseFunc searchfn, replyFunc replyfn, wallFunc wallfn, void *arg, searchCmd *cmd, nick *np, void *displayfn, int limit, void *target) {
+void newsearch_ctxinit(searchCtx *ctx, searchParseFunc searchfn, replyFunc replyfn, wallFunc wallfn, void *arg, searchCmd *cmd, nick *np, void *displayfn, int limit, array *targets) {
   memset(ctx, 0, sizeof(searchCtx));
   
   ctx->reply = replyfn;
   memset(ctx, 0, sizeof(searchCtx));
   
   ctx->reply = replyfn;
@@ -467,7 +468,7 @@ void newsearch_ctxinit(searchCtx *ctx, searchParseFunc searchfn, replyFunc reply
   ctx->searchcmd = cmd;
   ctx->sender = np;
   ctx->limit = limit;
   ctx->searchcmd = cmd;
   ctx->sender = np;
   ctx->limit = limit;
-  ctx->target = target;
+  ctx->targets = targets;
   ctx->displayfn = displayfn;
 }
 
   ctx->displayfn = displayfn;
 }
 
@@ -516,7 +517,7 @@ int do_nicksearch(void *source, int cargc, char **cargv) {
 }
 
 void nicksearch_exe(struct searchNode *search, searchCtx *ctx) {
 }
 
 void nicksearch_exe(struct searchNode *search, searchCtx *ctx) {
-  int i, j;
+  int i, j, k;
   int matches = 0;
   unsigned int cmarker;
   unsigned int tchans=0,uchans=0;
   int matches = 0;
   unsigned int cmarker;
   unsigned int tchans=0,uchans=0;
@@ -533,7 +534,13 @@ void nicksearch_exe(struct searchNode *search, searchCtx *ctx) {
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<NICKHASHSIZE;i++) {
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<NICKHASHSIZE;i++) {
-    for (np=ctx->target ? ctx->target : nicktable[i];np;np=np->next) {
+    for (np=nicktable[i], k = 0;ctx->targets ? (k < ctx->targets->cursi) : (np != NULL);np=np->next, k++) {
+      if (ctx->targets) {
+        np = ((nick **)ctx->targets->content)[k];
+        if (!np)
+          break;
+      }
+
       if ((search->exe)(ctx, search, np)) {
         /* Add total channels */
         tchans += np->channels->cursi;
       if ((search->exe)(ctx, search, np)) {
         /* Add total channels */
         tchans += np->channels->cursi;
@@ -554,13 +561,12 @@ void nicksearch_exe(struct searchNode *search, searchCtx *ctx) {
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
       }
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
       }
-
-      if (ctx->target)
-        goto done;
     }
     }
+
+    if (ctx->targets)
+      break;
   }
 
   }
 
-done:
   ctx->reply(sender,"--- End of list: %d matches; users were on %u channels (%u unique, %.1f average clones)", 
                 matches, tchans, uchans, (float)tchans/uchans);
 }
   ctx->reply(sender,"--- End of list: %d matches; users were on %u channels (%u unique, %.1f average clones)", 
                 matches, tchans, uchans, (float)tchans/uchans);
 }
@@ -617,18 +623,16 @@ void whowassearch_exe(struct searchNode *search, searchCtx *ctx) {
   WhowasDisplayFunc display = ctx->displayfn;
   int limit = ctx->limit;
 
   WhowasDisplayFunc display = ctx->displayfn;
   int limit = ctx->limit;
 
+  assert(!ctx->targets);
+
   /* The top-level node needs to return a BOOL */
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
 
   for (i = whowasoffset; i < whowasoffset + WW_MAXENTRIES; i++) {
   /* The top-level node needs to return a BOOL */
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
 
   for (i = whowasoffset; i < whowasoffset + WW_MAXENTRIES; i++) {
-    if (ctx->target) {
-      ww = ctx->target;
-    } else {
-      ww = &whowasrecs[i % WW_MAXENTRIES];
+    ww = &whowasrecs[i % WW_MAXENTRIES];
 
 
-      if (ww->type == WHOWAS_UNUSED)
-        continue;
-    }
+    if (ww->type == WHOWAS_UNUSED)
+      continue;
 
     /* Note: We're passing the nick to the filter function. The original
      * whowas record is in the nick's ->next field. */
 
     /* Note: We're passing the nick to the filter function. The original
      * whowas record is in the nick's ->next field. */
@@ -640,9 +644,6 @@ void whowassearch_exe(struct searchNode *search, searchCtx *ctx) {
         ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
       matches++;
     }
         ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
       matches++;
     }
-
-    if (ctx->target)
-      break;
   }
 
   ctx->reply(sender,"--- End of list: %d matches", matches);
   }
 
   ctx->reply(sender,"--- End of list: %d matches", matches);
@@ -700,25 +701,23 @@ void chansearch_exe(struct searchNode *search, searchCtx *ctx) {
   senderNSExtern = sender;
   ChanDisplayFunc display = ctx->displayfn;
   int limit = ctx->limit;
   senderNSExtern = sender;
   ChanDisplayFunc display = ctx->displayfn;
   int limit = ctx->limit;
-  
+
+  assert(!ctx->targets);  
+
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<CHANNELHASHSIZE;i++) {
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<CHANNELHASHSIZE;i++) {
-    for (cip=ctx->target ? ctx->target : chantable[i];cip;cip=cip->next) {
+    for (cip=chantable[i];cip;cip=cip->next) {
       if ((search->exe)(ctx, search, cip)) {
        if (matches<limit)
          display(ctx, sender, cip);
        if (matches==limit)
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
       if ((search->exe)(ctx, search, cip)) {
        if (matches<limit)
          display(ctx, sender, cip);
        if (matches==limit)
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
-
-        if (ctx->target)
-          goto done;
       }
     }
   }
 
       }
     }
   }
 
-done:
   ctx->reply(sender,"--- End of list: %d matches", matches);
 }
 
   ctx->reply(sender,"--- End of list: %d matches", matches);
 }
 
@@ -775,10 +774,12 @@ void usersearch_exe(struct searchNode *search, searchCtx *ctx) {
   UserDisplayFunc display = ctx->displayfn;
   senderNSExtern = sender;
 
   UserDisplayFunc display = ctx->displayfn;
   senderNSExtern = sender;
 
+  assert(!ctx->targets);
+
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<AUTHNAMEHASHSIZE;i++) {
   search=coerceNode(ctx, search, RETURNTYPE_BOOL);
   
   for (i=0;i<AUTHNAMEHASHSIZE;i++) {
-    for (aup=ctx->target ? ctx->target : authnametable[i];aup;aup=aup->next) {
+    for (aup=authnametable[i];aup;aup=aup->next) {
       if ((search->exe)(ctx, search, aup)) {
        if (matches<limit)
          display(ctx, sender, aup);
       if ((search->exe)(ctx, search, aup)) {
        if (matches<limit)
          display(ctx, sender, aup);
@@ -786,13 +787,9 @@ void usersearch_exe(struct searchNode *search, searchCtx *ctx) {
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
       }
          ctx->reply(sender, "--- More than %d matches, skipping the rest",limit);
        matches++;
       }
-
-      if (ctx->target)
-        goto done;
     }
   }
 
     }
   }
 
-done:
   ctx->reply(sender,"--- End of list: %d matches", matches);
 }
 
   ctx->reply(sender,"--- End of list: %d matches", matches);
 }
 
index e8d669364bc43fa6f92a927b36389f337a9a5d0d..3fd6b781f2b42396843bd90d2c3debc801623f52 100644 (file)
@@ -92,7 +92,7 @@ typedef struct searchCtx {
   struct searchCmd *searchcmd;
   nick *sender;
   int limit;
   struct searchCmd *searchcmd;
   nick *sender;
   int limit;
-  void *target;
+  array *targets;
   void *displayfn;
 } searchCtx;
 
   void *displayfn;
 } searchCtx;
 
@@ -222,7 +222,7 @@ struct searchVariable *var_register(searchCtx *ctx, char *arg, int type);
 searchNode *var_get(searchCtx *ctx, char *arg);
 void var_setstr(struct searchVariable *v, char *data);
 
 searchNode *var_get(searchCtx *ctx, char *arg);
 void var_setstr(struct searchVariable *v, char *data);
 
-void newsearch_ctxinit(searchCtx *ctx, searchParseFunc searchfn, replyFunc replyfn, wallFunc wallfn, void *arg, searchCmd *cmd, nick *sender, void *displayfn, int limit, void *target);
+void newsearch_ctxinit(searchCtx *ctx, searchParseFunc searchfn, replyFunc replyfn, wallFunc wallfn, void *arg, searchCmd *cmd, nick *sender, void *displayfn, int limit, array *targets);
 
 /* AST functions */
 
 
 /* AST functions */
 
@@ -273,16 +273,16 @@ typedef struct searchASTCache {
 
 searchNode *search_astparse(searchCtx *, char *);
 
 
 searchNode *search_astparse(searchCtx *, char *);
 
-int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, NickDisplayFunc display, HeaderFunc header, void *headerarg, int limit, nick *);
-int ast_chansearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, ChanDisplayFunc display, HeaderFunc header, void *headerarg, int limit, chanindex *);
-int ast_usersearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, UserDisplayFunc display, HeaderFunc header, void *headerarg, int limit, authname *);
-int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, WhowasDisplayFunc display, HeaderFunc header, void *headerarg, int limit, whowas *);
+int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, NickDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *);
+int ast_chansearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, ChanDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *);
+int ast_usersearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, UserDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *);
+int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, WhowasDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *);
 
 char *ast_printtree(char *buf, size_t bufsize, searchASTExpr *expr, searchCmd *cmd);
 
 int parseopts(int cargc, char **cargv, int *arg, int *limit, void **subset, void *display, CommandTree *sl, replyFunc reply, void *sender);
 
 
 char *ast_printtree(char *buf, size_t bufsize, searchASTExpr *expr, searchCmd *cmd);
 
 int parseopts(int cargc, char **cargv, int *arg, int *limit, void **subset, void *display, CommandTree *sl, replyFunc reply, void *sender);
 
-typedef int (*ASTFunc)(searchASTExpr *, replyFunc, void *, wallFunc, void *, HeaderFunc, void *, int limit, void *target);
+typedef int (*ASTFunc)(searchASTExpr *, replyFunc, void *, wallFunc, void *, HeaderFunc, void *, int limit, array *targets);
 
 /* erk */
 extern searchList *globalterms;
 
 /* erk */
 extern searchList *globalterms;
index 7eefed0082985b6a9469a07264c79826b1d0007b..c872579e6e38d301c8c06a2403e77a90337bda9f 100644 (file)
@@ -127,7 +127,7 @@ searchNode *search_astparse(searchCtx *ctx, char *loc) {
   }
 }
 
   }
 }
 
-int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, NickDisplayFunc display, HeaderFunc header, void *headerarg, int limit, nick *target) {
+int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, NickDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *targets) {
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
@@ -136,17 +136,20 @@ int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
-  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_nicksearch, sender, display, limit, target);
+  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_nicksearch, sender, display, limit, targets);
 
   buf[0] = '\0';
 
   buf[0] = '\0';
-  reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_nicksearch));
+  if (!targets)
+    reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_nicksearch));
   search = ctx.parser(&ctx, (char *)tree);
   if(!search) {
   search = ctx.parser(&ctx, (char *)tree);
   if(!search) {
-    reply(sender, "Parse error: %s", parseError);
+    if (!targets)
+      reply(sender, "Parse error: %s", parseError);
     return CMD_ERROR;
   }
 
     return CMD_ERROR;
   }
 
-  reply(sender, "Executing...");
+  if (!targets)
+    reply(sender, "Executing...");
   if(header)  
     header(sender, headerarg);
   nicksearch_exe(search, &ctx);
   if(header)  
     header(sender, headerarg);
   nicksearch_exe(search, &ctx);
@@ -156,7 +159,7 @@ int ast_nicksearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc
   return CMD_OK;
 }
 
   return CMD_OK;
 }
 
-int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, WhowasDisplayFunc display, HeaderFunc header, void *headerarg, int limit, whowas *target) {
+int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, WhowasDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *targets) {
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
@@ -165,7 +168,7 @@ int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFun
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
-  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_whowassearch, sender, display, limit, target);
+  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_whowassearch, sender, display, limit, targets);
 
   buf[0] = '\0';
   reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_whowassearch));
 
   buf[0] = '\0';
   reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_whowassearch));
@@ -185,13 +188,13 @@ int ast_whowassearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFun
   return CMD_OK;
 }
 
   return CMD_OK;
 }
 
-int ast_chansearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, ChanDisplayFunc display, HeaderFunc header, void *headerarg, int limit, chanindex *target) {
+int ast_chansearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, ChanDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *targets) {
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
   char buf[1024];
 
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
   char buf[1024];
 
-  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_chansearch, sender, display, limit, target);
+  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_chansearch, sender, display, limit, targets);
 
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
@@ -214,7 +217,7 @@ int ast_chansearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc
   return CMD_OK;
 }
 
   return CMD_OK;
 }
 
-int ast_usersearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, UserDisplayFunc display, HeaderFunc header, void *headerarg, int limit, authname *target) {
+int ast_usersearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc wall, UserDisplayFunc display, HeaderFunc header, void *headerarg, int limit, array *targets) {
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
   searchCtx ctx;
   searchASTCache cache;
   searchNode *search;
@@ -223,7 +226,7 @@ int ast_usersearch(searchASTExpr *tree, replyFunc reply, void *sender, wallFunc
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
   memset(&cache, 0, sizeof(cache));
   cache.tree = tree;
 
-  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_usersearch, sender, display, limit, target);
+  newsearch_ctxinit(&ctx, search_astparse, reply, wall, &cache, reg_usersearch, sender, display, limit, targets);
 
   buf[0] = '\0';
   reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_usersearch));
 
   buf[0] = '\0';
   reply(sender, "Parsing: %s", ast_printtree(buf, sizeof(buf), tree, reg_usersearch));
index d53bfb15ec47548c98d6acce393d9d2a73852444..c3bfa110b96efab6fe4d1cc1326e35f000b5334f 100644 (file)
@@ -19,79 +19,112 @@ typedef struct nickwatch {
 
 typedef struct nickwatchevent {
   char description[128];
 
 typedef struct nickwatchevent {
   char description[128];
-  long numeric;
+  struct nickwatchevent *next;
 } nickwatchevent;
 
 static nickwatch *nickwatches;
 static int nextnickwatch = 1;
 } nickwatchevent;
 
 static nickwatch *nickwatches;
 static int nextnickwatch = 1;
+static int nickwatchext;
 
 static void nw_dummyreply(nick *np, char *format, ...) { }
 
 static void nw_dummyreply(nick *np, char *format, ...) { }
-
 static void nw_dummywall(int level, char *format, ...) { }
 
 static nickwatch *nw_currentwatch;
 static void nw_dummywall(int level, char *format, ...) { }
 
 static nickwatch *nw_currentwatch;
-static nickwatchevent *nw_currentevent;
+static array nw_pendingnicks;
 
 static void nw_printnick(searchCtx *ctx, nick *sender, nick *np) {
   char hostbuf[HOSTLEN+NICKLEN+USERLEN+4];
 
 static void nw_printnick(searchCtx *ctx, nick *sender, nick *np) {
   char hostbuf[HOSTLEN+NICKLEN+USERLEN+4];
+  char events[512];
+  nickwatchevent *nwe = np->exts[nickwatchext];
+  int len;
 
   nw_currentwatch->hits++;
 
 
   nw_currentwatch->hits++;
 
-  controlwall(NO_OPER, NL_HITS, "nickwatch(#%d, %s): %s [%s] (%s) (%s)", nw_currentwatch->id, nw_currentevent->description, visiblehostmask(np,hostbuf),
+  events[0] = '\0';
+  len = 0;
+
+  for (nwe = np->exts[nickwatchext]; nwe; nwe = nwe->next) {
+    if (len > 0)
+      len += snprintf(events + len, sizeof(events) - len, ", ");
+
+    len += snprintf(events + len, sizeof(events) - len, "%s", nwe->description);
+  }
+
+  controlwall(NO_OPER, NL_HITS, "nickwatch(#%d, %s): %s [%s] (%s) (%s)", nw_currentwatch->id, events, visiblehostmask(np,hostbuf),
                IPtostr(np->ipaddress), printflags(np->umodes, umodeflags), np->realname->name->content);
 }
 
                IPtostr(np->ipaddress), printflags(np->umodes, umodeflags), np->realname->name->content);
 }
 
-static nickwatchevent *nwe_new(nick *np, const char *format, ...) {
+static void nwe_enqueue(nick *np, const char *format, ...) {
   nickwatchevent *nwe;
   va_list va;
   nickwatchevent *nwe;
   va_list va;
+  int slot;
 
   nwe = malloc(sizeof(nickwatchevent));
 
   nwe = malloc(sizeof(nickwatchevent));
-  nwe->numeric = np->numeric;
 
   va_start(va, format);
   vsnprintf(nwe->description, sizeof(nwe->description), format, va);
   va_end(va);
 
 
   va_start(va, format);
   vsnprintf(nwe->description, sizeof(nwe->description), format, va);
   va_end(va);
 
-  return nwe;
-}
+  nwe->next = np->exts[nickwatchext];
+  np->exts[nickwatchext] = nwe;
 
 
-static void nwe_free(nickwatchevent *nwe) {
-  free(nwe);
+  slot = array_getfreeslot(&nw_pendingnicks);
+  ((nick **)nw_pendingnicks.content)[slot] = np;
 }
 
 }
 
-static void nw_sched_processevent(void *arg) {
-  nickwatchevent *nwe = arg;
-  nick *np;
-  nickwatch *nw;
-
-  np = getnickbynumeric(nwe->numeric);
+static void nwe_clear(nick *np) {
+  nickwatchevent *nwe, *next;
 
 
-  if (!np) {
-    nwe_free(nwe);
-    return;
+  for (nwe = np->exts[nickwatchext]; nwe; nwe = next) {
+    next = nwe->next;
+    free(nwe);
   }
   }
-  nw_currentevent = nwe;
+
+  np->exts[nickwatchext] = NULL;
+}
+
+static void nw_sched_processevents(void *arg) {
+  nickwatch *nw;
+  int i;
+  nick *np;
 
   for (nw = nickwatches; nw; nw = nw->next) {
     nw_currentwatch = nw;
 
   for (nw = nickwatches; nw; nw = nw->next) {
     nw_currentwatch = nw;
-    ast_nicksearch(nw->tree->root, &nw_dummyreply, mynick, &nw_dummywall, &nw_printnick, NULL, NULL, 10, np);
+    ast_nicksearch(nw->tree->root, &nw_dummyreply, mynick, &nw_dummywall, &nw_printnick, NULL, NULL, 10, &nw_pendingnicks);
   }
 
   }
 
-  nwe_free(nwe);
+  for (i = 0; i < nw_pendingnicks.cursi; i++) {
+    np = ((nick **)nw_pendingnicks.content)[i];
+    nwe_clear(np);
+  }
+
+  array_free(&nw_pendingnicks);
+  array_init(&nw_pendingnicks, sizeof(nick *));
 }
 
 static void nw_hook_newnick(int hooknum, void *arg) {
   nick *np = arg;
 }
 
 static void nw_hook_newnick(int hooknum, void *arg) {
   nick *np = arg;
-  nickwatchevent *nwe = nwe_new(np, "new user");
-  scheduleoneshot(0, nw_sched_processevent, nwe);
+  nwe_enqueue(np, "new user");
+}
+
+static void nw_hook_lostnick(int hooknum, void *arg) {
+  nick *np = arg;
+  int i;
+
+  nwe_clear(np);
+
+  for (i = 0; i < nw_pendingnicks.cursi;)
+    if (((nick **)nw_pendingnicks.content)[i] == np)
+      array_delslot(&nw_pendingnicks, i);
+    else
+      i++;
 }
 
 static void nw_hook_rename(int hooknum, void *arg) {
   void **args = arg;
   nick *np = args[0];
   char *oldnick = args[1];
 }
 
 static void nw_hook_rename(int hooknum, void *arg) {
   void **args = arg;
   nick *np = args[0];
   char *oldnick = args[1];
-  nickwatchevent *nwe = nwe_new(np, "renamed from %s", oldnick);
-  scheduleoneshot(0, nw_sched_processevent, nwe);
+  nwe_enqueue(np, "renamed from %s", oldnick);
 }
 
 static void nw_hook_umodechange(int hooknum, void *arg) {
 }
 
 static void nw_hook_umodechange(int hooknum, void *arg) {
@@ -100,24 +133,21 @@ static void nw_hook_umodechange(int hooknum, void *arg) {
   flag_t oldmodes = (uintptr_t)args[1];
   char buf[64];
   strncpy(buf, printflags(np->umodes, umodeflags), sizeof(buf));
   flag_t oldmodes = (uintptr_t)args[1];
   char buf[64];
   strncpy(buf, printflags(np->umodes, umodeflags), sizeof(buf));
-  nickwatchevent *nwe = nwe_new(np, "umodes %s -> %s", printflags(oldmodes, umodeflags), buf);
-  scheduleoneshot(0, nw_sched_processevent, nwe);
+  nwe_enqueue(np, "umodes %s -> %s", printflags(oldmodes, umodeflags), buf);
 }
 
 static void nw_hook_message(int hooknum, void *arg) {
   void **args = arg;
   nick *np = args[0];
   int isnotice = (uintptr_t)args[2];
 }
 
 static void nw_hook_message(int hooknum, void *arg) {
   void **args = arg;
   nick *np = args[0];
   int isnotice = (uintptr_t)args[2];
-  nickwatchevent *nwe = nwe_new(np, isnotice ? "notice" : "message");
-  scheduleoneshot(0, nw_sched_processevent, nwe);
+  nwe_enqueue(np, isnotice ? "notice" : "message");
 }
 
 static void nw_hook_joinchannel(int hooknum, void *arg) {
   void **args = arg;
   channel *cp = args[0];
   nick *np = args[1];
 }
 
 static void nw_hook_joinchannel(int hooknum, void *arg) {
   void **args = arg;
   channel *cp = args[0];
   nick *np = args[1];
-  nickwatchevent *nwe = nwe_new(np, "join channel %s", cp->index->name->content);
-  scheduleoneshot(0, nw_sched_processevent, nwe);
+  nwe_enqueue(np, "join channel %s", cp->index->name->content);
 }
 
 static int nw_cmd_nickwatch(void *source, int cargc, char **cargv) {
 }
 
 static int nw_cmd_nickwatch(void *source, int cargc, char **cargv) {
@@ -190,17 +220,41 @@ static int nw_cmd_nickwatches(void *source, int cargc, char **cargv) {
   return CMD_OK;
 }
 
   return CMD_OK;
 }
 
+static int nw_cmd_nickburst(void *source, int cargc, char **cargv) {
+  nick *sender = source;
+  int i;
+  char *pargv[1];
+
+  pargv[0] = "(and (match (nick) \"*gunnar*\"))";
+
+  for (i = 0; i < 100; i++)
+    nw_cmd_nickwatch(source, 1, pargv);
+
+  for (i = 0; i < 50000; i++)
+    nwe_enqueue(sender, "new user");
+
+  return CMD_OK;
+}
+
 void _init(void) {
 void _init(void) {
+  nickwatchext = registernickext("nickwatch");
+
+  array_init(&nw_pendingnicks, sizeof(nick *));
+
   registercontrolhelpcmd("nickwatch", NO_OPER, 1, &nw_cmd_nickwatch, "Usage: nickwatch <nicksearch term>\nAdds a nickwatch entry.");
   registercontrolhelpcmd("nickunwatch", NO_OPER, 1, &nw_cmd_nickunwatch, "Usage: nickunwatch <#id>\nRemoves a nickwatch entry.");
   registercontrolhelpcmd("nickwatches", NO_OPER, 0, &nw_cmd_nickwatches, "Usage: nickwatches\nLists nickwatches.");
   registercontrolhelpcmd("nickwatch", NO_OPER, 1, &nw_cmd_nickwatch, "Usage: nickwatch <nicksearch term>\nAdds a nickwatch entry.");
   registercontrolhelpcmd("nickunwatch", NO_OPER, 1, &nw_cmd_nickunwatch, "Usage: nickunwatch <#id>\nRemoves a nickwatch entry.");
   registercontrolhelpcmd("nickwatches", NO_OPER, 0, &nw_cmd_nickwatches, "Usage: nickwatches\nLists nickwatches.");
+  registercontrolhelpcmd("nickburst", NO_OPER, 0, &nw_cmd_nickburst, "Usage: nickwatches\nSimulates a burst.");
 
   registerhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
 
   registerhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
+  registerhook(HOOK_NICK_LOSTNICK, &nw_hook_lostnick);
   registerhook(HOOK_NICK_RENAME, &nw_hook_rename);
   registerhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
   registerhook(HOOK_NICK_MESSAGE, &nw_hook_message);
   registerhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
   registerhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
   registerhook(HOOK_NICK_RENAME, &nw_hook_rename);
   registerhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
   registerhook(HOOK_NICK_MESSAGE, &nw_hook_message);
   registerhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
   registerhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
+
+  schedulerecurring(time(NULL) + 5, 0, 1, &nw_sched_processevents, NULL);
 }
 
 void _fini(void) {
 }
 
 void _fini(void) {
@@ -209,14 +263,25 @@ void _fini(void) {
   deregistercontrolcmd("nickwatch", &nw_cmd_nickwatch);
   deregistercontrolcmd("nickunwatch", &nw_cmd_nickunwatch);
   deregistercontrolcmd("nickwatches", &nw_cmd_nickwatches);
   deregistercontrolcmd("nickwatch", &nw_cmd_nickwatch);
   deregistercontrolcmd("nickunwatch", &nw_cmd_nickunwatch);
   deregistercontrolcmd("nickwatches", &nw_cmd_nickwatches);
+  deregistercontrolcmd("nickburst", &nw_cmd_nickburst);
 
   deregisterhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
 
   deregisterhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
+  deregisterhook(HOOK_NICK_LOSTNICK, &nw_hook_lostnick);
   deregisterhook(HOOK_NICK_RENAME, &nw_hook_rename);
   deregisterhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
   deregisterhook(HOOK_NICK_MESSAGE, &nw_hook_message);
   deregisterhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
   deregisterhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
 
   deregisterhook(HOOK_NICK_RENAME, &nw_hook_rename);
   deregisterhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
   deregisterhook(HOOK_NICK_MESSAGE, &nw_hook_message);
   deregisterhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
   deregisterhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
 
+  deleteallschedules(&nw_sched_processevents);
+
+  /* Process all pending events */
+  nw_sched_processevents(NULL);
+
+  array_free(&nw_pendingnicks);
+
+  releasenickext(nickwatchext);
+
   for (nw = nickwatches; nw; nw = next) {
     next = nw->next;
 
   for (nw = nickwatches; nw; nw = next) {
     next = nw->next;