1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message threading. TODO thread handling needs rewrite, m_collapsed must go
3 *
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-4-Clause
7 */
8 /*
9 * Copyright (c) 2004
10 * Gunnar Ritter. All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by Gunnar Ritter
23 * and his contributors.
24 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 */
40 #undef su_FILE
41 #define su_FILE thread
42 #define mx_SOURCE
43
44 #ifndef mx_HAVE_AMALGAMATION
45 # include "mx/nail.h"
46 #endif
47
48 #include <su/cs.h>
49 #include <su/mem.h>
50 #include <su/prime.h>
51
52 #include "mx/names.h"
53
54 /* TODO fake */
55 #include "su/code-in.h"
56
57 /* Open addressing is used for Message-IDs because the maximum number of
58 * messages in the table is known in advance (== msgCount) */
59 struct mitem {
60 struct message *mi_data;
61 char *mi_id;
62 };
63 #define NOT_AN_ID ((struct mitem*)-1)
64
65 struct msort {
66 union {
67 #ifdef mx_HAVE_SPAM
68 u32 ms_ui;
69 #endif
70 long ms_long;
71 char *ms_char;
72 } ms_u;
73 int ms_n;
74 };
75
76 /* Return the hash value for a message id modulo mprime, or mprime if the
77 * passed string does not look like a message-id */
78 static u32 _mhash(char const *cp, u32 mprime);
79
80 /* Look up a message id. Returns NOT_AN_ID if the passed string does not look
81 * like a message-id */
82 static struct mitem * _mlook(char *id, struct mitem *mt,
83 struct message *mdata, u32 mprime);
84
85 /* Child is to be adopted by parent. A thread tree is structured as follows:
86 *
87 * ------ m_child ------ m_child
88 * | |-------------------->| |------------------------> . . .
89 * | |<--------------------| |<----------------------- . . .
90 * ------ m_parent ------ m_parent
91 * ^^ | ^
92 * | \____ m_younger | |
93 * | \ | |
94 * | ---- | |
95 * | \ | | m_elder
96 * | m_parent ---- | |
97 * \ | |
98 * ---- | |
99 * \ + |
100 * ------ m_child
101 * | |------------------------> . . .
102 * | |<----------------------- . . .
103 * ------ m_parent
104 * | ^
105 * . . .
106 *
107 * The base message of a thread does not have a m_parent link. Elements
108 * connected by m_younger/m_elder links are replies to the same message, which
109 * is connected to them by m_parent links. The first reply to a message gets
110 * the m_child link */
111 static void _adopt(struct message *parent, struct message *child,
112 int dist);
113
114 /* Connect all msgs on the lowest thread level with m_younger/m_elder links */
115 static struct message * _interlink(struct message *m, u32 cnt, int nmail);
116
117 static void _finalize(struct message *mp);
118
119 /* Several sort comparison PTFs */
120 #ifdef mx_HAVE_SPAM
121 static int _mui32lt(void const *a, void const *b);
122 #endif
123 static int _mlonglt(void const *a, void const *b);
124 static int _mcharlt(void const *a, void const *b);
125
126 static void _lookup(struct message *m, struct mitem *mi,
127 u32 mprime);
128 static void _makethreads(struct message *m, u32 cnt, int nmail);
129 static int _colpt(int *msgvec, int cl);
130 static void _colps(struct message *b, int cl);
131 static void _colpm(struct message *m, int cl, int *cc, int *uncc);
132
133 static u32
_mhash(char const * cp,u32 mprime)134 _mhash(char const *cp, u32 mprime)
135 {
136 u32 h = 0, g, at = 0;
137 NYD2_IN;
138
139 for (--cp; *++cp != '\0';) {
140 /* Pay attention not to hash characters which are irrelevant for
141 * Message-ID semantics */
142 if (*cp == '(') {
143 cp = skip_comment(cp + 1) - 1;
144 continue;
145 }
146 if (*cp == '"' || *cp == '\\')
147 continue;
148 if (*cp == '@')
149 ++at;
150 /* TODO torek hash */
151 h = ((h << 4) & 0xffffffff) + su_cs_to_lower(*cp);
152 if ((g = h & 0xf0000000) != 0) {
153 h = h ^ (g >> 24);
154 h = h ^ g;
155 }
156 }
157 NYD2_OU;
158 return (at ? h % mprime : mprime);
159 }
160
161 static struct mitem *
_mlook(char * id,struct mitem * mt,struct message * mdata,u32 mprime)162 _mlook(char *id, struct mitem *mt, struct message *mdata, u32 mprime)
163 {
164 struct mitem *mp = NULL;
165 u32 h, c, n = 0;
166 NYD2_IN;
167
168 if (id == NULL) {
169 if ((id = hfield1("message-id", mdata)) == NULL)
170 goto jleave;
171 /* Normalize, what hfield1() doesn't do (TODO should now GREF, too!!) */
172 if (id[0] == '<') {
173 id[su_cs_len(id) -1] = '\0';
174 if (*id != '\0')
175 ++id;
176 }
177 }
178
179 if (mdata != NULL && mdata->m_idhash)
180 h = ~mdata->m_idhash;
181 else {
182 h = _mhash(id, mprime);
183 if (h == mprime) {
184 mp = NOT_AN_ID;
185 goto jleave;
186 }
187 }
188
189 mp = mt + (c = h);
190 while (mp->mi_id != NULL) {
191 if (!msgidcmp(mp->mi_id, id))
192 break;
193 c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
194 ++n;
195 if ((s32)c < 0)
196 c = 0;
197 else while (c >= mprime)
198 c -= mprime;
199 mp = mt + c;
200 }
201
202 if (mdata != NULL && mp->mi_id == NULL) {
203 mp->mi_id = su_cs_dup(id, 0);
204 mp->mi_data = mdata;
205 mdata->m_idhash = ~h;
206 }
207 if (mp->mi_id == NULL)
208 mp = NULL;
209 jleave:
210 NYD2_OU;
211 return mp;
212 }
213
214 static void
_adopt(struct message * parent,struct message * child,int dist)215 _adopt(struct message *parent, struct message *child, int dist)
216 {
217 struct message *mp, *mq;
218 NYD2_IN;
219
220 for (mp = parent; mp != NULL; mp = mp->m_parent)
221 if (mp == child)
222 goto jleave;
223
224 child->m_level = dist; /* temporarily store distance */
225 child->m_parent = parent;
226
227 if (parent->m_child != NULL) {
228 mq = NULL;
229 for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
230 if (mp->m_date >= child->m_date) {
231 if (mp->m_elder != NULL)
232 mp->m_elder->m_younger = child;
233 child->m_elder = mp->m_elder;
234 mp->m_elder = child;
235 child->m_younger = mp;
236 if (mp == parent->m_child)
237 parent->m_child = child;
238 goto jleave;
239 }
240 mq = mp;
241 }
242 mq->m_younger = child;
243 child->m_elder = mq;
244 } else
245 parent->m_child = child;
246 jleave:
247 NYD2_OU;
248 }
249
250 static struct message *
_interlink(struct message * m,u32 cnt,int nmail)251 _interlink(struct message *m, u32 cnt, int nmail)
252 {
253 struct message *root;
254 u32 n;
255 struct msort *ms;
256 int i, autocollapse;
257 NYD2_IN;
258
259 autocollapse = (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL) &&
260 ok_blook(autocollapse));
261 ms = n_alloc(sizeof *ms * cnt);
262
263 for (n = 0, i = 0; UCMP(32, i, <, cnt); ++i) {
264 if (m[i].m_parent == NULL) {
265 if (autocollapse)
266 _colps(m + i, 1);
267 ms[n].ms_u.ms_long = m[i].m_date;
268 ms[n].ms_n = i;
269 ++n;
270 }
271 }
272
273 if (n > 0) {
274 qsort(ms, n, sizeof *ms, &_mlonglt);
275 root = m + ms[0].ms_n;
276 for (i = 1; UCMP(32, i, <, n); ++i) {
277 m[ms[i-1].ms_n].m_younger = m + ms[i].ms_n;
278 m[ms[i].ms_n].m_elder = m + ms[i - 1].ms_n;
279 }
280 } else
281 root = NULL;
282
283 n_free(ms);
284 NYD2_OU;
285 return root;
286 }
287
288 static void
_finalize(struct message * mp)289 _finalize(struct message *mp)
290 {
291 long n;
292 NYD2_IN;
293
294 for (n = 0; mp; mp = next_in_thread(mp)) {
295 mp->m_threadpos = ++n;
296 mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
297 }
298 NYD2_OU;
299 }
300
301 #ifdef mx_HAVE_SPAM
302 static int
_mui32lt(void const * a,void const * b)303 _mui32lt(void const *a, void const *b)
304 {
305 struct msort const *xa = a, *xb = b;
306 int i;
307 NYD2_IN;
308
309 i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
310 if (i == 0)
311 i = xa->ms_n - xb->ms_n;
312 NYD2_OU;
313 return i;
314 }
315 #endif
316
317 static int
_mlonglt(void const * a,void const * b)318 _mlonglt(void const *a, void const *b)
319 {
320 struct msort const *xa = a, *xb = b;
321 int i;
322 NYD2_IN;
323
324 i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
325 if (i == 0)
326 i = xa->ms_n - xb->ms_n;
327 NYD2_OU;
328 return i;
329 }
330
331 static int
_mcharlt(void const * a,void const * b)332 _mcharlt(void const *a, void const *b)
333 {
334 struct msort const *xa = a, *xb = b;
335 int i;
336 NYD2_IN;
337
338 i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
339 if (i == 0)
340 i = xa->ms_n - xb->ms_n;
341 NYD2_OU;
342 return i;
343 }
344
345 static void
_lookup(struct message * m,struct mitem * mi,u32 mprime)346 _lookup(struct message *m, struct mitem *mi, u32 mprime)
347 {
348 struct mx_name *np;
349 struct mitem *ip;
350 char *cp;
351 long dist;
352 NYD2_IN;
353
354 if (m->m_flag & MHIDDEN)
355 goto jleave;
356
357 dist = 1;
358 if ((cp = hfield1("in-reply-to", m)) != NULL) {
359 if ((np = extract(cp, GREF)) != NULL)
360 do {
361 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
362 ip != NOT_AN_ID) {
363 _adopt(ip->mi_data, m, 1);
364 goto jleave;
365 }
366 } while ((np = np->n_flink) != NULL);
367 }
368
369 if ((cp = hfield1("references", m)) != NULL) {
370 if ((np = extract(cp, GREF)) != NULL) {
371 while (np->n_flink != NULL)
372 np = np->n_flink;
373 do {
374 if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
375 if (ip == NOT_AN_ID)
376 continue; /* skip dist++ */
377 _adopt(ip->mi_data, m, dist);
378 goto jleave;
379 }
380 ++dist;
381 } while ((np = np->n_blink) != NULL);
382 }
383 }
384 jleave:
385 NYD2_OU;
386 }
387
388 static void
_makethreads(struct message * m,u32 cnt,int nmail)389 _makethreads(struct message *m, u32 cnt, int nmail)
390 {
391 struct mitem *mt;
392 char *cp;
393 u32 i, mprime;
394 NYD2_IN;
395
396 if (cnt == 0)
397 goto jleave;
398
399 /* It is performance crucial to space this large enough in order to minimize
400 * bucket sharing */
401 mprime = su_prime_lookup_next((cnt < U32_MAX >> 3) ? cnt << 2 : cnt);
402 mt = n_calloc(mprime, sizeof *mt);
403
404 srelax_hold();
405
406 for (i = 0; i < cnt; ++i) {
407 if (!(m[i].m_flag & MHIDDEN)) {
408 _mlook(NULL, mt, m + i, mprime);
409 if (m[i].m_date == 0) {
410 if ((cp = hfield1("date", m + i)) != NULL)
411 m[i].m_date = rfctime(cp);
412 }
413 }
414 m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
415 m[i].m_level = 0;
416 if (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL))
417 m[i].m_collapsed = 0;
418 srelax();
419 }
420
421 /* Most folders contain the eldest messages first. Traversing them in
422 * descending order makes it more likely that younger brothers are found
423 * first, so elder ones can be prepended to the brother list, which is
424 * faster. The worst case is still in O(n^2) and occurs when all but one
425 * messages in a folder are replies to the one message, and are sorted such
426 * that youngest messages occur first */
427 for (i = cnt; i > 0; --i) {
428 _lookup(m + i - 1, mt, mprime);
429 srelax();
430 }
431
432 srelax_rele();
433
434 threadroot = _interlink(m, cnt, nmail);
435 _finalize(threadroot);
436
437 for (i = 0; i < mprime; ++i)
438 if (mt[i].mi_id != NULL)
439 n_free(mt[i].mi_id);
440
441 n_free(mt);
442 mb.mb_threaded = 1;
443 jleave:
444 NYD2_OU;
445 }
446
447 static int
_colpt(int * msgvec,int cl)448 _colpt(int *msgvec, int cl)
449 {
450 int *ip, rv;
451 NYD2_IN;
452
453 if (mb.mb_threaded != 1) {
454 fputs("Not in threaded mode.\n", n_stdout);
455 rv = 1;
456 } else {
457 for (ip = msgvec; *ip != 0; ++ip)
458 _colps(message + *ip - 1, cl);
459 rv = 0;
460 }
461 NYD2_OU;
462 return rv;
463 }
464
465 static void
_colps(struct message * b,int cl)466 _colps(struct message *b, int cl)
467 {
468 struct message *m;
469 int cc = 0, uncc = 0;
470 NYD2_IN;
471
472 if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
473 goto jleave;
474
475 if (b->m_child != NULL) {
476 m = b->m_child;
477 _colpm(m, cl, &cc, &uncc);
478 for (m = m->m_younger; m != NULL; m = m->m_younger)
479 _colpm(m, cl, &cc, &uncc);
480 }
481
482 if (cl) {
483 b->m_collapsed = -cc;
484 for (m = b->m_parent; m != NULL; m = m->m_parent)
485 if (m->m_collapsed <= -uncc) {
486 m->m_collapsed += uncc;
487 break;
488 }
489 } else {
490 if (b->m_collapsed > 0) {
491 b->m_collapsed = 0;
492 ++uncc;
493 }
494 for (m = b; m != NULL; m = m->m_parent)
495 if (m->m_collapsed <= -uncc) {
496 m->m_collapsed += uncc;
497 break;
498 }
499 }
500 jleave:
501 NYD2_OU;
502 }
503
504 static void
_colpm(struct message * m,int cl,int * cc,int * uncc)505 _colpm(struct message *m, int cl, int *cc, int *uncc)
506 {
507 NYD2_IN;
508 if (cl) {
509 if (m->m_collapsed > 0)
510 ++(*uncc);
511 if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
512 m->m_collapsed = 1;
513 if (m->m_collapsed > 0)
514 ++(*cc);
515 } else {
516 if (m->m_collapsed > 0) {
517 m->m_collapsed = 0;
518 ++(*uncc);
519 }
520 }
521
522 if (m->m_child != NULL) {
523 m = m->m_child;
524 _colpm(m, cl, cc, uncc);
525 for (m = m->m_younger; m != NULL; m = m->m_younger)
526 _colpm(m, cl, cc, uncc);
527 }
528 NYD2_OU;
529 }
530
531 FL int
c_thread(void * vp)532 c_thread(void *vp)
533 {
534 int rv;
535 NYD_IN;
536
537 if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
538 #ifdef mx_HAVE_IMAP
539 if (mb.mb_type == MB_IMAP)
540 imap_getheaders(1, msgCount);
541 #endif
542 _makethreads(message, msgCount, (vp == (void*)-1));
543 if (mb.mb_sorted != NULL)
544 n_free(mb.mb_sorted);
545 mb.mb_sorted = su_cs_dup("thread", 0);
546 }
547
548 if (vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
549 ok_blook(header))
550 rv = print_header_group(vp);
551 else
552 rv = 0;
553 NYD_OU;
554 return rv;
555 }
556
557 FL int
c_unthread(void * vp)558 c_unthread(void *vp)
559 {
560 struct message *m;
561 int rv;
562 NYD_IN;
563
564 mb.mb_threaded = 0;
565 if (mb.mb_sorted != NULL)
566 n_free(mb.mb_sorted);
567 mb.mb_sorted = NULL;
568
569 for (m = message; PCMP(m, <, message + msgCount); ++m)
570 m->m_collapsed = 0;
571
572 if (vp && !(n_pstate & n_PS_HOOK_MASK) && ok_blook(header))
573 rv = print_header_group(vp);
574 else
575 rv = 0;
576 NYD_OU;
577 return rv;
578 }
579
580 FL struct message *
next_in_thread(struct message * mp)581 next_in_thread(struct message *mp)
582 {
583 struct message *rv;
584 NYD2_IN;
585
586 if ((rv = mp->m_child) != NULL)
587 goto jleave;
588 if ((rv = mp->m_younger) != NULL)
589 goto jleave;
590
591 while ((rv = mp->m_parent) != NULL) {
592 mp = rv;
593 if ((rv = rv->m_younger) != NULL)
594 goto jleave;
595 }
596 jleave:
597 NYD2_OU;
598 return rv;
599 }
600
601 FL struct message *
prev_in_thread(struct message * mp)602 prev_in_thread(struct message *mp)
603 {
604 struct message *rv;
605 NYD2_IN;
606
607 if ((rv = mp->m_elder) != NULL) {
608 for (mp = rv; (rv = mp->m_child) != NULL;) {
609 mp = rv;
610 while ((rv = mp->m_younger) != NULL)
611 mp = rv;
612 }
613 rv = mp;
614 goto jleave;
615 }
616 rv = mp->m_parent;
617 jleave:
618 NYD2_OU;
619 return rv;
620 }
621
622 FL struct message *
this_in_thread(struct message * mp,long n)623 this_in_thread(struct message *mp, long n)
624 {
625 struct message *rv;
626 NYD2_IN;
627
628 if (n == -1) { /* find end of thread */
629 while (mp != NULL) {
630 if ((rv = mp->m_younger) != NULL) {
631 mp = rv;
632 continue;
633 }
634 rv = next_in_thread(mp);
635 if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
636 rv = mp;
637 goto jleave;
638 }
639 mp = rv;
640 }
641 rv = mp;
642 goto jleave;
643 }
644
645 while (mp != NULL && mp->m_threadpos < n) {
646 if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
647 mp = rv;
648 continue;
649 }
650 mp = next_in_thread(mp);
651 }
652 rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
653 jleave:
654 NYD2_OU;
655 return rv;
656 }
657
658 FL int
c_sort(void * vp)659 c_sort(void *vp)
660 {
661 enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
662 SORT_TO, SORT_SPAM, SORT_THREAD} method;
663 struct {
664 char const *me_name;
665 enum method me_method;
666 int (*me_func)(void const *, void const *);
667 } const methnames[] = {
668 {"date", SORT_DATE, &_mlonglt},
669 {"from", SORT_FROM, &_mcharlt},
670 {"to", SORT_TO, &_mcharlt},
671 {"subject", SORT_SUBJECT, &_mcharlt},
672 {"size", SORT_SIZE, &_mlonglt},
673 #ifdef mx_HAVE_SPAM
674 {"spam", SORT_SPAM, &_mui32lt},
675 #endif
676 {"status", SORT_STATUS, &_mlonglt},
677 {"thread", SORT_THREAD, NULL}
678 };
679
680 struct str in, out;
681 char *_args[2], *cp, **args = vp;
682 int msgvec[2], i, n;
683 int (*func)(void const *, void const *);
684 struct msort *ms;
685 struct message *mp;
686 boole showname;
687 NYD_IN;
688
689 if (vp == NULL || vp == (void*)-1) {
690 _args[0] = savestr((mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
691 _args[1] = NULL;
692 args = _args;
693 } else if (args[0] == NULL) {
694 fprintf(n_stdout, "Current sorting criterion is: %s\n",
695 (mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
696 i = 0;
697 goto jleave;
698 }
699
700 i = 0;
701 for (;;) {
702 if (*args[0] != '\0' && su_cs_starts_with(methnames[i].me_name, args[0]))
703 break;
704 if (UCMP(z, ++i, >=, NELEM(methnames))) {
705 n_err(_("Unknown sorting method: %s\n"), args[0]);
706 i = 1;
707 goto jleave;
708 }
709 }
710
711 if (mb.mb_sorted != NULL)
712 n_free(mb.mb_sorted);
713 mb.mb_sorted = su_cs_dup(args[0], 0);
714
715 method = methnames[i].me_method;
716 func = methnames[i].me_func;
717 msgvec[0] = (int)P2UZ(dot - message + 1);
718 msgvec[1] = 0;
719
720 if (method == SORT_THREAD) {
721 i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
722 goto jleave;
723 }
724
725 showname = ok_blook(showname);
726 ms = n_lofi_alloc(sizeof *ms * msgCount);
727 #ifdef mx_HAVE_IMAP
728 switch (method) {
729 case SORT_SUBJECT:
730 case SORT_DATE:
731 case SORT_FROM:
732 case SORT_TO:
733 if (mb.mb_type == MB_IMAP)
734 imap_getheaders(1, msgCount);
735 break;
736 default:
737 break;
738 }
739 #endif
740
741 srelax_hold();
742 for (n = 0, i = 0; i < msgCount; ++i) {
743 mp = message + i;
744 if (!(mp->m_flag & MHIDDEN)) {
745 switch (method) {
746 case SORT_DATE:
747 if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
748 mp->m_date = rfctime(cp);
749 ms[n].ms_u.ms_long = mp->m_date;
750 break;
751 case SORT_STATUS:
752 if (mp->m_flag & MDELETED)
753 ms[n].ms_u.ms_long = 1;
754 else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
755 ms[n].ms_u.ms_long = 90;
756 else if (mp->m_flag & MFLAGGED)
757 ms[n].ms_u.ms_long = 85;
758 else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
759 ms[n].ms_u.ms_long = 70;
760 else if (mp->m_flag & MNEW)
761 ms[n].ms_u.ms_long = 80;
762 else if (mp->m_flag & MREAD)
763 ms[n].ms_u.ms_long = 40;
764 else
765 ms[n].ms_u.ms_long = 60;
766 break;
767 case SORT_SIZE:
768 ms[n].ms_u.ms_long = mp->m_xsize;
769 break;
770 #ifdef mx_HAVE_SPAM
771 case SORT_SPAM:
772 ms[n].ms_u.ms_ui = mp->m_spamscore;
773 break;
774 #endif
775 case SORT_FROM:
776 case SORT_TO:
777 if ((cp = hfield1((method == SORT_FROM ? "from" : "to"), mp)
778 ) != NULL) {
779 ms[n].ms_u.ms_char = su_cs_dup((showname ? realname(cp)
780 : skin(cp)), 0);
781 makelow(ms[n].ms_u.ms_char);
782 } else
783 ms[n].ms_u.ms_char = su_cs_dup(n_empty, 0);
784 break;
785 default:
786 case SORT_SUBJECT:
787 if ((cp = hfield1("subject", mp)) != NULL) {
788 in.s = cp;
789 in.l = su_cs_len(in.s);
790 mime_fromhdr(&in, &out, TD_ICONV);
791 ms[n].ms_u.ms_char = su_cs_dup(subject_re_trim(out.s), 0);
792 n_free(out.s);
793 makelow(ms[n].ms_u.ms_char);
794 } else
795 ms[n].ms_u.ms_char = su_cs_dup(n_empty, 0);
796 break;
797 }
798 ms[n++].ms_n = i;
799 }
800 mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
801 mp->m_level = 0;
802 mp->m_collapsed = 0;
803 srelax();
804 }
805 srelax_rele();
806
807 if (n > 0) {
808 qsort(ms, n, sizeof *ms, func);
809 threadroot = message + ms[0].ms_n;
810 for (i = 1; i < n; ++i) {
811 message[ms[i - 1].ms_n].m_younger = message + ms[i].ms_n;
812 message[ms[i].ms_n].m_elder = message + ms[i - 1].ms_n;
813 }
814 } else
815 threadroot = NULL;
816
817 _finalize(threadroot);
818 mb.mb_threaded = 2;
819
820 switch (method) {
821 case SORT_FROM:
822 case SORT_TO:
823 case SORT_SUBJECT:
824 for (i = 0; i < n; ++i)
825 n_free(ms[i].ms_u.ms_char);
826 /* FALLTHRU */
827 default:
828 break;
829 }
830 n_lofi_free(ms);
831
832 i = ((vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
833 ok_blook(header)) ? print_header_group(msgvec) : 0);
834 jleave:
835 NYD_OU;
836 return i;
837 }
838
839 FL int
c_collapse(void * v)840 c_collapse(void *v)
841 {
842 int rv;
843 NYD_IN;
844
845 rv = _colpt(v, 1);
846 NYD_OU;
847 return rv;
848 }
849
850 FL int
c_uncollapse(void * v)851 c_uncollapse(void *v)
852 {
853 int rv;
854 NYD_IN;
855
856 rv = _colpt(v, 0);
857 NYD_OU;
858 return rv;
859 }
860
861 FL void
uncollapse1(struct message * mp,int always)862 uncollapse1(struct message *mp, int always)
863 {
864 NYD_IN;
865 if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
866 _colps(mp, 0);
867 NYD_OU;
868 }
869
870 #include "su/code-ou.h"
871 /* s-it-mode */
872