Introduce enum flock_type for fcntl_lock() (drop fcntl.h)
[s-mailx.git] / thread.c
blobd6e8d8b6501830d56894c9312b057b13164f75a5
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message threading.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 2004
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 /* Open addressing is used for Message-IDs because the maximum number of
45 * messages in the table is known in advance (== msgCount) */
46 struct mitem {
47 struct message *mi_data;
48 char *mi_id;
50 #define NOT_AN_ID ((struct mitem*)-1)
52 struct msort {
53 union {
54 #ifdef HAVE_SPAM
55 ui32_t ms_ui;
56 #endif
57 long ms_long;
58 char *ms_char;
59 } ms_u;
60 int ms_n;
63 /* Return the hash value for a message id modulo mprime, or mprime if the
64 * passed string does not look like a message-id */
65 static ui32_t _mhash(char const *cp, ui32_t mprime);
67 /* Look up a message id. Returns NOT_AN_ID if the passed string does not look
68 * like a message-id */
69 static struct mitem * _mlook(char *id, struct mitem *mt,
70 struct message *mdata, ui32_t mprime);
72 /* Child is to be adopted by parent. A thread tree is structured as follows:
74 * ------ m_child ------ m_child
75 * | |-------------------->| |------------------------> . . .
76 * | |<--------------------| |<----------------------- . . .
77 * ------ m_parent ------ m_parent
78 * ^^ | ^
79 * | \____ m_younger | |
80 * | \ | |
81 * | ---- | |
82 * | \ | | m_elder
83 * | m_parent ---- | |
84 * \ | |
85 * ---- | |
86 * \ + |
87 * ------ m_child
88 * | |------------------------> . . .
89 * | |<----------------------- . . .
90 * ------ m_parent
91 * | ^
92 * . . .
94 * The base message of a thread does not have a m_parent link. Elements
95 * connected by m_younger/m_elder links are replies to the same message, which
96 * is connected to them by m_parent links. The first reply to a message gets
97 * the m_child link */
98 static void _adopt(struct message *parent, struct message *child,
99 int dist);
101 /* Connect all msgs on the lowest thread level with m_younger/m_elder links */
102 static struct message * _interlink(struct message *m, ui32_t cnt, int nmail);
104 static void _finalize(struct message *mp);
106 /* Several sort comparison PTFs */
107 #ifdef HAVE_SPAM
108 static int _mui32lt(void const *a, void const *b);
109 #endif
110 static int _mlonglt(const void *a, const void *b);
111 static int _mcharlt(const void *a, const void *b);
113 static void _lookup(struct message *m, struct mitem *mi,
114 ui32_t mprime);
115 static void _makethreads(struct message *m, ui32_t cnt, int nmail);
116 static char const * _skipre(char const *cp);
117 static int _colpt(int *msgvec, int cl);
118 static void _colps(struct message *b, int cl);
119 static void _colpm(struct message *m, int cl, int *cc, int *uc);
121 static ui32_t
122 _mhash(char const *cp, ui32_t mprime)
124 ui32_t h = 0, g, at = 0;
125 NYD_ENTER;
127 for (--cp; *++cp;) {
128 /* Pay attention not to hash characters which are irrelevant for
129 * Message-ID semantics */
130 if (*cp == '(') {
131 cp = skip_comment(&cp[1]) - 1;
132 continue;
134 if (*cp == '"' || *cp == '\\')
135 continue;
136 if (*cp == '@')
137 at++;
138 /* TODO torek hash */
139 h = ((h << 4) & 0xffffffff) + lowerconv(*cp);
140 if ((g = h & 0xf0000000) != 0) {
141 h = h ^ (g >> 24);
142 h = h ^ g;
145 NYD_LEAVE;
146 return (at ? h % mprime : mprime);
149 static struct mitem *
150 _mlook(char *id, struct mitem *mt, struct message *mdata, ui32_t mprime)
152 struct mitem *mp = NULL;
153 ui32_t h, c, n = 0;
154 NYD_ENTER;
156 if (id == NULL && (id = hfield1("message-id", mdata)) == NULL)
157 goto jleave;
159 if (mdata && mdata->m_idhash)
160 h = ~mdata->m_idhash;
161 else {
162 h = _mhash(id, mprime);
163 if (h == mprime) {
164 mp = NOT_AN_ID;
165 goto jleave;
169 mp = &mt[c = h];
170 while (mp->mi_id != NULL) {
171 if (msgidcmp(mp->mi_id, id) == 0)
172 break;
173 c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
174 ++n;
175 while (c >= mprime)
176 c -= mprime;
177 mp = &mt[c];
180 if (mdata != NULL && mp->mi_id == NULL) {
181 mp->mi_id = sstrdup(id);
182 mp->mi_data = mdata;
183 mdata->m_idhash = ~h;
185 if (mp->mi_id == NULL)
186 mp = NULL;
187 jleave:
188 NYD_LEAVE;
189 return mp;
192 static void
193 _adopt(struct message *parent, struct message *child, int dist)
195 struct message *mp, *mq;
196 NYD_ENTER;
198 for (mp = parent; mp; mp = mp->m_parent)
199 if (mp == child)
200 goto jleave;
202 child->m_level = dist; /* temporarily store distance */
203 child->m_parent = parent;
205 if (parent->m_child != NULL) {
206 mq = NULL;
207 for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
208 if (mp->m_date >= child->m_date) {
209 if (mp->m_elder != NULL)
210 mp->m_elder->m_younger = child;
211 child->m_elder = mp->m_elder;
212 mp->m_elder = child;
213 child->m_younger = mp;
214 if (mp == parent->m_child)
215 parent->m_child = child;
216 goto jleave;
218 mq = mp;
220 mq->m_younger = child;
221 child->m_elder = mq;
222 } else
223 parent->m_child = child;
224 jleave:
225 NYD_LEAVE;
228 static struct message *
229 _interlink(struct message *m, ui32_t cnt, int nmail)
231 struct message *root;
232 ui32_t n;
233 struct msort *ms;
234 int i, autocollapse;
235 NYD_ENTER;
237 autocollapse = (!nmail && !(inhook & 2) && ok_blook(autocollapse));
238 ms = smalloc(sizeof *ms * cnt);
240 for (n = 0, i = 0; UICMP(32, i, <, cnt); ++i) {
241 if (m[i].m_parent == NULL) {
242 if (autocollapse)
243 _colps(&m[i], 1);
244 ms[n].ms_u.ms_long = m[i].m_date;
245 ms[n].ms_n = i;
246 n++;
250 if (n > 0) {
251 qsort(ms, n, sizeof *ms, &_mlonglt);
252 root = &m[ms[0].ms_n];
253 for (i = 1; UICMP(32, i, <, n); ++i) {
254 m[ms[i-1].ms_n].m_younger = &m[ms[i].ms_n];
255 m[ms[i].ms_n].m_elder = &m[ms[i-1].ms_n];
257 } else
258 root = &m[0];
260 free(ms);
261 NYD_LEAVE;
262 return root;
265 static void
266 _finalize(struct message *mp)
268 long n;
269 NYD_ENTER;
271 for (n = 0; mp; mp = next_in_thread(mp)) {
272 mp->m_threadpos = ++n;
273 mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
275 NYD_LEAVE;
278 #ifdef HAVE_SPAM
279 static int
280 _mui32lt(void const *a, void const *b)
282 struct msort const *xa = a, *xb = b;
283 int i;
284 NYD_ENTER;
286 i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
287 if (i == 0)
288 i = xa->ms_n - xb->ms_n;
289 NYD_LEAVE;
290 return i;
292 #endif
294 static int
295 _mlonglt(const void *a, const void *b)
297 struct msort const *xa = a, *xb = b;
298 int i;
299 NYD_ENTER;
301 i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
302 if (i == 0)
303 i = xa->ms_n - xb->ms_n;
304 NYD_LEAVE;
305 return i;
308 static int
309 _mcharlt(const void *a, const void *b)
311 struct msort const *xa = a, *xb = b;
312 int i;
313 NYD_ENTER;
315 i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
316 if (i == 0)
317 i = xa->ms_n - xb->ms_n;
318 NYD_LEAVE;
319 return i;
322 static void
323 _lookup(struct message *m, struct mitem *mi, ui32_t mprime)
325 struct name *np;
326 struct mitem *ip;
327 char *cp;
328 long dist;
329 NYD_ENTER;
331 if (m->m_flag & MHIDDEN)
332 goto jleave;
334 dist = 1;
335 if ((cp = hfield1("in-reply-to", m)) != NULL) {
336 if ((np = extract(cp, GREF)) != NULL)
337 do {
338 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
339 ip != NOT_AN_ID) {
340 _adopt(ip->mi_data, m, 1);
341 goto jleave;
343 } while ((np = np->n_flink) != NULL);
346 if ((cp = hfield1("references", m)) != NULL) {
347 if ((np = extract(cp, GREF)) != NULL) {
348 while (np->n_flink != NULL)
349 np = np->n_flink;
350 do {
351 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
352 if (ip == NOT_AN_ID)
353 continue; /* skip dist++ */
354 _adopt(ip->mi_data, m, dist);
355 goto jleave;
357 ++dist;
358 } while ((np = np->n_blink) != NULL);
361 jleave:
362 NYD_LEAVE;
365 static void
366 _makethreads(struct message *m, ui32_t cnt, int nmail)
368 struct mitem *mt;
369 char *cp;
370 ui32_t i, mprime;
371 NYD_ENTER;
373 if (cnt == 0)
374 goto jleave;
376 mprime = nextprime(cnt);
377 mt = scalloc(mprime, sizeof *mt);
379 srelax_hold();
381 for (i = 0; i < cnt; ++i) {
382 if (!(m[i].m_flag & MHIDDEN)) {
383 _mlook(NULL, mt, &m[i], mprime);
384 if (m[i].m_date == 0) {
385 if ((cp = hfield1("date", &m[i])) != NULL)
386 m[i].m_date = rfctime(cp);
389 m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
390 m[i].m_level = 0;
391 if (!nmail && !(inhook & 2))
392 m[i].m_collapsed = 0;
393 srelax();
396 /* Most folders contain the eldest messages first. Traversing them in
397 * descending order makes it more likely that younger brothers are found
398 * first, so elder ones can be prepended to the brother list, which is
399 * faster. The worst case is still in O(n^2) and occurs when all but one
400 * messages in a folder are replies to the one message, and are sorted such
401 * that youngest messages occur first */
402 for (i = cnt; i > 0; --i) {
403 _lookup(&m[i - 1], mt, mprime);
404 srelax();
407 srelax_rele();
409 threadroot = _interlink(m, cnt, nmail);
410 _finalize(threadroot);
412 for (i = 0; i < mprime; ++i)
413 if (mt[i].mi_id != NULL)
414 free(mt[i].mi_id);
416 free(mt);
417 mb.mb_threaded = 1;
418 jleave:
419 NYD_LEAVE;
422 static char const *
423 _skipre(char const *cp)
425 NYD_ENTER;
426 if (lowerconv(cp[0]) == 'r' && lowerconv(cp[1]) == 'e' && cp[2] == ':' &&
427 spacechar(cp[3])) {
428 cp = &cp[4];
429 while (spacechar(*cp))
430 ++cp;
432 NYD_LEAVE;
433 return cp;
436 static int
437 _colpt(int *msgvec, int cl)
439 int *ip, rv;
440 NYD_ENTER;
442 if (mb.mb_threaded != 1) {
443 puts("Not in threaded mode.");
444 rv = 1;
445 } else {
446 for (ip = msgvec; *ip != 0; ++ip)
447 _colps(&message[*ip - 1], cl);
448 rv = 0;
450 NYD_LEAVE;
451 return rv;
454 static void
455 _colps(struct message *b, int cl)
457 struct message *m;
458 int cc = 0, uc = 0;
459 NYD_ENTER;
461 if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
462 goto jleave;
464 if (b->m_child != NULL) {
465 m = b->m_child;
466 _colpm(m, cl, &cc, &uc);
467 for (m = m->m_younger; m != NULL; m = m->m_younger)
468 _colpm(m, cl, &cc, &uc);
471 if (cl) {
472 b->m_collapsed = -cc;
473 for (m = b->m_parent; m != NULL; m = m->m_parent)
474 if (m->m_collapsed <= -uc) {
475 m->m_collapsed += uc;
476 break;
478 } else {
479 if (b->m_collapsed > 0) {
480 b->m_collapsed = 0;
481 ++uc;
483 for (m = b; m != NULL; m = m->m_parent)
484 if (m->m_collapsed <= -uc) {
485 m->m_collapsed += uc;
486 break;
489 jleave:
490 NYD_LEAVE;
493 static void
494 _colpm(struct message *m, int cl, int *cc, int *uc)
496 NYD_ENTER;
497 if (cl) {
498 if (m->m_collapsed > 0)
499 ++(*uc);
500 if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
501 m->m_collapsed = 1;
502 if (m->m_collapsed > 0)
503 ++(*cc);
504 } else {
505 if (m->m_collapsed > 0) {
506 m->m_collapsed = 0;
507 ++(*uc);
511 if (m->m_child != NULL) {
512 m = m->m_child;
513 _colpm(m, cl, cc, uc);
514 for (m = m->m_younger; m != NULL; m = m->m_younger)
515 _colpm(m, cl, cc, uc);
517 NYD_LEAVE;
520 FL int
521 c_thread(void *vp)
523 int rv;
524 NYD_ENTER;
526 if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
527 #ifdef HAVE_IMAP
528 if (mb.mb_type == MB_IMAP)
529 imap_getheaders(1, msgCount);
530 #endif
531 _makethreads(message, msgCount, (vp == (void*)-1));
532 if (mb.mb_sorted != NULL)
533 free(mb.mb_sorted);
534 mb.mb_sorted = sstrdup("thread");
537 if (vp != NULL && vp != (void*)-1 && !inhook && ok_blook(header))
538 rv = c_headers(vp);
539 else
540 rv = 0;
541 NYD_LEAVE;
542 return rv;
545 FL int
546 c_unthread(void *vp)
548 struct message *m;
549 int rv;
550 NYD_ENTER;
552 mb.mb_threaded = 0;
553 free(mb.mb_sorted);
554 mb.mb_sorted = NULL;
556 for (m = message; PTRCMP(m, <, message + msgCount); ++m)
557 m->m_collapsed = 0;
559 if (vp && !inhook && ok_blook(header))
560 rv = c_headers(vp);
561 else
562 rv = 0;
563 NYD_LEAVE;
564 return rv;
567 FL struct message *
568 next_in_thread(struct message *mp)
570 struct message *rv;
571 NYD_ENTER;
573 if ((rv = mp->m_child) != NULL)
574 goto jleave;
575 if ((rv = mp->m_younger) != NULL)
576 goto jleave;
578 while ((rv = mp->m_parent) != NULL) {
579 mp = rv;
580 if ((rv = rv->m_younger) != NULL)
581 goto jleave;
583 jleave:
584 NYD_LEAVE;
585 return rv;
588 FL struct message *
589 prev_in_thread(struct message *mp)
591 struct message *rv;
592 NYD_ENTER;
594 if ((rv = mp->m_elder) != NULL) {
595 for (mp = rv; (rv = mp->m_child) != NULL;) {
596 mp = rv;
597 while ((rv = mp->m_younger) != NULL)
598 mp = rv;
600 rv = mp;
601 goto jleave;
603 rv = mp->m_parent;
604 jleave:
605 NYD_LEAVE;
606 return rv;
609 FL struct message *
610 this_in_thread(struct message *mp, long n)
612 struct message *rv;
613 NYD_ENTER;
615 if (n == -1) { /* find end of thread */
616 while (mp != NULL) {
617 if ((rv = mp->m_younger) != NULL) {
618 mp = rv;
619 continue;
621 rv = next_in_thread(mp);
622 if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
623 rv = mp;
624 goto jleave;
626 mp = rv;
628 rv = mp;
629 goto jleave;
632 while (mp != NULL && mp->m_threadpos < n) {
633 if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
634 mp = rv;
635 continue;
637 mp = next_in_thread(mp);
639 rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
640 jleave:
641 NYD_LEAVE;
642 return rv;
645 FL int
646 c_sort(void *vp)
648 enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
649 SORT_TO, SORT_SPAM, SORT_THREAD} method;
650 struct {
651 char const *me_name;
652 enum method me_method;
653 int (*me_func)(void const *, void const *);
654 } const methnames[] = {
655 {"date", SORT_DATE, &_mlonglt},
656 {"from", SORT_FROM, &_mcharlt},
657 {"to", SORT_TO, &_mcharlt},
658 {"subject", SORT_SUBJECT, &_mcharlt},
659 {"size", SORT_SIZE, &_mlonglt},
660 #ifdef HAVE_SPAM
661 {"spam", SORT_SPAM, &_mui32lt},
662 #endif
663 {"status", SORT_STATUS, &_mlonglt},
664 {"thread", SORT_THREAD, NULL}
667 struct str in, out;
668 char **args = (char**)vp, *cp, *_args[2];
669 int (*func)(void const *, void const *);
670 struct msort *ms;
671 int i, n, msgvec[2];
672 struct message *mp;
673 bool_t showname;
674 NYD_ENTER;
676 showname = ok_blook(showname);
677 msgvec[0] = (int)PTR2SIZE(dot - message + 1);
678 msgvec[1] = 0;
680 if (vp == NULL || vp == (void*)-1) {
681 _args[0] = savestr(mb.mb_sorted);
682 _args[1] = NULL;
683 args = _args;
684 } else if (args[0] == NULL) {
685 printf("Current sorting criterion is: %s\n",
686 (mb.mb_sorted ? mb.mb_sorted : "unsorted"));
687 i = 0;
688 goto jleave;
691 for (i = 0; UICMP(z, i, <, NELEM(methnames)); ++i)
692 if (*args[0] && is_prefix(args[0], methnames[i].me_name))
693 goto jmethok;
694 fprintf(stderr, "Unknown sorting method \"%s\"\n", args[0]);
695 i = 1;
696 goto jleave;
698 jmethok:
699 method = methnames[i].me_method;
700 func = methnames[i].me_func;
701 free(mb.mb_sorted);
702 mb.mb_sorted = sstrdup(args[0]);
704 if (method == SORT_THREAD) {
705 i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
706 goto jleave;
709 ms = ac_alloc(sizeof *ms * msgCount);
710 #ifdef HAVE_IMAP
711 switch (method) {
712 case SORT_SUBJECT:
713 case SORT_DATE:
714 case SORT_FROM:
715 case SORT_TO:
716 if (mb.mb_type == MB_IMAP)
717 imap_getheaders(1, msgCount);
718 break;
719 default:
720 break;
722 #endif
724 srelax_hold();
725 for (n = 0, i = 0; i < msgCount; ++i) {
726 mp = message + i;
727 if (!(mp->m_flag & MHIDDEN)) {
728 switch (method) {
729 case SORT_DATE:
730 if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
731 mp->m_date = rfctime(cp);
732 ms[n].ms_u.ms_long = mp->m_date;
733 break;
734 case SORT_STATUS:
735 if (mp->m_flag & MDELETED)
736 ms[n].ms_u.ms_long = 1;
737 else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
738 ms[n].ms_u.ms_long = 90;
739 else if (mp->m_flag & MFLAGGED)
740 ms[n].ms_u.ms_long = 85;
741 else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
742 ms[n].ms_u.ms_long = 70;
743 else if (mp->m_flag & MNEW)
744 ms[n].ms_u.ms_long = 80;
745 else if (mp->m_flag & MREAD)
746 ms[n].ms_u.ms_long = 40;
747 else
748 ms[n].ms_u.ms_long = 60;
749 break;
750 case SORT_SIZE:
751 ms[n].ms_u.ms_long = mp->m_xsize;
752 break;
753 #ifdef HAVE_SPAM
754 case SORT_SPAM:
755 ms[n].ms_u.ms_ui = mp->m_spamscore;
756 break;
757 #endif
758 case SORT_FROM:
759 case SORT_TO:
760 if ((cp = hfield1((method == SORT_FROM ? "from" : "to"), mp)) !=
761 NULL) {
762 ms[n].ms_u.ms_char = sstrdup(showname ? realname(cp) : skin(cp));
763 makelow(ms[n].ms_u.ms_char);
764 } else
765 ms[n].ms_u.ms_char = sstrdup("");
766 break;
767 default:
768 case SORT_SUBJECT:
769 if ((cp = hfield1("subject", mp)) != NULL) {
770 in.s = cp;
771 in.l = strlen(in.s);
772 mime_fromhdr(&in, &out, TD_ICONV);
773 ms[n].ms_u.ms_char = sstrdup(_skipre(out.s));
774 free(out.s);
775 makelow(ms[n].ms_u.ms_char);
776 } else
777 ms[n].ms_u.ms_char = sstrdup("");
778 break;
780 ms[n++].ms_n = i;
782 mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
783 mp->m_level = 0;
784 mp->m_collapsed = 0;
785 srelax();
787 srelax_rele();
789 if (n > 0) {
790 qsort(ms, n, sizeof *ms, func);
791 threadroot = &message[ms[0].ms_n];
792 for (i = 1; i < n; ++i) {
793 message[ms[i-1].ms_n].m_younger = &message[ms[i].ms_n];
794 message[ms[i].ms_n].m_elder = &message[ms[i-1].ms_n];
796 } else
797 threadroot = &message[0];
798 _finalize(threadroot);
799 mb.mb_threaded = 2;
801 switch (method) {
802 case SORT_FROM:
803 case SORT_TO:
804 case SORT_SUBJECT:
805 for (i = 0; i < n; ++i)
806 free(ms[i].ms_u.ms_char);
807 /* FALLTHRU */
808 default:
809 break;
811 ac_free(ms);
812 i = ((vp != NULL && vp != (void*)-1 && !inhook && ok_blook(header))
813 ? c_headers(msgvec) : 0);
814 jleave:
815 NYD_LEAVE;
816 return i;
819 FL int
820 c_collapse(void *v)
822 int rv;
823 NYD_ENTER;
825 rv = _colpt(v, 1);
826 NYD_LEAVE;
827 return rv;
830 FL int
831 c_uncollapse(void *v)
833 int rv;
834 NYD_ENTER;
836 rv = _colpt(v, 0);
837 NYD_LEAVE;
838 return rv;
841 FL void
842 uncollapse1(struct message *mp, int always)
844 NYD_ENTER;
845 if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
846 _colps(mp, 0);
847 NYD_LEAVE;
850 /* vim:set fenc=utf-8:s-it-mode */