nmh-workers
[Top] [All Lists]

Re: [Nmh-workers] Threads

2013-04-09 02:44:34
I found a newer version at work today; how much newer, I have
no idea.  Looks like the only real difference is memory
management; I guess the original version never freed anything;
mabye that became a problem with larger mailboxes at work :)

Oh, and I can see now that I never bothered with
subject matching.

OK, back to lurking.  Thought I might be able to find time to
help if you are interested in using this.



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..596f8b6
--- /dev/null
+++ b/h/thread.h
@@ -0,0 +1,45 @@
+/* thread.h -- implementation of jwz's threading algorithm
+ *             (http://www.jwz.org/doc/threading.html)
+ */
+
+#include <sys/types.h>
+
+typedef struct mhthread_container_list mhthread_container_list_t;
+typedef struct mhthread_container mhthread_container_t;
+
+struct mhthread_container {
+    /* The message number for the message, or 0 if this container represents a
+     * message referenced by some other message but missing from the selectin 
of
+     * messages processed. */
+    int msgnum;
+
+    /* Reference to the message to which this message is a follow-up. */
+    mhthread_container_t *parent;
+
+    /* List of follow-ups to this message. */
+    mhthread_container_list_t *children;
+};
+
+struct mhthread_container_list {
+    mhthread_container_t *container;
+    mhthread_container_list_t *next;
+};
+
+/* Thread the selected messages.  Returns a list of top-level messages, that is
+ * all messages without follow-ups in the selection.  Each of these messages 
may
+ * have a list of follow-ups, each of which may themselves have follow-ups, and
+ * so on. */
+mhthread_container_list_t *
+mhthread_thread_messages(struct msgs *mp);
+
+void
+mhthread_container_list_free(mhthread_container_list_t *list);
+
+size_t
+mhthread_container_list_length(mhthread_container_list_t *list);
+
+/* If the specified message number is contained directly in the specified
+ * container, or in any child container (recursively), return the container
+ * holding that message.  Return NULl otherwise. */
+mhthread_container_t *
+mhthread_container_find_descendant(mhthread_container_t *haystack, int needle);
diff --git a/sbr/thread.c b/sbr/thread.c
new file mode 100644
index 0000000..0017af1
--- /dev/null
+++ b/sbr/thread.c
@@ -0,0 +1,885 @@
+/* thread.c -- implementation of jwz's threading algorithm
+ *             (http://www.jwz.org/doc/threading.html)
+ */
+
+#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
+ */
+
+static 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;
+}
+
+void
+mhthread_container_list_free(mhthread_container_list_t *list)
+{
+    mhthread_container_list_t *item, *last;
+    for (last = NULL, item = list; item; item = item->next) {
+        if (last) {
+            if (last->container) {
+                mhthread_container_list_free(last->container->children);
+                free(last->container);
+            }
+            free(last);
+        }
+        last = item;
+    }
+    if (last) {
+        if (last->container) {
+            mhthread_container_list_free(last->container->children);
+            free(last->container);
+        }
+        free(last);
+    }
+}
+
+static 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;
+}
+
+static mhthread_container_list_t *
+mhthread_container_list_remove(mhthread_container_list_t *list,
+                               mhthread_container_t *container)
+{
+    mhthread_container_list_t *item, *last, *removed;
+
+    assert(container != NULL);
+
+    removed = last = NULL;
+    for (item = list; item; item = item->next) {
+        if (item->container == container) {
+            if (last) {
+                last->next = item->next;
+            } else {
+                list = item->next;
+            }
+            removed = item;
+        }
+        last = item;
+    }
+
+    if (removed) {
+        free(removed);
+    }
+
+    return list;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static mhthread_container_t *
+find_descendant_by_pointer(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 = find_descendant_by_pointer(item->container,
+                                                       needle))) {
+            return found;
+        }
+    }
+
+    return NULL;
+}
+
+mhthread_container_t *
+mhthread_container_find_descendant(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(item->container,
+                                                       needle))) {
+            return found;
+        }
+    }
+
+    return NULL;
+}
+
+static mhthread_container_list_t *
+mhthread_container_prune(mhthread_container_t *container)
+{
+    mhthread_container_list_t *new_children, *item, *list;
+    mhthread_container_list_t *last;
+
+    assert(container != NULL);
+
+    /* Recur on each child of container, building a new list of this list's
+     * direct children. */
+    for (item = container->children, last = new_children = NULL;
+         item;
+         item = item->next) {
+        list = mhthread_container_prune(item->container);
+        new_children = mhthread_container_list_extend(new_children, list);
+        if (last) {
+            free(last);
+        }
+        last = item;
+    }
+    if (last) {
+        free(last);
+    }
+    /* Reverse new_children back into proper order, and save it as container's
+     * children. */
+    for (item = new_children, container->children = last = NULL;
+         item;
+         item = item->next) {
+        if (last) {
+            last->next = container->children;
+            container->children = last;
+        }
+        last = item;
+    }
+    if (last) {
+        last->next = container->children;
+        container->children = last;
+    }
+
+    if (container->msgnum == 0
+        && mhthread_container_list_length(container->children) == 0) {
+        /* 4A Remove empty containers. */
+        free(container);
+        return NULL;
+    } else if (container->msgnum == 0
+               && (mhthread_container_list_length(container->children) == 1
+                   || container->parent)) {
+        /* 4B: Promote children. */
+        list = container->children;
+        free(container);
+        return list;
+    }
+
+    list = mhthread_container_list_new();
+    list->container = container;
+    return list;
+}
+
+/******************************************************************************
+ * id_table
+ */
+
+typedef struct id_table id_table_t;
+struct id_table {
+    char *id;
+    mhthread_container_t *container;
+    id_table_t *next;
+};
+
+static id_table_t *
+id_table_lookup(id_table_t *id_table, char *message_id)
+{
+    id_table_t *item;
+
+    for (item = id_table; item; item = item->next) {
+        if (strcmp(item->id, message_id) == 0) {
+            return item;
+        }
+    }
+
+    return NULL;
+}
+
+static id_table_t *
+id_table_add(id_table_t *id_table, mhthread_container_t *container,
+             char *message_id)
+{
+    id_table_t *item;
+
+    item = xmalloc(sizeof(id_table_t));
+    item->id = message_id;
+    item->container = container;
+
+    item->next = id_table;
+
+    return item;
+}
+
+static void
+id_table_free(id_table_t *id_table) {
+    id_table_t *item, *last;
+    for (last = NULL, item = id_table; item; item = item->next) {
+        if (last) {
+            free(last->id);
+            free(last);
+        }
+        last = item;
+    }
+    if (last) {
+        free(last->id);
+        free(last);
+    }
+}
+
+/******************************************************************************
+ * 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;
+}
+
+static char *
+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(&reg, msgid_regex, REG_EXTENDED);
+    if (*status != 0) {
+        need_errlen = regerror(*status, &reg, *errmsg, *errlen);
+        if (need_errlen > *errlen) {
+            *errlen = need_errlen;
+            *errmsg = realloc(*errmsg, *errlen);
+            regerror(*status, &reg, *errmsg, *errlen);
+        }
+
+        goto out;
+    }
+
+    *status = regexec(&reg, message_id, 2, regmatch, 0);
+    if (*status != 0) {
+        if (*status == REG_NOMATCH) {
+            goto out;
+        }
+        need_errlen = regerror(*status, &reg, *errmsg, *errlen);
+        if (need_errlen > *errlen) {
+            *errlen = need_errlen;
+            *errmsg = realloc(*errmsg, *errlen);
+            regerror(*status, &reg, *errmsg, *errlen);
+        }
+
+        goto out;
+    }
+
+    result = fill_it(message_id, regmatch, 1);
+
+    if (pos) {
+        *pos = regmatch[0].rm_eo;
+    }
+
+out:
+    regfree(&reg);
+
+    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.
+ */
+static int
+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 */
+
+/* If *message_id already in the table, free *message_id and point it at the
+ * entry from the table.  Else, allocate a new table entry referencing
+ * *message_id.  So, either way, callers MUST NOT free it.
+ */
+static mhthread_container_t *
+handle_one_reference(char **message_id,
+                     mhthread_container_t *this_container,
+                     mhthread_container_t *last_container,
+                     id_table_t **id_table)
+{
+    id_table_t *entry;
+    mhthread_container_t *container;
+
+    assert(message_id != NULL);
+    assert(*message_id != NULL);
+    assert(id_table != NULL);
+
+    entry = id_table_lookup(*id_table, *message_id);
+    if (entry) {
+        container = entry->container;
+        free(*message_id);
+        *message_id = entry->id;
+    } else {
+        container = mhthread_container_new();
+        *id_table = id_table_add(*id_table, container, *message_id);
+    }
+
+    if (last_container
+        && container != this_container
+        && !find_descendant_by_pointer(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,
+                  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 = extract_message_id(references, &pos,
+                                             &status, &errmsg, &errlen);
+             message_id;
+             references = references + pos,
+                 message_id = 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 = 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);
+        } else {
+            free(message_id);
+        }
+    }
+
+    return last_container;
+}
+
+static id_table_t *
+process_messages(struct msgs *mp)
+{
+    char *references;
+    char *subject;
+    char *message_id_value;
+    char *in_reply_to;
+    char *message_id;
+    int msgnum, status;
+    char *errmsg;
+    size_t errlen;
+    time_t now;
+    id_table_t *id_table, *entry;
+    mhthread_container_t *container, *last_container;
+
+    id_table = NULL;
+
+    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
+        if (!is_selected(mp, msgnum)) {
+            continue;
+        }
+
+        if (!get_fields(msgnum, &in_reply_to, &message_id_value,
+                        &references, &subject)) {
+            if (in_reply_to) free(in_reply_to);
+            if (message_id_value) free(message_id_value);
+            if (references) free(references);
+            if (subject) free(subject);
+            return NULL;
+        }
+
+        message_id = NULL;
+        if (message_id_value) {
+            errmsg = NULL;
+            errlen = 0;
+            message_id = extract_message_id(message_id_value, NULL,
+                                            &status, &errmsg, &errlen);
+            free(message_id_value);
+            if (status != 0) {
+                if (status != REG_NOMATCH) {
+                    /* XXX: show error message and return */
+                    fprintf(stderr, "%d: %s\n", msgnum, errmsg);
+                    exit(1);
+                }
+            }
+        }
+        if (!message_id) {
+            message_id = xmalloc(MESSAGE_ID_LEN);
+            time(&now);
+            sprintf(message_id, "%d.%ld@%s",
+                    (int)getpid(), (long int)now, LocalName(0));
+        }
+
+        /* 1A */
+        entry = id_table_lookup(id_table, message_id);
+        if (entry) {
+            free(message_id);
+            container = entry->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, "%d.%ld@%s",
+                        (int)getpid(), (long int)now, LocalName(0));
+                container = mhthread_container_new();
+                id_table = id_table_add(id_table, container, message_id);
+            }
+        } else {
+            container = mhthread_container_new();
+            id_table = 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
+                && !find_descendant_by_pointer(container, last_container)) {
+                if (container->parent) {
+                    mhthread_container_remove_child(container->parent,
+                                                    container);
+                }
+                mhthread_container_add_child(last_container, container);
+            }
+        }
+
+        if (in_reply_to) free(in_reply_to);
+        if (references) free(references);
+        if (subject) free(subject);
+    }
+
+    return id_table;
+}
+
+static mhthread_container_list_t *
+find_root_set(id_table_t *id_table)
+{
+    mhthread_container_list_t *list;
+    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;
+}
+
+static mhthread_container_list_t *
+prune_empty_containers(mhthread_container_list_t *list)
+{
+    mhthread_container_list_t *newlist, *item, *tmp, *last;
+
+    last = newlist = NULL;
+    for (item = list; item; item = item->next) {
+        tmp = mhthread_container_prune(item->container);
+        newlist = mhthread_container_list_extend(newlist, tmp);
+        if (last) {
+            free(last);
+        }
+        last = item;
+    }
+    if (last) {
+        free(last);
+    }
+
+    return newlist;
+}
+
+mhthread_container_list_t *
+mhthread_thread_messages(struct msgs *mp)
+{
+    id_table_t *id_table;
+    mhthread_container_list_t *root_set;
+
+    assert(mp != NULL);
+
+    /* process_messages is part 1 in jwz's description. */
+    id_table = process_messages(mp);
+    if (!id_table) {
+        return NULL;
+    }
+
+    /* find_root_set is part 2 in jwz's description. */
+    root_set = find_root_set(id_table);
+    id_table_free(id_table);
+
+    /* prune_empty_containers is part 4 in jwz's description. */
+    return prune_empty_containers(root_set);
+}
diff --git a/uip/pickthread.c b/uip/pickthread.c
new file mode 100644
index 0000000..2855422
--- /dev/null
+++ b/uip/pickthread.c
@@ -0,0 +1,213 @@
+/* pickthread.c -- Pick messages in same thread as cur.
+ */
+
+#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 },
+#define DUMPSW 3
+    { "dump", 0 },
+    { NULL, 0 }
+};
+
+static void
+print_container(mhthread_container_list_t *list, int indent)
+{
+    int child_indent;;
+    mhthread_container_list_t *item;
+
+
+    if (indent == -1) {
+        child_indent = -1;
+    } else {
+        child_indent = indent + 2;
+    }
+
+    for (item = list; item; item = item->next) {
+        if (indent != -1) {
+            int i;
+            for (i = 0; i < indent; i++) {
+                fputs(" ", stdout);
+            }
+        }
+        if (item->container->msgnum != 0) {
+            printf("%d\n", item->container->msgnum);
+        }
+        print_container(item->container->children, child_indent);
+    }
+
+
+}
+
+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, *item;
+    int listparents, dump;
+
+#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;
+    dump = 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 DUMPSW:
+                    dump = 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 (item = list; item; item = item->next) {
+            if (item->container->msgnum) {
+                printf("%d\n", item->container->msgnum);
+            }
+        }
+    } else if (dump) {
+        print_container(list, 0);
+    } else {
+        for (item = list; item; item = item->next) {
+            if (mhthread_container_find_descendant(item->container,
+                                                   curp->lowsel)) {
+                if (item->container->msgnum != 0) {
+                    printf("%d\n", item->container->msgnum);
+                }
+                print_container(item->container->children, -1);
+                break;
+            }
+        }
+    }
+
+    mhthread_container_list_free(list);
+
+    folder_free (mp);
+
+    done (0);
+    return 1;
+}

_______________________________________________
Nmh-workers mailing list
Nmh-workers(_at_)nongnu(_dot_)org
https://lists.nongnu.org/mailman/listinfo/nmh-workers

<Prev in Thread] Current Thread [Next in Thread>