922 lines
18 KiB
C
922 lines
18 KiB
C
/*
|
|
** Copyright 2000 Double Precision, Inc.
|
|
** See COPYING for distribution information.
|
|
*/
|
|
|
|
/*
|
|
** $Id$
|
|
*/
|
|
|
|
#ifndef __WINDOWS__
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "rfc822.h"
|
|
#include "imaprefs.h"
|
|
|
|
static void swapmsgdata(struct imap_refmsg *a, struct imap_refmsg *b)
|
|
{
|
|
char *cp;
|
|
char c;
|
|
time_t t;
|
|
unsigned long ul;
|
|
|
|
#define swap(a,b,tmp) (tmp)=(a); (a)=(b); (b)=(tmp);
|
|
|
|
swap(a->msgid, b->msgid, cp);
|
|
swap(a->isdummy, b->isdummy, c);
|
|
swap(a->flag2, b->flag2, c);
|
|
|
|
swap(a->timestamp, b->timestamp, t);
|
|
swap(a->seqnum, b->seqnum, ul);
|
|
|
|
#undef swap
|
|
}
|
|
|
|
struct imap_refmsgtable *rfc822_threadalloc()
|
|
{
|
|
struct imap_refmsgtable *p;
|
|
|
|
p=(struct imap_refmsgtable *)malloc(sizeof(struct imap_refmsgtable));
|
|
if (p)
|
|
memset(p, 0, sizeof(*p));
|
|
return (p);
|
|
}
|
|
|
|
void rfc822_threadfree(struct imap_refmsgtable *p)
|
|
{
|
|
int i;
|
|
struct imap_refmsghash *h;
|
|
struct imap_subjlookup *s;
|
|
struct imap_refmsg *m;
|
|
|
|
for (i=0; i<sizeof(p->hashtable)/sizeof(p->hashtable[0]); i++)
|
|
while ((h=p->hashtable[i]) != 0)
|
|
{
|
|
p->hashtable[i]=h->nexthash;
|
|
free(h);
|
|
}
|
|
|
|
for (i=0; i<sizeof(p->subjtable)/sizeof(p->subjtable[0]); i++)
|
|
while ((s=p->subjtable[i]) != 0)
|
|
{
|
|
p->subjtable[i]=s->nextsubj;
|
|
free(s->subj);
|
|
free(s);
|
|
}
|
|
|
|
while ((m=p->firstmsg) != 0)
|
|
{
|
|
p->firstmsg=m->next;
|
|
if (m->subj)
|
|
free(m->subj);
|
|
free(m);
|
|
}
|
|
free(p);
|
|
}
|
|
|
|
static int hashmsgid(const char *msgid)
|
|
{
|
|
unsigned long hashno=0;
|
|
|
|
while (*msgid)
|
|
{
|
|
unsigned long n= (hashno << 1);
|
|
|
|
#define HMIDS (((struct imap_refmsgtable *)0)->hashtable)
|
|
#define HHMIDSS ( sizeof(HMIDS) / sizeof( HMIDS[0] ))
|
|
|
|
if (hashno & HHMIDSS )
|
|
n ^= 1;
|
|
|
|
hashno= n ^ (unsigned char)*msgid++;
|
|
}
|
|
|
|
return (hashno % HHMIDSS);
|
|
}
|
|
|
|
struct imap_refmsg *rfc822_threadallocmsg(struct imap_refmsgtable *mt,
|
|
const char *msgid)
|
|
{
|
|
int n=hashmsgid(msgid);
|
|
struct imap_refmsg *msgp= (struct imap_refmsg *)
|
|
malloc(sizeof(struct imap_refmsg)+1+strlen(msgid));
|
|
struct imap_refmsghash *h, **hp;
|
|
|
|
if (!msgp) return (0);
|
|
memset(msgp, 0, sizeof(*msgp));
|
|
strcpy ((msgp->msgid=(char *)(msgp+1)), msgid);
|
|
|
|
h=(struct imap_refmsghash *)malloc(sizeof(struct imap_refmsghash));
|
|
if (!h)
|
|
{
|
|
free(msgp);
|
|
return (0);
|
|
}
|
|
|
|
for (hp= &mt->hashtable[n]; *hp; hp= & (*hp)->nexthash)
|
|
{
|
|
if (strcmp( (*hp)->msg->msgid, msgp->msgid) > 0)
|
|
break;
|
|
}
|
|
|
|
h->nexthash= *hp;
|
|
*hp=h;
|
|
h->msg=msgp;
|
|
|
|
msgp->last=mt->lastmsg;
|
|
|
|
if (mt->lastmsg)
|
|
mt->lastmsg->next=msgp;
|
|
else
|
|
mt->firstmsg=msgp;
|
|
|
|
mt->lastmsg=msgp;
|
|
return (msgp);
|
|
}
|
|
|
|
struct imap_refmsg *rfc822_threadsearchmsg(struct imap_refmsgtable *mt,
|
|
const char *msgid)
|
|
{
|
|
int n=hashmsgid(msgid);
|
|
struct imap_refmsghash *h;
|
|
|
|
for (h= mt->hashtable[n]; h; h= h->nexthash)
|
|
{
|
|
int rc=strcmp(h->msg->msgid, msgid);
|
|
|
|
if (rc == 0) return (h->msg);
|
|
if (rc > 0)
|
|
break;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int findsubj(struct imap_refmsgtable *mt, const char *s, int *isrefwd,
|
|
int create, struct imap_subjlookup **ptr)
|
|
{
|
|
char *ss=rfc822_coresubj(s, isrefwd);
|
|
int n;
|
|
struct imap_subjlookup **h;
|
|
struct imap_subjlookup *newsubj;
|
|
|
|
if (!ss) return (-1);
|
|
n=hashmsgid(ss);
|
|
|
|
for (h= &mt->subjtable[n]; *h; h= &(*h)->nextsubj)
|
|
{
|
|
int rc=strcmp((*h)->subj, ss);
|
|
|
|
if (rc == 0)
|
|
{
|
|
free(ss);
|
|
*ptr= *h;
|
|
return (0);
|
|
}
|
|
if (rc > 0)
|
|
break;
|
|
}
|
|
if (!create)
|
|
{
|
|
free(ss);
|
|
*ptr=0;
|
|
return (0);
|
|
}
|
|
|
|
newsubj=malloc(sizeof(struct imap_subjlookup));
|
|
if (!newsubj)
|
|
{
|
|
free(ss);
|
|
return (-1);
|
|
}
|
|
memset(newsubj, 0, sizeof(*newsubj));
|
|
newsubj->subj=ss;
|
|
newsubj->nextsubj= *h;
|
|
newsubj->msgisrefwd= *isrefwd;
|
|
*h=newsubj;
|
|
*ptr=newsubj;
|
|
return (0);
|
|
}
|
|
|
|
static void linkparent(struct imap_refmsg *msg, struct imap_refmsg *lastmsg)
|
|
{
|
|
msg->parent=lastmsg;
|
|
msg->prevsib=lastmsg->lastchild;
|
|
if (msg->prevsib)
|
|
msg->prevsib->nextsib=msg;
|
|
else
|
|
lastmsg->firstchild=msg;
|
|
|
|
lastmsg->lastchild=msg;
|
|
msg->nextsib=0;
|
|
}
|
|
|
|
|
|
static void breakparent(struct imap_refmsg *m)
|
|
{
|
|
if (!m->parent) return;
|
|
|
|
if (m->prevsib) m->prevsib->nextsib=m->nextsib;
|
|
else m->parent->firstchild=m->nextsib;
|
|
|
|
if (m->nextsib) m->nextsib->prevsib=m->prevsib;
|
|
else m->parent->lastchild=m->prevsib;
|
|
m->parent=0;
|
|
}
|
|
|
|
static struct imap_refmsg *dorefcreate(struct imap_refmsgtable *mt,
|
|
const char *newmsgid,
|
|
struct rfc822a *a)
|
|
/* a - references header */
|
|
{
|
|
struct imap_refmsg *lastmsg=0, *m;
|
|
struct imap_refmsg *msg;
|
|
int n;
|
|
|
|
/*
|
|
(A) Using the Message-IDs in the message's references, link
|
|
the corresponding messages together as parent/child. Make
|
|
the first reference the parent of the second (and the second
|
|
a child of the first), the second the parent of the third
|
|
(and the third a child of the second), etc. The following
|
|
rules govern the creation of these links:
|
|
|
|
If no reference message can be found with a given
|
|
Message-ID, create a dummy message with this ID. Use
|
|
this dummy message for all subsequent references to this
|
|
ID.
|
|
*/
|
|
|
|
for (n=0; n<a->naddrs; n++)
|
|
{
|
|
char *msgid=rfc822_getaddr(a, n);
|
|
|
|
msg=*msgid ? rfc822_threadsearchmsg(mt, msgid ? msgid:""):0;
|
|
if (!msg)
|
|
{
|
|
msg=rfc822_threadallocmsg(mt, msgid ? msgid:"");
|
|
if (!msg)
|
|
{
|
|
if (msgid)
|
|
free(msgid);
|
|
|
|
return (0);
|
|
}
|
|
msg->isdummy=1;
|
|
}
|
|
|
|
if (msgid)
|
|
free(msgid);
|
|
|
|
/*
|
|
If a reference message already has a parent, don't change
|
|
the existing link.
|
|
*/
|
|
|
|
if (lastmsg == 0 || msg->parent)
|
|
{
|
|
lastmsg=msg;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
Do not create a parent/child link if creating that link
|
|
would introduce a loop. For example, before making
|
|
message A the parent of B, make sure that A is not a
|
|
descendent of B.
|
|
|
|
*/
|
|
|
|
for (m=lastmsg; m; m=m->parent)
|
|
if (strcmp(m->msgid, msg->msgid) == 0)
|
|
break;
|
|
if (m)
|
|
{
|
|
lastmsg=msg;
|
|
continue;
|
|
}
|
|
|
|
linkparent(msg, lastmsg);
|
|
|
|
lastmsg=msg;
|
|
}
|
|
|
|
/*
|
|
(B) Create a parent/child link between the last reference
|
|
(or NIL if there are no references) and the current message.
|
|
If the current message has a parent already, break the
|
|
current parent/child link before creating the new one. Note
|
|
that if this message has no references, that it will now
|
|
have no parent.
|
|
|
|
NOTE: The parent/child links MUST be kept consistent with
|
|
one another at ALL times.
|
|
|
|
*/
|
|
|
|
msg=*newmsgid ? rfc822_threadsearchmsg(mt, newmsgid):0;
|
|
|
|
/*
|
|
If a message does not contain a Message-ID header line,
|
|
or the Message-ID header line does not contain a valid
|
|
Message ID, then assign a unique Message ID to this
|
|
message.
|
|
|
|
Implementation note: empty msgid, plus dupe check below,
|
|
implements that.
|
|
*/
|
|
|
|
if (msg && msg->isdummy)
|
|
{
|
|
msg->isdummy=0;
|
|
if (msg->parent)
|
|
breakparent(msg);
|
|
}
|
|
else
|
|
{
|
|
#if 1
|
|
/*
|
|
** If two or more messages have the same Message ID, assign
|
|
** a unique Message ID to each of the duplicates.
|
|
**
|
|
** Implementation note: just unlink the existing message from
|
|
** it's parents/children.
|
|
*/
|
|
if (msg)
|
|
{
|
|
while (msg->firstchild)
|
|
breakparent(msg->firstchild);
|
|
breakparent(msg);
|
|
newmsgid="";
|
|
|
|
/* Create new entry with an empty msgid, if any more
|
|
** msgids come, they'll hit the dupe check again.
|
|
*/
|
|
|
|
}
|
|
#endif
|
|
msg=rfc822_threadallocmsg(mt, newmsgid);
|
|
if (!msg) return (0);
|
|
}
|
|
|
|
if (lastmsg)
|
|
{
|
|
for (m=lastmsg; m; m=m->parent)
|
|
if (strcmp(m->msgid, msg->msgid) == 0)
|
|
break;
|
|
if (!m)
|
|
linkparent(msg, lastmsg);
|
|
}
|
|
return (msg);
|
|
}
|
|
|
|
struct imap_refmsg *rfc822_threadmsg(struct imap_refmsgtable *mt,
|
|
const char *msgidhdr,
|
|
const char *refhdr,
|
|
const char *subjheader,
|
|
const char *dateheader,
|
|
unsigned long seqnum)
|
|
{
|
|
struct rfc822t *t;
|
|
struct rfc822a *a;
|
|
struct imap_refmsg *m;
|
|
|
|
char *msgid_s;
|
|
|
|
t=rfc822t_alloc(msgidhdr ? msgidhdr:"", 0);
|
|
if (!t)
|
|
return (0);
|
|
a=rfc822a_alloc(t);
|
|
if (!a)
|
|
{
|
|
rfc822t_free(t);
|
|
return (0);
|
|
}
|
|
|
|
msgid_s=a->naddrs > 0 ? rfc822_getaddr(a, 0):strdup("");
|
|
|
|
rfc822a_free(a);
|
|
rfc822t_free(t);
|
|
|
|
if (!msgid_s)
|
|
return (0);
|
|
|
|
t=rfc822t_alloc(refhdr ? refhdr:"", 0);
|
|
if (!t)
|
|
{
|
|
free(msgid_s);
|
|
return (0);
|
|
}
|
|
|
|
a=rfc822a_alloc(t);
|
|
if (!a)
|
|
{
|
|
rfc822t_free(t);
|
|
free(msgid_s);
|
|
return (0);
|
|
}
|
|
|
|
m=dorefcreate(mt, msgid_s, a);
|
|
|
|
rfc822a_free(a);
|
|
rfc822t_free(t);
|
|
free(msgid_s);
|
|
|
|
if (!m)
|
|
return (0);
|
|
|
|
if (subjheader && (m->subj=strdup(subjheader)) == 0)
|
|
return (0); /* Cleanup in rfc822_threadfree() */
|
|
|
|
m->timestamp=dateheader ? rfc822_parsedt(dateheader):0;
|
|
|
|
m->seqnum=seqnum;
|
|
|
|
return (m);
|
|
}
|
|
|
|
/*
|
|
(2) Gather together all of the messages that have no parents
|
|
and make them all children (siblings of one another) of a dummy
|
|
parent (the "root"). These messages constitute first messages
|
|
of the threads created thus far.
|
|
|
|
*/
|
|
|
|
struct imap_refmsg *rfc822_threadgetroot(struct imap_refmsgtable *mt)
|
|
{
|
|
struct imap_refmsg *root, *m;
|
|
|
|
if (mt->rootptr)
|
|
return (mt->rootptr);
|
|
|
|
root=rfc822_threadallocmsg(mt, "(root)");
|
|
|
|
if (!root) return (0);
|
|
|
|
root->parent=root; /* Temporary */
|
|
root->isdummy=1;
|
|
|
|
for (m=mt->firstmsg; m; m=m->next)
|
|
if (!m->parent)
|
|
{
|
|
if (m->isdummy && m->firstchild == 0)
|
|
continue; /* Can happen in reference creation */
|
|
|
|
linkparent(m, root);
|
|
}
|
|
root->parent=NULL;
|
|
return (mt->rootptr=root);
|
|
}
|
|
|
|
/*
|
|
**
|
|
** (3) Prune dummy messages from the thread tree. Traverse each
|
|
** thread under the root, and for each message:
|
|
*/
|
|
|
|
void rfc822_threadprune(struct imap_refmsgtable *mt)
|
|
{
|
|
struct imap_refmsg *msg;
|
|
|
|
for (msg=mt->firstmsg; msg; msg=msg->next)
|
|
{
|
|
struct imap_refmsg *saveparent, *m;
|
|
|
|
if (!msg->parent)
|
|
continue; // The root, need it later.
|
|
|
|
if (!msg->isdummy)
|
|
continue;
|
|
|
|
/*
|
|
**
|
|
** If it is a dummy message with NO children, delete it.
|
|
*/
|
|
|
|
if (msg->firstchild == 0)
|
|
{
|
|
breakparent(msg);
|
|
/*
|
|
** Don't free the node, it'll be done on msgtable
|
|
** purge.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
** If it is a dummy message with children, delete it, but
|
|
** promote its children to the current level. In other words,
|
|
** splice them in with the dummy's siblings.
|
|
**
|
|
** Do not promote the children if doing so would make them
|
|
** children of the root, unless there is only one child.
|
|
*/
|
|
|
|
if (msg->firstchild->nextsib &&
|
|
msg->parent->parent)
|
|
continue;
|
|
|
|
saveparent=msg->parent;
|
|
breakparent(msg);
|
|
|
|
while ((m=msg->firstchild) != 0)
|
|
{
|
|
breakparent(m);
|
|
linkparent(m, saveparent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** (4) Gather together messages under the root that have the same
|
|
** extracted subject text.
|
|
**
|
|
** (A) Create a table for associating extracted subjects with
|
|
** messages.
|
|
**
|
|
** (B) Populate the subject table with one message per
|
|
** extracted subject. For each message under the root:
|
|
*/
|
|
|
|
int rfc822_threadsortsubj(struct imap_refmsgtable *mt,
|
|
struct imap_refmsg *root)
|
|
{
|
|
struct imap_refmsg *toproot, *p;
|
|
|
|
for (toproot=root->firstchild; toproot; toproot=toproot->nextsib)
|
|
{
|
|
const char *subj;
|
|
struct imap_subjlookup *subjtop;
|
|
int isrefwd;
|
|
|
|
/*
|
|
** (i) Find the subject of this thread by extracting the
|
|
** base subject from the current message, or its first child
|
|
** if the current message is a dummy.
|
|
*/
|
|
|
|
p=toproot;
|
|
if (p->isdummy)
|
|
p=p->firstchild;
|
|
|
|
subj=p->subj ? p->subj:"";
|
|
|
|
|
|
/*
|
|
** (ii) If the extracted subject is empty, skip this
|
|
** message.
|
|
*/
|
|
|
|
if (*subj == 0)
|
|
continue;
|
|
|
|
/*
|
|
** (iii) Lookup the message associated with this extracted
|
|
** subject in the table.
|
|
*/
|
|
|
|
if (findsubj(mt, subj, &isrefwd, 1, &subjtop))
|
|
return (-1);
|
|
|
|
/*
|
|
**
|
|
** (iv) If there is no message in the table with this
|
|
** subject, add the current message and the extracted
|
|
** subject to the subject table.
|
|
*/
|
|
|
|
if (subjtop->msg == 0)
|
|
{
|
|
subjtop->msg=toproot;
|
|
subjtop->msgisrefwd=isrefwd;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
** Otherwise, replace the message in the table with the
|
|
** current message if the message in the table is not a
|
|
** dummy AND either of the following criteria are true:
|
|
*/
|
|
|
|
if (!subjtop->msg->isdummy)
|
|
{
|
|
/*
|
|
** The current message is a dummy
|
|
**
|
|
*/
|
|
|
|
if (toproot->isdummy)
|
|
{
|
|
subjtop->msg=toproot;
|
|
subjtop->msgisrefwd=isrefwd;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
** The message in the table is a reply or forward (its
|
|
** original subject contains a subj-refwd part and/or a
|
|
** "(fwd)" subj-trailer) and the current message is
|
|
not.
|
|
*/
|
|
|
|
if (subjtop->msgisrefwd && !isrefwd)
|
|
{
|
|
subjtop->msg=toproot;
|
|
subjtop->msgisrefwd=isrefwd;
|
|
}
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
** (C) Merge threads with the same subject. For each message
|
|
** under the root:
|
|
*/
|
|
|
|
int rfc822_threadmergesubj(struct imap_refmsgtable *mt,
|
|
struct imap_refmsg *root)
|
|
{
|
|
struct imap_refmsg *toproot, *p, *q, *nextroot;
|
|
char *str;
|
|
|
|
for (toproot=root->firstchild; toproot; toproot=nextroot)
|
|
{
|
|
const char *subj;
|
|
struct imap_subjlookup *subjtop;
|
|
int isrefwd;
|
|
|
|
nextroot=toproot->nextsib;
|
|
|
|
/*
|
|
** (i) Find the subject of this thread as in step 4.B.i
|
|
** above.
|
|
*/
|
|
|
|
p=toproot;
|
|
if (p->isdummy)
|
|
p=p->firstchild;
|
|
|
|
subj=p->subj ? p->subj:"";
|
|
|
|
/*
|
|
** (ii) If the extracted subject is empty, skip this
|
|
** message.
|
|
*/
|
|
|
|
if (*subj == 0)
|
|
continue;
|
|
|
|
/*
|
|
** (iii) Lookup the message associated with this extracted
|
|
** subject in the table.
|
|
*/
|
|
|
|
if (findsubj(mt, subj, &isrefwd, 0, &subjtop) || subjtop == 0)
|
|
return (-1);
|
|
|
|
/*
|
|
** (iv) If the message in the table is the current message,
|
|
** skip it.
|
|
*/
|
|
|
|
/* NOTE - ptr comparison IS NOT LEGAL */
|
|
|
|
subjtop->msg->flag2=1;
|
|
if (toproot->flag2)
|
|
{
|
|
toproot->flag2=0;
|
|
continue;
|
|
}
|
|
subjtop->msg->flag2=0;
|
|
|
|
/*
|
|
** Otherwise, merge the current message with the one in the
|
|
** table using the following rules:
|
|
**
|
|
** If both messages are dummies, append the current
|
|
** message's children to the children of the message in
|
|
** the table (the children of both messages become
|
|
** siblings), and then delete the current message.
|
|
*/
|
|
|
|
if (subjtop->msg->isdummy && toproot->isdummy)
|
|
{
|
|
while ((p=toproot->firstchild) != 0)
|
|
{
|
|
breakparent(p);
|
|
linkparent(p, subjtop->msg);
|
|
}
|
|
breakparent(toproot);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
** If the message in the table is a dummy and the current
|
|
** message is not, make the current message a child of
|
|
** the message in the table (a sibling of it's children).
|
|
*/
|
|
|
|
if (subjtop->msg->isdummy)
|
|
{
|
|
breakparent(toproot);
|
|
linkparent(toproot, subjtop->msg);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
** If the current message is a reply or forward and the
|
|
** message in the table is not, make the current message
|
|
** a child of the message in the table (a sibling of it's
|
|
** children).
|
|
*/
|
|
|
|
if (isrefwd)
|
|
{
|
|
p=subjtop->msg;
|
|
if (p->isdummy)
|
|
p=p->firstchild;
|
|
|
|
subj=p->subj ? p->subj:"";
|
|
|
|
str=rfc822_coresubj(subj, &isrefwd);
|
|
|
|
if (!str)
|
|
return (-1);
|
|
free(str); /* Don't really care */
|
|
|
|
if (!isrefwd)
|
|
{
|
|
breakparent(toproot);
|
|
linkparent(toproot, subjtop->msg);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Otherwise, create a new dummy container and make both
|
|
** messages children of the dummy, and replace the
|
|
** message in the table with the dummy message.
|
|
*/
|
|
|
|
/* What we do is create a new message, then move the
|
|
** contents of subjtop->msg (including its children)
|
|
** to the new message, then make the new message a child
|
|
** of subjtop->msg, and mark subjtop->msg as a dummy msg.
|
|
*/
|
|
|
|
q=rfc822_threadallocmsg(mt, "(dummy)");
|
|
if (!q)
|
|
return (-1);
|
|
|
|
q->isdummy=1;
|
|
|
|
swapmsgdata(q, subjtop->msg);
|
|
|
|
while ((p=subjtop->msg->firstchild) != 0)
|
|
{
|
|
breakparent(p);
|
|
linkparent(p, q);
|
|
}
|
|
linkparent(q, subjtop->msg);
|
|
|
|
breakparent(toproot);
|
|
linkparent(toproot, subjtop->msg);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
** (5) Traverse the messages under the root and sort each set of
|
|
** siblings by date. Traverse the messages in such a way that the
|
|
** "youngest" set of siblings are sorted first, and the "oldest"
|
|
** set of siblings are sorted last (grandchildren are sorted
|
|
** before children, etc). In the case of an exact match on date,
|
|
** use the order in which the messages appear in the mailbox (that
|
|
** is, by sequence number) to determine the order. In the case of
|
|
** a dummy message (which can only occur with top-level siblings),
|
|
** use its first child for sorting.
|
|
*/
|
|
|
|
static int cmp_msgs(const void *a, const void *b)
|
|
{
|
|
struct imap_refmsg *ma=*(struct imap_refmsg **)a;
|
|
struct imap_refmsg *mb=*(struct imap_refmsg **)b;
|
|
time_t ta, tb;
|
|
unsigned long na, nb;
|
|
|
|
while (ma && ma->isdummy)
|
|
ma=ma->firstchild;
|
|
|
|
while (mb && mb->isdummy)
|
|
mb=mb->firstchild;
|
|
|
|
ta=tb=0;
|
|
na=nb=0;
|
|
if (ma)
|
|
{
|
|
ta=ma->timestamp;
|
|
na=ma->seqnum;
|
|
}
|
|
if (mb)
|
|
{
|
|
tb=mb->timestamp;
|
|
nb=mb->seqnum;
|
|
}
|
|
|
|
return (ta < tb ? -1: ta > tb ? 1:
|
|
na < nb ? -1: na > nb ? 1:0);
|
|
}
|
|
|
|
struct imap_threadsortinfo {
|
|
struct imap_refmsgtable *mt;
|
|
struct imap_refmsg **sort_table;
|
|
size_t sort_table_cnt;
|
|
} ;
|
|
|
|
static int dothreadsort(struct imap_threadsortinfo *,
|
|
struct imap_refmsg *);
|
|
|
|
int rfc822_threadsortbydate(struct imap_refmsgtable *mt)
|
|
{
|
|
struct imap_threadsortinfo itsi;
|
|
int rc;
|
|
|
|
itsi.mt=mt;
|
|
itsi.sort_table=0;
|
|
itsi.sort_table_cnt=0;
|
|
|
|
rc=dothreadsort(&itsi, mt->rootptr);
|
|
|
|
if (itsi.sort_table)
|
|
free(itsi.sort_table);
|
|
return (rc);
|
|
}
|
|
|
|
static int dothreadsort(struct imap_threadsortinfo *itsi,
|
|
struct imap_refmsg *p)
|
|
{
|
|
struct imap_refmsg *q;
|
|
size_t i, n;
|
|
|
|
for (q=p->firstchild; q; q=q->nextsib)
|
|
dothreadsort(itsi, q);
|
|
|
|
n=0;
|
|
for (q=p->firstchild; q; q=q->nextsib)
|
|
++n;
|
|
|
|
if (n > itsi->sort_table_cnt)
|
|
{
|
|
struct imap_refmsg **new_array=(struct imap_refmsg **)
|
|
(itsi->sort_table ?
|
|
realloc(itsi->sort_table,
|
|
sizeof(struct imap_refmsg *)*n)
|
|
:malloc(sizeof(struct imap_refmsg *)*n));
|
|
|
|
if (!new_array)
|
|
return (-1);
|
|
|
|
itsi->sort_table=new_array;
|
|
itsi->sort_table_cnt=n;
|
|
}
|
|
|
|
n=0;
|
|
while ((q=p->firstchild) != 0)
|
|
{
|
|
breakparent(q);
|
|
itsi->sort_table[n++]=q;
|
|
}
|
|
|
|
qsort(itsi->sort_table, n, sizeof(struct imap_refmsg *), cmp_msgs);
|
|
|
|
for (i=0; i<n; i++)
|
|
linkparent(itsi->sort_table[i], p);
|
|
return (0);
|
|
}
|
|
|
|
struct imap_refmsg *rfc822_thread(struct imap_refmsgtable *mt)
|
|
{
|
|
if (!mt->rootptr)
|
|
{
|
|
rfc822_threadprune(mt);
|
|
if ((mt->rootptr=rfc822_threadgetroot(mt)) == 0)
|
|
return (0);
|
|
if (rfc822_threadsortsubj(mt, mt->rootptr) ||
|
|
rfc822_threadmergesubj(mt, mt->rootptr) ||
|
|
rfc822_threadsortbydate(mt))
|
|
{
|
|
mt->rootptr=0;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (mt->rootptr);
|
|
}
|