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(®, 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) { + if (*status == REG_NOMATCH) { + goto out; + } + 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. + */ +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@nongnu.org https://lists.nongnu.org/mailman/listinfo/nmh-workers