[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Nmh-workers] Threads
From: |
Eric Gillespie |
Subject: |
Re: [Nmh-workers] Threads |
Date: |
Sun, 07 Apr 2013 23:57:13 -0700 |
I've attached my own pickthread that I've used for the last
decade (!). Actually, since I started using MH-E a few years
back, I often use its own threading, but I still use good old
scan and show, and so also this.
The code is probably crap since I was a stupid(er) kid back then,
but maybe it's a decent start. Maybe not.
I haven't even tried to read it; it appears I didn't bother to
comment very much, sigh. At first I tried to drop it into nmh
from git, but apparently that's diverged pretty wildly, at least
around m_getfld (hurrah!), so instead this patch is against 1.5,
which only required adding new int args to decode_rfc2047
and LocalName.
I usually use it like this:
pick `pickthread cur-last` -seq thread; scan thread
Sometimes things like 'last:200' rather than 'cur-last'.
That could be just an out-dated habit though; I wouldn't be
surprised if everything is fast enough now (especially with
flash-based "hard drive") if my usage of last:200 and such with
all pick commands just isn't useful anymore.
Anyway, here it is, for what it's worth.
diff --git a/Makefile.am b/Makefile.am
index 9e4e31a..fbda6de 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -116,7 +116,7 @@ bin_PROGRAMS = uip/ali uip/anno uip/burst uip/comp uip/dist
uip/flist \
uip/mhpath uip/mhshow uip/mhstore uip/msgchk uip/msh uip/new \
uip/packf uip/pick uip/prompter uip/refile uip/repl uip/rmf \
uip/rmm uip/scan uip/send uip/show uip/sortm uip/whatnow \
- uip/whom
+ uip/whom uip/pickthread
bin_SCRIPTS = etc/sendfiles
@@ -142,7 +142,7 @@ noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h
h/dropsbr.h \
h/mh.h h/mhcachesbr.h h/mhparse.h h/mime.h h/msh.h \
h/mts.h h/nmh.h h/picksbr.h h/popsbr.h h/prototypes.h \
h/rcvmail.h h/scansbr.h h/signals.h h/tws.h h/utils.h \
- h/vmhsbr.h mts/smtp/smtp.h
+ h/vmhsbr.h mts/smtp/smtp.h h/thread.h
##
## Extra files we need to install in various places
@@ -308,6 +308,8 @@ uip_packf_SOURCES = uip/packf.c uip/dropsbr.c
uip_pick_SOURCES = uip/pick.c uip/picksbr.c
+uip_pickthread_SOURCES = uip/pickthread.c
+
uip_prompter_SOURCES = uip/prompter.c
uip_refile_SOURCES = uip/refile.c
@@ -484,7 +486,7 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c
sbr/atooi.c sbr/brkstring.c \
sbr/seq_getnum.c sbr/seq_list.c sbr/seq_nameok.c \
sbr/seq_print.c sbr/seq_read.c sbr/seq_save.c \
sbr/seq_setcur.c sbr/seq_setprev.c sbr/seq_setunseen.c \
- sbr/showfile.c sbr/signals.c sbr/smatch.c \
+ sbr/showfile.c sbr/signals.c sbr/smatch.c sbr/thread.c \
sbr/snprintb.c sbr/ssequal.c sbr/strcasecmp.c \
sbr/strindex.c sbr/trimcpy.c sbr/uprf.c sbr/vfgets.c \
sbr/fmt_def.c sbr/m_msgdef.c sbr/mf.c sbr/utils.c \
diff --git a/h/thread.h b/h/thread.h
new file mode 100644
index 0000000..3155208
--- /dev/null
+++ b/h/thread.h
@@ -0,0 +1,110 @@
+/* thread.h -- implementation of jwz's threading algorithm
+ * (http://www.jwz.org/doc/threading.html)
+ *
+ * $Id: thread.h,v 1.1 2002/09/23 03:34:02 epg Exp $
+ */
+
+#include <sys/types.h>
+
+typedef struct mhthread_container_list mhthread_container_list_t;
+typedef struct mhthread_container mhthread_container_t;
+typedef struct mhthread_id_table mhthread_id_table_t;
+
+struct mhthread_container {
+ int msgnum; /* 0 indicates no message. */
+ mhthread_container_t *parent;
+ mhthread_container_list_t *children;
+};
+
+struct mhthread_container_list {
+ mhthread_container_t *container;
+ mhthread_container_list_t *next;
+};
+
+struct mhthread_id_table {
+ char *id;
+ mhthread_container_t *container;
+ mhthread_id_table_t *next;
+};
+
+/******************************************************************************
+ * Container
+ */
+
+mhthread_container_list_t *
+mhthread_container_list_new(void);
+
+mhthread_container_list_t *
+mhthread_container_list_prepend(mhthread_container_list_t *list,
+ mhthread_container_t *container);
+
+mhthread_container_list_t *
+mhthread_container_list_remove(mhthread_container_list_t *list,
+ mhthread_container_t *container);
+
+mhthread_container_list_t *
+mhthread_container_list_extend(mhthread_container_list_t *list,
+ mhthread_container_list_t *extend);
+
+size_t
+mhthread_container_list_length(mhthread_container_list_t *list);
+
+mhthread_container_list_t *
+mhthread_container_list_copy(mhthread_container_list_t *list);
+
+mhthread_container_t *
+mhthread_container_new(void);
+
+mhthread_container_t *
+mhthread_container_add_child(mhthread_container_t *container,
+ mhthread_container_t *child);
+
+mhthread_container_t *
+mhthread_container_remove_child(mhthread_container_t *container,
+ mhthread_container_t *child);
+
+mhthread_container_t *
+mhthread_container_find_descendant(mhthread_container_t *haystack,
+ mhthread_container_t *needle);
+
+mhthread_container_t *
+mhthread_container_find_descendant_by_number(mhthread_container_t *haystack,
+ int needle);
+
+mhthread_container_list_t *
+mhthread_container_prune(mhthread_container_t *container);
+
+/******************************************************************************
+ * id_table
+ */
+
+mhthread_container_t *
+mhthread_id_table_lookup(mhthread_id_table_t *id_table, char *message_id);
+
+mhthread_id_table_t *
+mhthread_id_table_add(mhthread_id_table_t *id_table,
+ mhthread_container_t *container, char *message_id);
+
+/******************************************************************************
+ * stuff
+ */
+
+char *
+mhthread_extract_message_id(char *message_id, off_t *pos,
+ int *status, char **errmsg, size_t *errlen);
+
+int
+mhthread_get_fields(int msgnum, char **in_reply_to, char **message_id,
+ char **references, char **subject);
+
+mhthread_id_table_t *
+mhthread_process_messages(struct msgs *mp);
+
+mhthread_container_list_t *
+mhthread_find_root_set(mhthread_id_table_t *id_table);
+
+mhthread_container_list_t *
+mhthread_prune_empty_containers(mhthread_container_list_t *list);
+
+mhthread_container_list_t *
+mhthread_thread_messages(struct msgs *mp);
diff --git a/sbr/thread.c b/sbr/thread.c
new file mode 100644
index 0000000..c400a4d
--- /dev/null
+++ b/sbr/thread.c
@@ -0,0 +1,812 @@
+/* thread.c -- implementation of jwz's threading algorithm
+ * (http://www.jwz.org/doc/threading.html)
+ *
+ * $Id: thread.c,v 1.15 2002/10/21 04:57:08 epg Exp $
+ */
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <h/mh.h>
+#include <h/thread.h>
+
+/* holds 2^128 - 1 in decimal, plus '\0' */
+#define LEN_ULONG 40
+
+#if !defined(HOST_NAME_MAX)
+#if defined MAXHOSTNAMELEN
+#define HOST_NAME_MAX MAXHOSTNAMELEN
+#else
+#define HOST_NAME_MAX 64
+#endif
+#endif
+
+/* 1 for '\0', 1 for '@', 2 for the two dots ('.'), HOST_NAME_MAX for
+ * the hostname, and two LEN_ULONGs for time and pid. */
+#define MESSAGE_ID_LEN 1 + 1 + 2 + HOST_NAME_MAX + LEN_ULONG + LEN_ULONG
+
+#define xmalloc malloc
+#define xrealloc realloc
+
+/* XXX */
+static char
+msgid_regex[] = "<([^>]+)>";
+
+/******************************************************************************
+ * Container
+ */
+
+mhthread_container_list_t *
+mhthread_container_list_new(void)
+{
+ mhthread_container_list_t *list;
+
+ list = xmalloc(sizeof(mhthread_container_list_t));
+ list->container = NULL;
+ list->next = NULL;
+
+ return list;
+}
+
+mhthread_container_list_t *
+mhthread_container_list_prepend(mhthread_container_list_t *list,
+ mhthread_container_t *container)
+{
+ mhthread_container_list_t *result;
+
+ assert(container != NULL);
+
+ result = mhthread_container_list_new();
+ result->container = container;
+
+ result->next = list;
+
+ return result;
+}
+
+mhthread_container_list_t *
+mhthread_container_list_remove(mhthread_container_list_t *list,
+ mhthread_container_t *container)
+{
+ mhthread_container_list_t *item, *last;
+
+ assert(container != NULL);
+
+ last = NULL;
+ for (item = list; item; item = item->next) {
+ if (item->container == container) {
+ if (last) {
+ last->next = item->next;
+ } else {
+ list = item->next;
+ }
+ }
+ last = item;
+ }
+
+ return list;
+}
+
+mhthread_container_list_t *
+mhthread_container_list_extend(mhthread_container_list_t *list,
+ mhthread_container_list_t *extend)
+{
+ mhthread_container_list_t *item;
+
+ if (!list) {
+ return extend;
+ }
+
+ for (item = list; item->next; item = item->next) {
+ /* Just find the last item. */
+ ;
+ }
+
+ item->next = extend;
+
+ return list;
+}
+
+size_t
+mhthread_container_list_length(mhthread_container_list_t *list)
+{
+ size_t len;
+
+ len = 0;
+ for (; list; list = list->next) {
+ len++;
+ }
+
+ return len;
+}
+
+mhthread_container_list_t *
+mhthread_container_list_copy(mhthread_container_list_t *list)
+{
+ mhthread_container_list_t *item, *newlist, *newitem, *last;
+
+ newlist = last = NULL;
+
+ for (item = list; item; item = item->next) {
+ newitem = mhthread_container_list_new();
+ newitem->container = item->container;
+ if (last) {
+ last->next = newitem;
+ last = newitem;
+ } else {
+ newlist = last = newitem;
+ }
+ }
+
+ return newlist;
+}
+
+mhthread_container_t *
+mhthread_container_new(void)
+{
+ mhthread_container_t *container;
+
+ container = xmalloc(sizeof(mhthread_container_t));
+ container->msgnum = 0;
+ container->parent = NULL;
+ container->children = NULL;
+
+ return container;
+}
+
+mhthread_container_t *
+mhthread_container_add_child(mhthread_container_t *container,
+ mhthread_container_t *child)
+{
+ assert(container != NULL);
+ assert(child != NULL);
+
+ /* Prepending is lazy, but it doesn't really matter. */
+ container->children = mhthread_container_list_prepend(container->children,
+ child);
+ child->parent = container;
+
+ return child;
+}
+
+mhthread_container_t *
+mhthread_container_remove_child(mhthread_container_t *container,
+ mhthread_container_t *child)
+{
+ assert(container != NULL);
+ assert(child != NULL);
+
+ container->children = mhthread_container_list_remove(container->children,
+ child);
+ child->parent = NULL;
+
+ return child;
+}
+
+mhthread_container_t *
+mhthread_container_find_descendant(mhthread_container_t *haystack,
+ mhthread_container_t *needle)
+{
+ mhthread_container_list_t *item;
+ mhthread_container_t *found;
+
+ assert(haystack != NULL);
+ assert(needle != NULL);
+
+ if (haystack == needle) {
+ return haystack;
+ }
+
+ for (item = haystack->children; item; item = item->next) {
+ if (item->container == needle) {
+ return item->container;
+ } else if ((found = mhthread_container_find_descendant(item->container,
+ needle))) {
+ return found;
+ }
+ }
+
+ return NULL;
+}
+
+mhthread_container_t *
+mhthread_container_find_descendant_by_number(mhthread_container_t *haystack,
+ int needle)
+{
+ mhthread_container_list_t *item;
+ mhthread_container_t *found;
+
+ assert(haystack != NULL);
+ assert(needle > 0);
+
+ if (haystack->msgnum == needle) {
+ return haystack;
+ }
+
+ for (item = haystack->children; item; item = item->next) {
+ if (item->container->msgnum == needle) {
+ return item->container;
+ } else if ((found =
+
mhthread_container_find_descendant_by_number(item->container,
+ needle))) {
+ return found;
+ }
+ }
+
+ return NULL;
+}
+
+mhthread_container_list_t *
+mhthread_container_prune(mhthread_container_t *container)
+{
+ mhthread_container_list_t *new_children, *item, *list;
+
+ assert(container != NULL);
+
+ new_children = NULL;
+ for (item = container->children;
+ item;
+ item = item->next) {
+ list = mhthread_container_prune(item->container);
+ new_children = mhthread_container_list_extend(new_children, list);
+ mhthread_container_remove_child(container, item->container);
+ }
+
+ for (; new_children; new_children = new_children->next) {
+ mhthread_container_add_child(container, new_children->container);
+ }
+
+ if (container->msgnum == 0
+ && mhthread_container_list_length(container->children) == 0) {
+ /* 4A Remove empty containers. */
+ return NULL;
+ } else if (container->msgnum == 0
+ && (mhthread_container_list_length(container->children) == 1
+ || container->parent)) {
+ /* 4B: Promote children. */
+ list = mhthread_container_list_copy(container->children);
+ for (item = list; item; item = item->next) {
+ mhthread_container_remove_child(container, item->container);
+ }
+ return list;
+ }
+
+ list = mhthread_container_list_new();
+ list->container = container;
+ return list;
+}
+
+/******************************************************************************
+ * id_table
+ */
+
+mhthread_container_t *
+mhthread_id_table_lookup(mhthread_id_table_t *id_table, char *message_id)
+{
+ mhthread_id_table_t *item;
+
+ for (item = id_table; item; item = item->next) {
+ if (strcmp(item->id, message_id) == 0) {
+ return item->container;
+ }
+ }
+
+ return NULL;
+}
+
+mhthread_id_table_t *
+mhthread_id_table_add(mhthread_id_table_t *id_table,
+ mhthread_container_t *container, char *message_id)
+{
+ mhthread_id_table_t *item;
+
+ item = xmalloc(sizeof(mhthread_id_table_t));
+ item->id = message_id;
+ item->container = container;
+
+ item->next = id_table;
+
+ return item;
+}
+
+/******************************************************************************
+ * Utility procedures
+ */
+
+static char *
+fill_it(char *str, regmatch_t *regmatch, int idx)
+{
+ size_t len;
+ char *result;
+
+ result = NULL;
+
+ len = regmatch[idx].rm_eo - regmatch[idx].rm_so;
+ if (len > 0) {
+ result = xmalloc(len + 1);
+ strncpy(result, str + regmatch[idx].rm_so, len);
+ result[len] = '\0';
+ }
+
+ return result;
+}
+
+char *
+mhthread_extract_message_id(char *message_id, off_t *pos,
+ int *status, char **errmsg, size_t *errlen)
+{
+ regex_t reg;
+ regmatch_t regmatch[2];
+ size_t need_errlen;
+ char *result;
+
+ assert(message_id != NULL);
+ /* pos may be NULL */
+ assert(status != NULL);
+ assert(errmsg != NULL);
+ assert(errlen != NULL);
+
+ result = NULL;
+
+ *status = regcomp(®, msgid_regex, REG_EXTENDED);
+ if (*status != 0) {
+ need_errlen = regerror(*status, ®, *errmsg, *errlen);
+ if (need_errlen > *errlen) {
+ *errlen = need_errlen;
+ *errmsg = realloc(*errmsg, *errlen);
+ regerror(*status, ®, *errmsg, *errlen);
+ }
+
+ goto out;
+ }
+
+ *status = regexec(®, message_id, 2, regmatch, 0);
+ if (*status != 0) {
+ need_errlen = regerror(*status, ®, *errmsg, *errlen);
+ if (need_errlen > *errlen) {
+ *errlen = need_errlen;
+ *errmsg = realloc(*errmsg, *errlen);
+ regerror(*status, ®, *errmsg, *errlen);
+ }
+
+ goto out;
+ }
+
+ result = fill_it(message_id, regmatch, 1);
+
+ if (pos) {
+ *pos = regmatch[0].rm_eo;
+ }
+
+out:
+ regfree(®);
+
+ return result;
+}
+
+/* This code was copied from scansbr.c and mangled; needs to be fixed. */
+/* Get the fields relevant to threading. Store NULL in each variable
+ * whose corresponding header is not present.
+ *
+ * Return boolean value indicating success.
+ */
+int
+mhthread_get_fields(int msgnum, char **in_reply_to, char **message_id,
+ char **references, char **subject)
+{
+ int state;
+ FILE *msgfile;
+ char *filename;
+ char name[NAMESZ];
+ char tmpbuf[BUFSIZ] = {'\0'};
+ char **this_header; /* Used to append folded header text. */
+ size_t len;
+ char *tmp;
+ int result;
+
+ result = 0;
+
+ if (in_reply_to) {
+ *in_reply_to = NULL;
+ }
+
+ if (message_id) {
+ *message_id = NULL;
+ }
+
+ if (references) {
+ *references = NULL;
+ }
+
+ if (subject) {
+ *subject = NULL;
+ }
+
+ filename = m_name (msgnum);
+
+ msgfile = fopen (filename, "r");
+ if (!msgfile) {
+ admonish (filename, "unable to open message");
+ return 0;
+ }
+
+ state = FLD;
+ for (;;) {
+ this_header = NULL;
+ state = m_getfld (state, name, tmpbuf, 5, msgfile);
+ switch (state) {
+ case FLD:
+ case FLDPLUS:
+ if (strcasecmp(name, "references") == 0) {
+ if (references) {
+ this_header = references;
+ len = strlen(tmpbuf);
+ *this_header = xmalloc(len + 1);
+ strcpy(*this_header, tmpbuf);
+ }
+ } else if (strcasecmp(name, "in-reply-to") == 0) {
+ if (in_reply_to) {
+ this_header = in_reply_to;
+ len = strlen(tmpbuf);
+ *this_header = xmalloc(len + 1);
+ strcpy(*this_header, tmpbuf);
+ }
+ } else if (strcasecmp(name, "subject") == 0) {
+ if (subject) {
+ this_header = subject;
+ len = strlen(tmpbuf);
+ *this_header = xmalloc(len + 1);
+ strcpy(*this_header, tmpbuf);
+ }
+ } else if (strcasecmp(name, "message-id") == 0) {
+ if (message_id) {
+ this_header = message_id;
+ len = strlen(tmpbuf);
+ *this_header = xmalloc(len + 1);
+ strcpy(*this_header, tmpbuf);
+ }
+ }
+
+ /* XXX: This does not handle folded headers correctly.
+ * I want to reimplement m_getfld to behave more
+ * reasonably, to clean it up, and make it not do so
+ * many different things. That function was written
+ * in 1986 and is optimized like crazy.
+ */
+ /* XXX: Or maybe it does. I've been using pickthread
+ * for a while now and it seems to work very well. I
+ * don't really know what i was thinking when i wrote
+ * this, i don't understand m_getfld. This certainly
+ * does need to be re-examined. Also i don't know why
+ * i use 5 here; that must be wrong.
+ */
+ /* Ah, I think I used 5 here (and especially above) to
+ * test FLDPLUS; if I read a decent amount from the
+ * message file, I'd read the whole header at once and
+ * not get to test this block (which normally would only
+ * run on really long headers). */
+ while (state == FLDPLUS) {
+ state = m_getfld (state, name, tmpbuf, 5, msgfile);
+ if (this_header) {
+ len += strlen(tmpbuf);
+ *this_header = xrealloc(*this_header, len + 5);
+ strcat(*this_header, tmpbuf);
+ }
+ }
+
+ break;
+
+ case BODY:
+ result = 1;
+ goto finished;
+
+ case LENERR:
+ case FMTERR:
+ case FILEEOF:
+ if (ferror(msgfile)) {
+ advise("read", "unable to"); /* "read error" */
+ goto finished;
+ } else {
+ result = 1;
+ goto finished;
+ }
+
+ default:
+ adios (NULL, "getfld() returned %d", state);
+ }
+ }
+
+finished:
+ fclose(msgfile);
+
+ if (*subject) {
+ size_t len = strlen(*subject) + 1;
+ tmp = xmalloc(len);
+ strcpy(tmp, *subject);
+ decode_rfc2047(tmp, *subject, len);
+ free(tmp);
+ }
+
+ return result;
+}
+
+/* XXX: what to call these funcs */
+static mhthread_container_t *
+handle_one_reference(char *message_id,
+ mhthread_container_t *this_container,
+ mhthread_container_t *last_container,
+ mhthread_id_table_t **id_table)
+{
+ mhthread_container_t *container;
+
+ assert(message_id != NULL);
+ assert(id_table != NULL);
+
+ container = mhthread_id_table_lookup(*id_table, message_id);
+ if (!container) {
+ container = mhthread_container_new();
+ *id_table = mhthread_id_table_add(*id_table, container, message_id);
+ }
+
+ if (last_container
+ && container != this_container
+ && !mhthread_container_find_descendant(container, last_container)) {
+ if (!container->parent) {
+ mhthread_container_add_child(last_container, container);
+ }
+ }
+
+ return container;
+}
+
+
+/* XXX: what to call thsi one */
+static mhthread_container_t *
+handle_references(char *references, char *in_reply_to,
+ mhthread_container_t *this_container,
+ mhthread_id_table_t **id_table)
+{
+ mhthread_container_t *last_container;
+ char *message_id, *last_message_id;
+ off_t pos;
+ int status;
+ char *errmsg;
+ size_t errlen;
+
+ assert(id_table != NULL);
+
+ last_container = NULL;
+ last_message_id = NULL;
+ errmsg = NULL;
+ errlen = 0;
+
+ if (references) {
+ for (message_id = mhthread_extract_message_id(references,
+ &pos,
+ &status,
+ &errmsg,
+ &errlen);
+ message_id;
+ references = references + pos,
+ message_id = mhthread_extract_message_id(references,
+ &pos,
+ &status,
+ &errmsg,
+ &errlen)) {
+ last_container = handle_one_reference(message_id,
+ this_container,
+ last_container,
+ id_table);
+ last_message_id = message_id;
+ }
+
+ if (status != 0 && status != REG_NOMATCH) {
+ /* XXX: show error message and return */
+ fprintf(stderr, "%s\n", errmsg);
+ return last_container;
+ }
+ }
+
+ /* Really, we *should* be able to ignore In-Reply-To, but we can't
+ * because not all MUAs use References correctly. The In-Reply-To
+ * header might not have anything of value at all, but usually it
+ * will. We can only look for one message-id (the first string
+ * that looks like one); anything else is most likely an email
+ * address. For a message with a correct References header, the
+ * message-id in the In-Reply-To header will be the same as the
+ * last one in the References header; note that we test for that
+ * case and ignore that message-id.
+ *
+ * However, some MUAs (such as recent versions of Eudora), simply
+ * copy the parent message's References header and only put the
+ * parent's message-id into In-Reply-To, not appending it to
+ * References at all. For these MUAs, we must check for a
+ * message-id in In-Reply-To that isn't the same as the last
+ * message-id from References.
+ */
+ if (in_reply_to) {
+ message_id = mhthread_extract_message_id(in_reply_to,
+ NULL,
+ &status,
+ &errmsg,
+ &errlen);
+ if (status != 0) {
+ if (status == REG_NOMATCH) {
+ /* no message-id in this header */
+ return last_container;
+ } else {
+ /* XXX: show error message and return */
+ fprintf(stderr, "%d: %s\n", status, errmsg);
+ return last_container;
+ }
+ }
+
+ /* Only use this message-id if there were none in References
+ * (!last_message_id) or if this message-id is not the last
+ * one from References (see comment above this block).
+ */
+ if (!last_message_id || strcmp(message_id, last_message_id) != 0) {
+ last_container = handle_one_reference(message_id,
+ this_container,
+ last_container,
+ id_table);
+ }
+ }
+
+ return last_container;
+}
+
+mhthread_id_table_t *
+mhthread_process_messages(struct msgs *mp)
+{
+ char *references;
+ char *subject;
+ char *message_id;
+ char *in_reply_to;
+ int msgnum, status;
+ char *errmsg;
+ size_t errlen;
+ time_t now;
+ mhthread_id_table_t *id_table;
+ mhthread_container_t *container, *last_container;
+
+ id_table = NULL;
+
+ for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
+ if (!is_selected(mp, msgnum)) {
+ continue;
+ }
+
+ if (!mhthread_get_fields(msgnum, &in_reply_to, &message_id,
+ &references, &subject)) {
+ return NULL;
+ }
+
+ if (message_id) {
+ errmsg = NULL;
+ errlen = 0;
+ message_id = mhthread_extract_message_id(message_id, NULL,
+ &status, &errmsg,
+ &errlen);
+ if (status != 0) {
+ /* XXX: show error message and return */
+ fprintf(stderr, "%s\n", errmsg);
+ exit(1);
+ }
+ } else {
+ message_id = xmalloc(MESSAGE_ID_LEN);
+ time(&now);
+ sprintf(message_id, "address@hidden",
+ (int)getpid(), (long int)now, LocalName(0));
+ }
+
+ /* 1A */
+ container = mhthread_id_table_lookup(id_table, message_id);
+ if (container) {
+ if (container->msgnum != 0) {
+ /* This is a duplicate message-id. Note that jwz's
+ * description does not mention this case, but the
+ * IMAP thread extension draft RFC does. That draft
+ * says that in this case we should assign a unique
+ * message-id to this message. The URL for this draft
+ * is still changing at the moment (as of this writing
+ * it is
+ *
http://www.ietf.org/internet-drafts/draft-ietf-imapext-thread-12.txt),
+ * but you can find it with this Google search:
+ * <http://www.google.com/search?q=site%3Aietf.org+imap+jwz>.
+ */
+ message_id = xmalloc(MESSAGE_ID_LEN);
+ time(&now);
+ sprintf(message_id, "address@hidden",
+ (int)getpid(), (long int)now, LocalName(0));
+ container = mhthread_container_new();
+ id_table = mhthread_id_table_add(id_table, container,
+ message_id);
+ }
+ } else {
+ container = mhthread_container_new();
+ id_table = mhthread_id_table_add(id_table, container, message_id);
+ }
+ container->msgnum = msgnum;
+
+ if (references || in_reply_to) {
+ /* 1B */
+ last_container = handle_references(references, in_reply_to,
+ container, &id_table);
+ /* 1C */
+ if (last_container
+ && last_container != container
+ && !mhthread_container_find_descendant(container,
+ last_container)) {
+ if (container->parent) {
+ mhthread_container_remove_child(container->parent,
+ container);
+ }
+ mhthread_container_add_child(last_container, container);
+ }
+ }
+ }
+
+ return id_table;
+}
+
+mhthread_container_list_t *
+mhthread_find_root_set(mhthread_id_table_t *id_table)
+{
+ mhthread_container_list_t *list;
+ mhthread_id_table_t *item;
+
+ assert(id_table != NULL);
+
+ list = NULL;
+ for (item = id_table; item; item = item->next) {
+ if (!item->container->parent) {
+ list = mhthread_container_list_prepend(list, item->container);
+ }
+ }
+
+ return list;
+}
+
+mhthread_container_list_t *
+mhthread_prune_empty_containers(mhthread_container_list_t *list)
+{
+ mhthread_container_list_t *newlist, *item, *tmp;
+
+ newlist = NULL;
+ for (item = list; item; item = item->next) {
+ tmp = mhthread_container_prune(item->container);
+ newlist = mhthread_container_list_extend(newlist, tmp);
+ }
+
+ return newlist;
+}
+
+mhthread_container_list_t *
+mhthread_thread_messages(struct msgs *mp)
+{
+ mhthread_id_table_t *id_table;
+ mhthread_container_list_t *root_set;
+
+ assert(mp != NULL);
+
+ /* mhthread_process_messages is part 1 in jwz's description. */
+ id_table = mhthread_process_messages(mp);
+ if (!id_table) {
+ return NULL;
+ }
+
+ /* mhthread_find_root_set is part 2 in jwz's description. */
+ root_set = mhthread_find_root_set(id_table);
+
+ /* mhthread_prune_empty_containers is part 4 in jwz's description. */
+ return mhthread_prune_empty_containers(root_set);
+}
diff --git a/uip/pickthread.c b/uip/pickthread.c
new file mode 100644
index 0000000..f60cca6
--- /dev/null
+++ b/uip/pickthread.c
@@ -0,0 +1,186 @@
+/* pickthread.c -- Pick messages in same thread as cur.
+ *
+ * $Id: pickthread.c,v 1.2 2002/09/23 08:51:54 epg Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <h/mh.h>
+#include <h/thread.h>
+
+/*
+ * We allocate space for message names (msgs array)
+ * this number of elements at a time.
+ */
+#define MAXMSGS 256
+
+static struct swit switches[] = {
+#define VERSIONSW 0
+ { "version", 0 },
+#define HELPSW 1
+ { "help", 0 },
+#define PARENTSW 2
+ { "parents", 0 },
+ { NULL, 0 }
+};
+
+static void
+print_container(mhthread_container_t *list)
+{
+ mhthread_container_list_t *item;
+
+ if (list->msgnum != 0) {
+ printf("%d\n", list->msgnum);
+ }
+
+ for (item = list->children; item; item = item->next) {
+ print_container(item->container);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ int msgnum, nummsgs, maxmsgs;
+ char *cp, *maildir, *folder = NULL;
+ char buf[BUFSIZ];
+ char **argp, **arguments, **msgs;
+ struct msgs *mp, *curp;
+ mhthread_container_list_t *list;
+ int listparents;
+
+#ifdef LOCALE
+ setlocale(LC_ALL, "");
+#endif
+ invo_name = r1bindex (argv[0], '/');
+
+ /* read user profile/context */
+ context_read();
+
+ arguments = getarguments (invo_name, argc, argv, 1);
+ argp = arguments;
+
+ /*
+ * Allocate the initial space to record message
+ * names, ranges, and sequences.
+ */
+ nummsgs = 0;
+ maxmsgs = MAXMSGS;
+ if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs)))))
+ adios (NULL, "unable to allocate storage");
+
+ /*
+ * Parse arguments
+ */
+
+ /* defaults */
+ listparents = 0;
+
+ while ((cp = *argp++)) {
+ if (*cp == '-') {
+ switch (smatch (++cp, switches)) {
+ case AMBIGSW:
+ ambigsw (cp, switches);
+ done (1);
+ case UNKWNSW:
+ adios (NULL, "-%s unknown", cp);
+
+ case PARENTSW:
+ listparents = 1;
+ continue;
+
+ case HELPSW:
+ snprintf (buf, sizeof(buf), "%s [+folder] [msgs]
[switches]",
+ invo_name);
+ print_help (buf, switches, 1);
+ done (1);
+ case VERSIONSW:
+ print_version(invo_name);
+ done (1);
+ }
+ }
+ if (*cp == '+' || *cp == '@') {
+ if (folder)
+ adios (NULL, "only one folder at a time!");
+ else
+ folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
+ } else {
+ /*
+ * Check if we need to allocate more space
+ * for message names/ranges/sequences.
+ */
+ if (nummsgs >= maxmsgs) {
+ maxmsgs += MAXMSGS;
+ if (!(msgs = (char **) realloc (msgs,
+ (size_t) (maxmsgs * sizeof(*msgs)))))
+ adios (NULL, "unable to reallocate msgs storage");
+ }
+ msgs[nummsgs++] = cp;
+ }
+ }
+
+ if (!context_find ("path"))
+ free (path ("./", TFOLDER));
+
+ if (!nummsgs)
+ msgs[nummsgs++] = "all";
+ if (!folder)
+ folder = getfolder (1);
+ maildir = m_maildir (folder);
+
+ if (chdir (maildir) != 0)
+ adios (maildir, "unable to change directory to");
+
+ /* read folder and create message structure */
+ if (!(mp = folder_read (folder)))
+ adios (NULL, "unable to read folder %s", folder);
+
+ /* check for empty folder */
+ if (mp->nummsg == 0)
+ adios (NULL, "no messages in %s", folder);
+
+ /* parse all the message ranges/sequences and set SELECTED */
+ for (msgnum = 0; msgnum < nummsgs; msgnum++)
+ if (!m_convert (mp, msgs[msgnum]))
+ done(1);
+ seq_setprev (mp); /* set the Previous-Sequence */
+
+ context_replace (pfolder, folder); /* update current folder */
+ seq_save (mp); /* synchronize message sequences */
+ context_save (); /* save the context file */
+
+ if (!(curp = folder_read (folder))) {
+ adios (NULL, "unable to read folder %s", folder);
+ }
+
+ if (!m_convert (curp, "cur")) {
+ adios (NULL, "failed to get cur message in %s", folder);
+ }
+
+ list = mhthread_thread_messages(mp);
+
+ if (listparents) {
+ for (; list; list = list->next) {
+ if (list->container->msgnum) {
+ printf("%d\n", list->container->msgnum);
+ }
+ }
+ } else {
+ for (; list; list = list->next) {
+ if (mhthread_container_find_descendant_by_number(list->container,
+ curp->lowsel)) {
+ print_container(list->container);
+ break;
+ }
+ }
+ }
+
+ folder_free (mp);
+
+ done (0);
+ return 1;
+}
- Re: [Nmh-workers] Threads, (continued)
- Re: [Nmh-workers] Threads, Ralph Corderoy, 2013/04/07
- Re: [Nmh-workers] Threads, Ralph Corderoy, 2013/04/07
- Re: [Nmh-workers] Threads, Ken Hornstein, 2013/04/07
- Re: [Nmh-workers] Threads, norm, 2013/04/07
- Re: [Nmh-workers] Threads, Bill Wohler, 2013/04/08
- Re: [Nmh-workers] Threads, Ralph Corderoy, 2013/04/09
- Re: [Nmh-workers] Threads, Bill Wohler, 2013/04/11
- Re: [Nmh-workers] Threads, Ralph Corderoy, 2013/04/28
- Re: [Nmh-workers] Threads, Bill Wohler, 2013/04/11
- Re: [Nmh-workers] Threads, Ken Hornstein, 2013/04/07
- Re: [Nmh-workers] Threads,
Eric Gillespie <=
- Re: [Nmh-workers] Threads, Ken Hornstein, 2013/04/08
- Re: [Nmh-workers] Threads, Ralph Corderoy, 2013/04/08
- Re: [Nmh-workers] Threads, Ken Hornstein, 2013/04/08
- Re: [Nmh-workers] Threads, epg, 2013/04/08
- Re: [Nmh-workers] Threads, Eric Gillespie, 2013/04/09
- Re: [Nmh-workers] Relative Message Numbers, Bill Wohler, 2013/04/06
- Re: [Nmh-workers] Relative Message Numbers, Lyndon Nerenberg, 2013/04/06
- Re: [Nmh-workers] Relative Message Numbers, Bill Wohler, 2013/04/06
- Re: [Nmh-workers] Relative Message Numbers, Lyndon Nerenberg, 2013/04/06
- Re: [Nmh-workers] Relative Message Numbers, Ralph Corderoy, 2013/04/07