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