2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 static char sccsid
[] = "@(#)thread.c 1.57 (gritter) 3/4/06";
53 * Mail -- a mail program
59 * Open addressing is used for Message-IDs because the maximum number of
60 * messages in the table is known in advance (== msgCount).
63 struct message
*mi_data
;
76 static unsigned mhash(const char *cp
, int mprime
);
77 static struct mitem
*mlook(char *id
, struct mitem
*mt
, struct message
*mdata
,
79 static void adopt(struct message
*parent
, struct message
*child
, int dist
);
80 static struct message
*interlink(struct message
*m
, long count
, int newmail
);
81 static void finalize(struct message
*mp
);
82 static int mlonglt(const void *a
, const void *b
);
83 static int mfloatlt(const void *a
, const void *b
);
84 static int mcharlt(const void *a
, const void *b
);
85 static void lookup(struct message
*m
, struct mitem
*mi
, int mprime
);
86 static void makethreads(struct message
*m
, long count
, int newmail
);
87 static char *skipre(const char *cp
);
88 static int colpt(int *msgvec
, int cl
);
89 static void colps(struct message
*b
, int cl
);
90 static void colpm(struct message
*m
, int cl
, int *cc
, int *uc
);
93 * Return the hash value for a message id modulo mprime, or mprime
94 * if the passed string does not look like a message-id.
97 mhash(const char *cp
, int mprime
)
100 unsigned h
= 0, g
, at
= 0;
105 * Pay attention not to hash characters which are
106 * irrelevant for Message-ID semantics.
109 cp
= skip_comment(&cp
[1]) - 1;
112 if (*cp
== '"' || *cp
== '\\')
116 h
= ((h
<< 4) & 0xffffffff) + lowerconv(*cp
& 0377);
117 if ((g
= h
& 0xf0000000) != 0) {
122 return at
? h
% (unsigned int)mprime
: (unsigned int)mprime
;
125 #define NOT_AN_ID ((struct mitem *)-1)
128 * Look up a message id. Returns NOT_AN_ID if the passed string does
129 * not look like a message-id.
131 static struct mitem
*
132 mlook(char *id
, struct mitem
*mt
, struct message
*mdata
, int mprime
)
135 unsigned h
, c
, n
= 0;
137 if (id
== NULL
&& (id
= hfield("message-id", mdata
)) == NULL
)
139 if (mdata
&& mdata
->m_idhash
)
140 h
= ~mdata
->m_idhash
;
142 h
= mhash(id
, mprime
);
143 if (h
== (unsigned int)mprime
)
147 while (mp
->mi_id
!= NULL
) {
148 if (msgidcmp(mp
->mi_id
, id
) == 0)
150 c
+= n
&1 ? -((n
+1)/2) * ((n
+1)/2) : ((n
+1)/2) * ((n
+1)/2);
152 while (c
>= (unsigned int)mprime
)
156 if (mdata
!= NULL
&& mp
->mi_id
== NULL
) {
159 mdata
->m_idhash
= ~h
;
161 return mp
->mi_id
? mp
: NULL
;
165 * Child is to be adopted by parent. A thread tree is structured
168 * ------ m_child ------ m_child
169 * | |-------------------->| |------------------------> . . .
170 * | |<--------------------| |<----------------------- . . .
171 * ------ m_parent ------ m_parent
173 * | \____ m_younger | |
177 * | m_parent ---- | |
182 * | | |------------------------> . . .
183 * | | |<----------------------- . . .
186 * \----- m_younger | |
195 * | |------------------------> . . .
196 * | |<----------------------- . . .
201 * The base message of a thread does not have a m_parent link. Elements
202 * connected by m_younger/m_elder links are replies to the same message,
203 * which is connected to them by m_parent links. The first reply to a
204 * message gets the m_child link.
207 adopt(struct message
*parent
, struct message
*child
, int dist
)
209 struct message
*mp
, *mq
;
211 for (mp
= parent
; mp
; mp
= mp
->m_parent
)
214 child
->m_level
= dist
; /* temporarily store distance */
215 child
->m_parent
= parent
;
216 if (parent
->m_child
!= NULL
) {
218 for (mp
= parent
->m_child
; mp
; mp
= mp
->m_younger
) {
219 if (mp
->m_date
>= child
->m_date
) {
221 mp
->m_elder
->m_younger
= child
;
222 child
->m_elder
= mp
->m_elder
;
224 child
->m_younger
= mp
;
225 if (mp
== parent
->m_child
)
226 parent
->m_child
= child
;
231 mq
->m_younger
= child
;
234 parent
->m_child
= child
;
238 * Connect all messages on the lowest thread level with m_younger/m_elder
241 static struct message
*
242 interlink(struct message
*m
, long count
, int newmail
)
247 struct message
*root
;
248 int autocollapse
= !newmail
&& !(inhook
&2) &&
249 value("autocollapse") != NULL
;
251 ms
= smalloc(sizeof *ms
* count
);
252 for (n
= 0, i
= 0; i
< count
; i
++) {
253 if (m
[i
].m_parent
== NULL
) {
256 ms
[n
].ms_u
.ms_long
= m
[i
].m_date
;
262 qsort(ms
, n
, sizeof *ms
, mlonglt
);
263 root
= &m
[ms
[0].ms_n
];
264 for (i
= 1; i
< n
; i
++) {
265 m
[ms
[i
-1].ms_n
].m_younger
= &m
[ms
[i
].ms_n
];
266 m
[ms
[i
].ms_n
].m_elder
= &m
[ms
[i
-1].ms_n
];
275 finalize(struct message
*mp
)
279 for (n
= 0; mp
; mp
= next_in_thread(mp
)) {
280 mp
->m_threadpos
= ++n
;
281 mp
->m_level
= mp
->m_parent
?
282 mp
->m_level
+ mp
->m_parent
->m_level
: 0;
287 mlonglt(const void *a
, const void *b
)
291 i
= ((struct msort
*)a
)->ms_u
.ms_long
-
292 ((struct msort
*)b
)->ms_u
.ms_long
;
294 i
= ((struct msort
*)a
)->ms_n
- ((struct msort
*)b
)->ms_n
;
299 mfloatlt(const void *a
, const void *b
)
303 i
= ((struct msort
*)a
)->ms_u
.ms_float
-
304 ((struct msort
*)b
)->ms_u
.ms_float
;
306 i
= ((struct msort
*)a
)->ms_n
- ((struct msort
*)b
)->ms_n
;
307 return i
> 0 ? 1 : i
< 0 ? -1 : 0;
311 mcharlt(const void *a
, const void *b
)
315 i
= strcoll(((struct msort
*)a
)->ms_u
.ms_char
,
316 ((struct msort
*)b
)->ms_u
.ms_char
);
318 i
= ((struct msort
*)a
)->ms_n
- ((struct msort
*)b
)->ms_n
;
324 lookup(struct message
*m
, struct mitem
*mi
, int mprime
)
331 if (m
->m_flag
& MHIDDEN
)
334 if ((cp
= hfield("in-reply-to", m
)) != NULL
) {
335 if ((np
= extract(cp
, GREF
)) != NULL
)
337 if ((ip
= mlook(np
->n_name
, mi
, NULL
, mprime
))
338 != NULL
&& ip
!= NOT_AN_ID
) {
339 adopt(ip
->mi_data
, m
, 1);
342 } while ((np
= np
->n_flink
) != NULL
);
344 if ((cp
= hfield("references", m
)) != NULL
) {
345 if ((np
= extract(cp
, GREF
)) != NULL
) {
346 while (np
->n_flink
!= NULL
)
349 if ((ip
= mlook(np
->n_name
, mi
, NULL
, mprime
))
352 continue; /* skip dist++ */
353 adopt(ip
->mi_data
, m
, dist
);
357 } while ((np
= np
->n_blink
) != NULL
);
363 makethreads(struct message
*m
, long count
, int newmail
)
371 mprime
= nextprime(count
);
372 mt
= scalloc(mprime
, sizeof *mt
);
373 for (i
= 0; i
< count
; i
++) {
374 if ((m
[i
].m_flag
&MHIDDEN
) == 0) {
375 mlook(NULL
, mt
, &m
[i
], mprime
);
376 if (m
[i
].m_date
== 0) {
377 if ((cp
= hfield("date", &m
[i
])) != NULL
)
378 m
[i
].m_date
= rfctime(cp
);
381 m
[i
].m_child
= m
[i
].m_younger
= m
[i
].m_elder
=
382 m
[i
].m_parent
= NULL
;
384 if (!newmail
&& !(inhook
&2))
385 m
[i
].m_collapsed
= 0;
388 * Most folders contain the eldest messages first. Traversing
389 * them in descending order makes it more likely that younger
390 * brothers are found first, so elder ones can be prepended to
391 * the brother list, which is faster. The worst case is still
392 * in O(n^2) and occurs when all but one messages in a folder
393 * are replies to the one message, and are sorted such that
394 * youngest messages occur first.
396 for (i
= count
-1; i
>= 0; i
--)
397 lookup(&m
[i
], mt
, mprime
);
398 threadroot
= interlink(m
, count
, newmail
);
399 finalize(threadroot
);
407 if (mb
.mb_threaded
!= 1 || vp
== NULL
|| vp
== (void *)-1) {
408 if (mb
.mb_type
== MB_IMAP
)
409 imap_getheaders(1, msgCount
);
410 makethreads(message
, msgCount
, vp
== (void *)-1);
412 mb
.mb_sorted
= sstrdup("thread");
414 if (vp
&& vp
!= (void *)-1 && !inhook
&& value("header"))
427 for (m
= &message
[0]; m
< &message
[msgCount
]; m
++)
429 if (vp
&& !inhook
&& value("header"))
435 next_in_thread(struct message
*mp
)
440 return mp
->m_younger
;
441 while (mp
->m_parent
) {
442 if (mp
->m_parent
->m_younger
)
443 return mp
->m_parent
->m_younger
;
450 prev_in_thread(struct message
*mp
)
454 while (mp
->m_child
) {
456 while (mp
->m_younger
)
465 this_in_thread(struct message
*mp
, long n
)
469 if (n
== -1) { /* find end of thread */
475 mq
= next_in_thread(mp
);
476 if (mq
== NULL
|| mq
->m_threadpos
< mp
->m_threadpos
)
482 while (mp
&& mp
->m_threadpos
< n
) {
483 if (mp
->m_younger
&& mp
->m_younger
->m_threadpos
<= n
) {
487 mp
= next_in_thread(mp
);
489 return mp
&& mp
->m_threadpos
== n
? mp
: NULL
;
493 * Sorted mode is internally just a variant of threaded mode with all
494 * m_parent and m_child links being NULL.
511 enum method me_method
;
512 int (*me_func
)(const void *, const void *);
514 { "date", SORT_DATE
, mlonglt
},
515 { "from", SORT_FROM
, mcharlt
},
516 { "to", SORT_TO
, mcharlt
},
517 { "subject", SORT_SUBJECT
, mcharlt
},
518 { "size", SORT_SIZE
, mlonglt
},
519 { "status", SORT_STATUS
, mlonglt
},
520 { "score", SORT_SCORE
, mfloatlt
},
521 { "thread", SORT_THREAD
, NULL
},
524 char **args
= (char **)vp
, *cp
, *_args
[2];
525 int (*func
)(const void *, const void *);
529 int showname
= value("showname") != NULL
;
532 msgvec
[0] = dot
- &message
[0] + 1;
534 if (vp
== NULL
|| vp
== (void *)-1) {
535 _args
[0] = savestr(mb
.mb_sorted
);
538 } else if (args
[0] == NULL
) {
539 printf("Current sorting criterion is: %s\n",
540 mb
.mb_sorted
? mb
.mb_sorted
: "unsorted");
543 for (i
= 0; methnames
[i
].me_name
; i
++)
544 if (*args
[0] && is_prefix(args
[0], methnames
[i
].me_name
))
546 if (methnames
[i
].me_name
== NULL
) {
547 fprintf(stderr
, "Unknown sorting method \"%s\"\n", args
[0]);
550 method
= methnames
[i
].me_method
;
551 func
= methnames
[i
].me_func
;
553 mb
.mb_sorted
= sstrdup(args
[0]);
554 if (method
== SORT_THREAD
)
555 return thread(vp
&& vp
!= (void *)-1 ? msgvec
: vp
);
556 ms
= ac_alloc(sizeof *ms
* msgCount
);
562 if (mb
.mb_type
== MB_IMAP
)
563 imap_getheaders(1, msgCount
);
568 for (n
= 0, i
= 0; i
< msgCount
; i
++) {
570 if ((mp
->m_flag
&MHIDDEN
) == 0) {
573 if (mp
->m_date
== 0 &&
574 (cp
= hfield("date", mp
)) != 0)
575 mp
->m_date
= rfctime(cp
);
576 ms
[n
].ms_u
.ms_long
= mp
->m_date
;
579 if (mp
->m_flag
& MDELETED
)
580 ms
[n
].ms_u
.ms_long
= 1;
581 else if ((mp
->m_flag
&(MNEW
|MREAD
)) == MNEW
)
582 ms
[n
].ms_u
.ms_long
= 90;
583 else if (mp
->m_flag
& MFLAGGED
)
584 ms
[n
].ms_u
.ms_long
= 85;
585 else if ((mp
->m_flag
&(MNEW
|MBOX
)) == MBOX
)
586 ms
[n
].ms_u
.ms_long
= 70;
587 else if (mp
->m_flag
& MNEW
)
588 ms
[n
].ms_u
.ms_long
= 80;
589 else if (mp
->m_flag
& MREAD
)
590 ms
[n
].ms_u
.ms_long
= 40;
592 ms
[n
].ms_u
.ms_long
= 60;
595 ms
[n
].ms_u
.ms_long
= mp
->m_xsize
;
598 ms
[n
].ms_u
.ms_float
= mp
->m_score
;
602 if ((cp
= hfield(method
== SORT_FROM
?
603 "from" : "to", mp
)) != NULL
) {
604 ms
[n
].ms_u
.ms_char
= showname
?
605 realname(cp
) : skin(cp
);
606 makelow(ms
[n
].ms_u
.ms_char
);
608 ms
[n
].ms_u
.ms_char
= "";
612 if ((cp
= hfield("subject", mp
)) != NULL
) {
615 mime_fromhdr(&in
, &out
, TD_ICONV
);
617 savestr(skipre(out
.s
));
619 makelow(ms
[n
].ms_u
.ms_char
);
621 ms
[n
].ms_u
.ms_char
= "";
626 mp
->m_child
= mp
->m_younger
= mp
->m_elder
= mp
->m_parent
= NULL
;
631 qsort(ms
, n
, sizeof *ms
, func
);
632 threadroot
= &message
[ms
[0].ms_n
];
633 for (i
= 1; i
< n
; i
++) {
634 message
[ms
[i
-1].ms_n
].m_younger
= &message
[ms
[i
].ms_n
];
635 message
[ms
[i
].ms_n
].m_elder
= &message
[ms
[i
-1].ms_n
];
638 threadroot
= &message
[0];
639 finalize(threadroot
);
642 return vp
&& vp
!= (void *)-1 && !inhook
&&
643 value("header") ? headers(msgvec
) : 0;
647 skipre(const char *cp
)
649 if (lowerconv(cp
[0]&0377) == 'r' &&
650 lowerconv(cp
[1]&0377) == 'e' &&
652 spacechar(cp
[3]&0377)) {
654 while (spacechar(*cp
&0377))
673 colpt(int *msgvec
, int cl
)
677 if (mb
.mb_threaded
!= 1) {
678 puts("Not in threaded mode.");
681 for (ip
= msgvec
; *ip
!= 0; ip
++)
682 colps(&message
[*ip
-1], cl
);
687 colps(struct message
*b
, int cl
)
692 if (cl
&& (b
->m_collapsed
> 0 || (b
->m_flag
& (MNEW
|MREAD
)) == MNEW
))
696 colpm(m
, cl
, &cc
, &uc
);
697 for (m
= m
->m_younger
; m
; m
= m
->m_younger
)
698 colpm(m
, cl
, &cc
, &uc
);
701 b
->m_collapsed
= -cc
;
702 for (m
= b
->m_parent
; m
; m
= m
->m_parent
)
703 if (m
->m_collapsed
<= -uc
) {
704 m
->m_collapsed
+= uc
;
708 if (b
->m_collapsed
> 0) {
712 for (m
= b
; m
; m
= m
->m_parent
)
713 if (m
->m_collapsed
<= -uc
) {
714 m
->m_collapsed
+= uc
;
721 colpm(struct message
*m
, int cl
, int *cc
, int *uc
)
724 if (m
->m_collapsed
> 0)
726 if ((m
->m_flag
& (MNEW
|MREAD
)) != MNEW
|| m
->m_collapsed
< 0)
728 if (m
->m_collapsed
> 0)
731 if (m
->m_collapsed
> 0) {
738 colpm(m
, cl
, cc
, uc
);
739 for (m
= m
->m_younger
; m
; m
= m
->m_younger
)
740 colpm(m
, cl
, cc
, uc
);
745 uncollapse1(struct message
*m
, int always
)
747 if (mb
.mb_threaded
== 1 && (always
|| m
->m_collapsed
> 0))