1 /* -*- mode: C; mode: fold; -*- */
2 /*
3  This file is part of SLRN.
4  It contains the sorting routines for article mode.
5 
6  Copyright (c) 1994, 1999, 2007-2016 John E. Davis <jed@jedsoft.org>
7  Copyright (c) 2001-2006 Thomas Schultz <tststs@gmx.de>
8 
9  This program is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License as published by the Free
11  Software Foundation; either version 2 of the License, or (at your option)
12  any later version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
17  more details.
18 
19  You should have received a copy of the GNU General Public License along
20  with this program; if not, write to the Free Software Foundation, Inc.,
21  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23 #include "config.h"
24 #include "slrnfeat.h"
25 
26 /*{{{ system include files */
27 #include <stdio.h>
28 #include <string.h>
29 
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33 
34 #ifdef HAVE_STDLIB_H
35 # include <stdlib.h>
36 #endif
37 
38 #include <slang.h>
39 /*}}}*/
40 /*{{{ slrn include files */
41 #include "jdmacros.h"
42 #include "slrn.h"
43 #include "group.h"
44 #include "art.h"
45 #include "art_sort.h"
46 #include "misc.h"
47 #include "menu.h"
48 #include "hash.h"
49 #include "util.h"
50 #include "strutil.h"
51 #include "snprintf.h"
52 #include "hooks.h"
53 #include "common.h"
54 
55 /*}}}*/
56 
57 /*{{{ extern global variables  */
58 int _art_Headers_Threaded = 0;
59 int Slrn_New_Subject_Breaks_Threads = 0;
60 int Slrn_Uncollapse_Threads = 0;
61 
62 /* Sorting mode:
63  *   0 No sorting
64  *   1 sort by threads
65  *   2 sort by subject
66  *   3 sort by subject and threads
67  *   4 sort by score
68  *   5 sort by score and threads
69  *   6 sort by score then by subject
70  *   7 thread, then sort by score then subject
71  *   8 sort by date
72  */
73 #define SORT_BY_THREADS  1
74 #define SORT_BY_SUBJECT  2
75 #define SORT_BY_SCORE   4
76 #define SORT_BY_DATE	8
77 #define SORT_CUSTOM     12
78 
79 int Slrn_Sorting_Mode = (SORT_BY_THREADS|SORT_BY_SUBJECT);
80 
81 /* Custom sorting: */
82 int Slrn_Sort_By_Threads = 0;
83 char *Slrn_Sort_Order=NULL;
84 
85 /*}}}*/
86 /*{{{ static global variables */
87 typedef int (*Header_Cmp_Func_Type)(Slrn_Header_Type *, Slrn_Header_Type *);
88 
89 typedef struct sort_function_type
90 {
91    Header_Cmp_Func_Type fun;
92    int inverse;
93    struct sort_function_type *next;
94 }
95 sort_function_type;
96 
97 static sort_function_type *Sort_Functions=NULL, *Sort_Thread_Functions=NULL;
98 
99 #define ALL_THREAD_FLAGS (FAKE_PARENT | FAKE_CHILDREN | FAKE_HEADER_HIGH_SCORE)
100 /*}}}*/
101 
102 /*{{{ static function declarations */
103 static char *get_current_sort_order (int*);
104 static void recompile_sortorder (char*);
105 static void sort_by_threads (void);
106 static int header_cmp (sort_function_type *sort_function, Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted);
107 static int header_initial_cmp(Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted);
108 static int header_thread_cmp(Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted);
109 
110 /*}}}*/
111 
_art_toggle_sort(void)112 void _art_toggle_sort (void) /*{{{*/
113 {
114    int rsp;
115 
116    rsp = slrn_sbox_sorting_method ();
117    if (rsp != -1)
118      {
119 	Slrn_Sorting_Mode = rsp;
120 	slrn_sort_headers ();
121      }
122 }
123 /*}}}*/
124 
125 /*{{{ Functions that compare headers */
126 
127 /* Helper function to compare two subjects; will be used for sorting and
128  * when linking lost relatives later on */
_art_subject_cmp(char * sa,char * sb)129 int _art_subject_cmp (char *sa, char *sb) /*{{{*/
130 {
131    char *end_a = sa + strlen (sa);
132    char *end_b = sb + strlen (sb);
133    char *was;
134 
135    /* find "(was: ...)" and set end_[ab] */
136    if (NULL != (was = strstr (sa, "(was:"))
137        && (was != sa) && (*(end_a - 1) == ')'))
138      end_a = was;
139 
140    if (NULL != (was = strstr (sb, "(was:"))
141        && (was != sb) && (*(end_b - 1) == ')'))
142      end_b = was;
143 
144    /* skip past re: */
145    while (*sa == ' ') sa++;
146    while (*sb == ' ') sb++;
147 
148    if (((*sa | 0x20) == 'r') && ((*(sa + 1) | 0x20) == 'e')
149        && (*(sa + 2) == ':'))
150      {
151 	sa += 3;
152      }
153 
154    if (((*sb | 0x20) == 'r') && ((*(sb + 1) | 0x20) == 'e')
155        && (*(sb + 2) == ':'))
156      {
157 	sb += 3;
158      }
159 
160    while (1)
161      {
162 	char cha, chb;
163 
164 	while ((cha = *sa) == ' ') sa++;
165 	while ((chb = *sb) == ' ') sb++;
166 
167 	if ((sa == end_a) && (sb == end_b))
168 	  return 0;
169 
170 	/* This hack sorts "(3/31)" before "(25/31)" */
171 	if (isdigit (cha) && isdigit (chb))
172 	  {
173 	     int a = atoi (sa), b = atoi (sb);
174 	     if (a != b)
175 	       return a - b;
176 	  }
177 
178 	cha = UPPER_CASE(cha);
179 	chb = UPPER_CASE(chb);
180 
181 	if (cha != chb)
182 	  return (int) cha - (int) chb;
183 
184 	sa++;
185 	sb++;
186      }
187 }
188 /*}}}*/
189 
190 /* Now the functions for the various sorting criteria.
191  * Default sorting order:
192  * header_subject_cmp	=> Subjects alphabetically a-z, case insensitive
193  * header_date_cmp	=> Oldest articles first
194  * header_has_body_cmp  => Articles without body first
195  * header_highscore_cmp	=> Articles without high scores first
196  * header_score_cmp	=> Lowest scores first
197  * header_author_cmp	=> Realnames alphabetically A-z
198  * header_num_cmp	=> Lowest server numbers first
199  * header_lines_cmp	=> Lowest number of lines first
200  * header_msgid_cmp	=> Message-Ids alphabetically A-z
201  */
202 
header_subject_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)203 static int header_subject_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
204 {
205    return _art_subject_cmp (a->subject, b->subject);
206 }
207 /*}}}*/
208 
header_date_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)209 static int header_date_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
210 {
211    long asec, bsec;
212 
213    asec = slrn_date_to_order_parm (a->date);
214    bsec = slrn_date_to_order_parm (b->date);
215 
216    return (int) (asec - bsec);
217 }
218 /*}}}*/
219 
header_has_body_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)220 static int header_has_body_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
221 {
222    int abody, bbody;
223 
224    abody = (0 != (a->flags & HEADER_WITHOUT_BODY));
225    bbody = (0 != (b->flags & HEADER_WITHOUT_BODY));
226 
227    return bbody - abody;
228 }
229 /*}}}*/
230 
header_highscore_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)231 static int header_highscore_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
232 {
233    int ahigh, bhigh;
234 
235    ahigh = (0 != (a->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
236    bhigh = (0 != (b->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
237 
238    return ahigh - bhigh;
239 }
240 /*}}}*/
241 
header_score_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)242 static int header_score_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
243 {
244    return a->thread_score - b->thread_score;
245 }
246 /*}}}*/
247 
header_author_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)248 static int header_author_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
249 {
250    return strcmp(a->realname, b->realname);
251 }
252 /*}}}*/
253 
header_num_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)254 static int header_num_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
255 {
256    return a->number - b->number;
257 }
258 /*}}}*/
259 
header_lines_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)260 static int header_lines_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
261 {
262    return a->lines - b->lines;
263 }
264 /*}}}*/
265 
header_msgid_cmp(Slrn_Header_Type * a,Slrn_Header_Type * b)266 static int header_msgid_cmp (Slrn_Header_Type *a, Slrn_Header_Type *b) /*{{{*/
267 {
268    return strcmp(a->msgid, b->msgid);
269 }
270 /*}}}*/
271 
272 /* This functions looks at the user-defined Sort_Functions and calls the
273  * appropriate functions above to compare the headers.
274  */
header_cmp(sort_function_type * sort_function,Slrn_Header_Type ** unsorted,Slrn_Header_Type ** sorted)275 static int header_cmp (sort_function_type *sort_function, Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted) /*{{{*/
276 {
277    int result=0;
278 
279    while (sort_function != NULL)
280      {
281         result = (sort_function->fun)(*unsorted, *sorted);
282         if (result) break;
283         sort_function = sort_function->next;
284      }
285 
286    if (sort_function != NULL && sort_function->inverse) result *= -1;
287 
288    return result;
289 }
290 /*}}}*/
291 
header_initial_cmp(Slrn_Header_Type ** unsorted,Slrn_Header_Type ** sorted)292 static int header_initial_cmp (Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted) /*{{{*/
293 {
294     return header_cmp(Sort_Functions, unsorted, sorted);
295 }
296 /*}}}*/
297 
header_thread_cmp(Slrn_Header_Type ** unsorted,Slrn_Header_Type ** sorted)298 static int header_thread_cmp (Slrn_Header_Type **unsorted, Slrn_Header_Type **sorted) /*{{{*/
299 {
300     return header_cmp(Sort_Thread_Functions, unsorted, sorted);
301 }
302 /*}}}*/
303 
304 /*{{{ Functions that do the actual sorting */
305 
306 /* This function is called to sort the headers in article mode. */
slrn_sort_headers(void)307 void slrn_sort_headers (void) /*{{{*/
308 {
309    Slrn_Header_Type **header_list, *h;
310    unsigned int i, nheaders;
311    static char *prev_sort_order = NULL;
312    char *sort_order;
313    int do_threading;
314    void (*qsort_fun) (char *, unsigned int,
315 		      unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
316 
317    /* This is a silly hack to make up for braindead compilers and the lack of
318     * uniformity in prototypes for qsort.
319     */
320    qsort_fun = (void (*)(char *, unsigned int,
321 			 unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
322      qsort;
323 
324    /* Maybe we must (re-)compile Sort_Functions first */
325    sort_order = get_current_sort_order (&do_threading);
326    if ((sort_order != NULL) &&
327        ((prev_sort_order == NULL) ||
328 	(strcmp (prev_sort_order, sort_order))))
329      {
330 	slrn_free (prev_sort_order);
331 	prev_sort_order = slrn_safe_strmalloc (sort_order);
332 	recompile_sortorder (sort_order);
333      }
334 
335    /* Pre-sort by thread / server number */
336    if (do_threading)
337      sort_by_threads ();
338    else
339      _art_sort_by_server_number();
340 
341    /* sort_by_threads already did the sorting inside of the threads; what's
342     * left to us is the sorting of headers that don't have parents.
343     * First, count their number and get memory for an array of them. */
344    nheaders = 0;
345    h = Slrn_First_Header;
346    while (h != NULL)
347      {
348 	if (h->parent == NULL) nheaders++;
349 	h = h->real_next;
350      }
351    if (nheaders < 2)
352      goto cleanup_screen_and_return;
353 
354    if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nheaders + 1)))
355      {
356 	slrn_error (_("slrn_sort_headers(): memory allocation failure."));
357 	goto cleanup_screen_and_return;
358      }
359 
360    /* Now, fill the array and call qsort on it; use our header_initial_cmp function
361     * to do the comparison. */
362    h = Slrn_First_Header;
363    nheaders = 0;
364    while (h != NULL)
365      {
366 	if (h->parent == NULL)
367 	  header_list[nheaders++] = h;
368 	h = h->real_next;
369      }
370    header_list[nheaders] = NULL;
371 
372    (*qsort_fun) ((char *) header_list, nheaders, sizeof (Slrn_Header_Type *), header_initial_cmp);
373 
374    /* What we do now depends on the threading state. If the headers are
375     * unthreaded, simply link them in the order returned by qsort. */
376    if (!do_threading)
377      {
378 	header_list[0]->next = header_list[1];
379 	header_list[0]->prev = NULL;
380 
381 	for (i = 1; i < nheaders; i++)
382 	  {
383 	     h = header_list[i];
384 	     h->next = header_list[i + 1];
385 	     h->prev = header_list[i - 1];
386 	  }
387      }
388    else
389      {
390 	/* If the headers are threaded, we have sorted parents. Always link
391 	 * them with the last child of the previous thread. */
392 	h = NULL;
393 	for (i = 0; i <= nheaders; i++)
394 	  {
395 	     Slrn_Header_Type *h1 = header_list[i];
396 	     if (h != NULL)
397 	       {
398 		  h->sister = h1;
399 		  while (h->child != NULL)
400 		    {
401 		       h = h->child;
402 		       while (h->sister != NULL) h = h->sister;
403 		    }
404 		  h->next = h1;
405 	       }
406 	     if (h1 != NULL) h1->prev = h;
407 	     h = h1;
408 	  }
409      }
410 
411    /* Finally, free the array and clean up the screen. */
412    _art_Headers = header_list[0];
413    SLFREE (header_list);
414 
415    cleanup_screen_and_return:
416    _art_find_header_line_num ();
417    Slrn_Full_Screen_Update = 1;
418 
419    if (!Slrn_Uncollapse_Threads)
420      slrn_collapse_threads (1);
421 }
422 /*}}}*/
423 
424 /* Sort headers by server number, un-threading them if necessary.
425  * Note: This function does *not* sync the screen. */
_art_sort_by_server_number(void)426 void _art_sort_by_server_number (void) /*{{{*/
427 {
428    Slrn_Header_Type *h;
429 
430    /* This is easy since the real_next, prev are already ordered. */
431    h = Slrn_First_Header;
432    while (h != NULL)
433      {
434 	h->next = h->real_next;
435 	h->prev = h->real_prev;
436 	h->num_children = 0;
437 	h->flags &= ~(HEADER_HIDDEN | ALL_THREAD_FLAGS);
438 	h->sister = h->parent = h->child = NULL;
439 	h->thread_score = h->score;
440 	h = h->real_next;
441      }
442    _art_Headers_Threaded = 0;
443 
444    /* Now find out where to put the Slrn_Headers pointer */
445    while (_art_Headers->prev != NULL) _art_Headers = _art_Headers->prev;
446 }
447 /*}}}*/
448 
449 /*{{{ Sorting by thread */
450 
451 /* Insertion of children found while linking lost relatives / same subjects */
insert_fake_child(Slrn_Header_Type * parent,Slrn_Header_Type * new_child)452 static void insert_fake_child (Slrn_Header_Type *parent, Slrn_Header_Type *new_child) /*{{{*/
453 {
454    Slrn_Header_Type *child = parent->child, *last_child = NULL;
455 
456    /* Order: "normal" children first, so skip them; also skip all faked
457     * children that sort higher than new_child */
458    while ((child != NULL) &&
459 	  (((child->flags & FAKE_PARENT) == 0) ||
460 	   (header_thread_cmp (&new_child, &child) >= 0)))
461      {
462 	last_child = child;
463 	child = child->sister;
464      }
465 
466    /* Now, insert new_child */
467    if (last_child == NULL)
468      parent->child = new_child;
469    else
470      last_child->sister = new_child;
471    new_child->sister = child;
472    new_child->parent = parent;
473    parent->flags |= FAKE_CHILDREN;
474    new_child->flags |= FAKE_PARENT;
475 }
476 /*}}}*/
477 
478 typedef struct /*{{{*/
479 {
480    unsigned long ref_hash;
481    Slrn_Header_Type *h;
482 }
483 /*}}}*/
484 Relative_Type;
485 
486 /* Find articles whose References header starts with the same Message-Id
487  * and put them into the same thread. */
link_lost_relatives(void)488 static void link_lost_relatives (void) /*{{{*/
489 {
490    unsigned int n, i, j;
491    Slrn_Header_Type *h;
492    Relative_Type *relatives;
493 
494    /* Count the number of possible lost relatives */
495    n = 0;
496    h = Slrn_First_Header;
497    while (h != NULL)
498      {
499 	if ((h->parent == NULL)
500 	    && (h->refs != NULL)
501 	    && (*h->refs != 0)) n++;
502 
503 	h = h->real_next;
504      }
505    if (n < 2) return;
506 
507    /* Allocate an array for them and fill it, remembering a hash value of the
508     * first Message-ID in the References header line. */
509    relatives = (Relative_Type *) slrn_malloc (sizeof (Relative_Type) * n, 0, 0);
510    if (relatives == NULL)
511      return;
512 
513    n = 0;
514    h = Slrn_First_Header;
515    while (h != NULL)
516      {
517 	if ((h->parent == NULL)
518 	    && (h->refs != NULL)
519 	    && (*h->refs != 0))
520 	  {
521 	     unsigned char *r, *ref_begin;
522 
523 	     r = (unsigned char *) h->refs;
524 	     while (*r && (*r != '<')) r++;
525 	     if (*r)
526 	       {
527 		  ref_begin = r;
528 		  while (*r && (*r != '>')) r++;
529 		  if (*r) r++;
530 		  relatives[n].ref_hash = slrn_compute_hash (ref_begin, r);
531 		  relatives[n].h = h;
532 		  n++;
533 	       }
534 	  }
535 	h = h->real_next;
536      }
537 
538    /* Walk through the array and mark all headers with the same hash value
539     * as relatives. */
540    for (i = 0; i < n; i++)
541      {
542 	unsigned long ref_hash;
543 	Relative_Type *ri = relatives + i;
544 	Slrn_Header_Type *rih;
545 
546 	ref_hash = ri->ref_hash;
547 	rih = ri->h;
548 
549 	for (j = i + 1; j < n; j++)
550 	  {
551 	     if (relatives[j].ref_hash == ref_hash)
552 	       {
553 		  Slrn_Header_Type *rjh = relatives[j].h;
554 
555 		  if ((Slrn_New_Subject_Breaks_Threads & 1)
556 		      && (rih->subject != NULL)
557 		      && (rjh->subject != NULL)
558 		      && (0 != _art_subject_cmp (rih->subject, rjh->subject)))
559 		    continue;
560 
561 		  if (rih->parent != NULL)
562 		    {
563 		       /* rih has a parent, so make rjh also a faked child of rih->parent */
564 		       insert_fake_child (rih->parent, rjh);
565 		    }
566 		  else /* make rjh a faked child of rih */
567 		    {
568 		       insert_fake_child (rih, rjh);
569 		    }
570 		  break;
571 	       } /* if (found same hash value) */
572 	  }
573      }
574    SLFREE (relatives);
575 }
576 /*}}}*/
577 
qsort_subject_cmp(Slrn_Header_Type ** a,Slrn_Header_Type ** b)578 static int qsort_subject_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b) /*{{{*/
579 {
580    return _art_subject_cmp ((*a)->subject, (*b)->subject);
581 }
582 /*}}}*/
583 
584 /* Find articles with the same subject and put them into the same thread. */
link_same_subjects(void)585 static void link_same_subjects (void) /*{{{*/
586 {
587    Slrn_Header_Type **header_list, *h;
588    unsigned int i, nparents;
589    int use_hook = 0;
590    void (*qsort_fun) (char *, unsigned int, unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
591 
592    /* This is a silly hack to make up for braindead compilers and the lack of
593     * uniformity in prototypes for qsort.
594     */
595    qsort_fun = (void (*)(char *,
596 			 unsigned int, unsigned int,
597 			 int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
598      qsort;
599 
600    /* Count number of threads we might want to link. */
601    h = Slrn_First_Header;
602    nparents = 0;
603    while (h != NULL)
604      {
605 	if (h->parent == NULL)
606 	  nparents++;
607 	h = h->real_next;
608      }
609    if (nparents < 2) return;
610 
611    /* Allocate an array for them, fill and qsort() it. */
612    if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nparents)))
613      {
614 	slrn_error (_("link_same_subjects: memory allocation failure."));
615 	return;
616      }
617 
618    h = Slrn_First_Header;
619    i = 0;
620    while (i < nparents)
621      {
622 	if (h->parent == NULL) header_list[i++] = h;
623 	h = h->real_next;
624      }
625 
626    (*qsort_fun) ((char *) header_list,
627 		 nparents, sizeof (Slrn_Header_Type *), qsort_subject_cmp);
628 
629    if (0 != slrn_is_hook_defined(HOOK_SUBJECT_COMPARE))
630      use_hook = 1;
631 
632    h = header_list[0];
633    for (i = 1; i < nparents; i++)
634      {
635 	Slrn_Header_Type *h1 = header_list[i];
636 	int differ;
637 
638 	differ = _art_subject_cmp (h->subject, h1->subject);
639 
640 	if (differ && use_hook)
641 	  {
642 	     int rslt;
643 
644 	     if ((1 == slrn_run_hooks (HOOK_SUBJECT_COMPARE, 2, h->subject, h1->subject))
645 		 && (-1 != SLang_pop_integer (&rslt)))
646 	       differ = rslt;
647 	  }
648 
649 	if (differ == 0)
650 	  {
651 	     /* h and h1 have the same subject. Now make h1 a (faked) child of h. */
652 	     insert_fake_child (h, h1);
653 
654 	     if (h1->flags & FAKE_CHILDREN)
655 	       {
656 		  /* h1 has fake children, we have to link them up to the new
657 		   * parent.  That is, h1 will become their sister.  So,
658 		   * extract the adopted children of h1 and make them the sister,
659 		   */
660 		  Slrn_Header_Type *child = h1->child, *last_child;
661 		  last_child = child;
662 
663 		  /* child CANNOT be NULL here!! (the parent claims to have
664 		   *				  children) */
665 		  child = child->sister;
666 		  while ((child != NULL) && ((child->flags & FAKE_PARENT) == 0))
667 		    {
668 		       last_child = child;
669 		       child = child->sister;
670 		    }
671 
672 		  if (last_child->flags & FAKE_PARENT) /* h1 has only fake children */
673 		    {
674 		       child = last_child;
675 		       h1->child = NULL;
676 		    }
677 		  else
678 		    last_child->sister = NULL;
679 
680 		  last_child = child;
681 		  while (last_child != NULL)
682 		    {
683 		       child = last_child->sister;
684 		       insert_fake_child (h, last_child);
685 		       last_child = child;
686 		    }
687 		  h1->flags &= ~FAKE_CHILDREN;
688 	       } /* if (h1 had faked children) */
689 	  } /* if (found same subject) */
690 	else h = h1;
691      } /* traversing the array */
692    SLFREE (header_list);
693 }
694 /*}}}*/
695 
696 /* Returns the number of children for h and sets the field num_children for
697  * all of them. */
compute_num_children(Slrn_Header_Type * h)698 static unsigned int compute_num_children (Slrn_Header_Type *h) /*{{{*/
699 {
700    unsigned int n = 0, dn;
701 
702    h = h->child;
703    while (h != NULL)
704      {
705 	n++;
706 	if (h->child == NULL) dn = 0;
707 	else
708 	  {
709 	     dn = compute_num_children (h);
710 	     n += dn;
711 	  }
712 	h->num_children = dn;
713 	h = h->sister;
714      }
715    return n;
716 }
717 /*}}}*/
718 
719 /* Sets the next/prev pointers and draws the tree. */
fixup_thread_node(Slrn_Header_Type * h,char * tree)720 static Slrn_Header_Type *fixup_thread_node (Slrn_Header_Type *h, char *tree) /*{{{*/
721 {
722    Slrn_Header_Type *last = NULL;
723    static unsigned int level;
724    unsigned char vline_char;
725 
726    if (h == NULL) return NULL;
727 
728    vline_char = Graphic_VLine_Char;
729 
730    while (1)
731      {
732 	last = h;
733 
734 	if (h->child != NULL)
735 	  {
736 	     Slrn_Header_Type *child = h->child;
737 	     unsigned int tree_level;
738 	     unsigned int save_level = level;
739 
740 	     h->next = child;
741 	     child->prev = h;
742 
743 	     if (level == 0)
744 	       {
745 		  if ((h->flags & FAKE_CHILDREN) &&
746 		      ((child->flags & FAKE_PARENT) == 0))
747 		    level = 1;
748 	       }
749 	     else if (h->flags & FAKE_PARENT)
750 	       {
751 		  if (h->sister != NULL) tree[0] = vline_char;
752 		  else tree[0] = ' ';
753 		  tree[1] = ' ';
754 		  level = 1;
755 	       }
756 
757 	     tree_level = 2 * level - 2;
758 
759 	     if (level && (tree_level < MAX_TREE_SIZE - 2))
760 	       {
761 		  if (h->sister != NULL)
762 		    {
763 		       if (((h->sister->flags & FAKE_PARENT) == 0)
764 			   || (h->flags & FAKE_PARENT))
765 			 {
766 			    tree[tree_level] = vline_char;
767 			 }
768 		       else tree[tree_level] = ' ';
769 		    }
770 		  else
771 		    {
772 		       if ((h->parent == NULL) && (h->flags & FAKE_CHILDREN))
773 			 {
774 			    tree[tree_level] = vline_char;
775 			 }
776 		       else tree[tree_level] = ' ';
777 		    }
778 		  tree[tree_level + 1] = ' ';
779 		  tree[tree_level + 2] = 0;
780 	       }
781 
782 	     level++;
783 	     last = fixup_thread_node (h->child, tree);
784 	     level--;
785 
786 	     if (level && ((tree_level < MAX_TREE_SIZE - 2)))
787 	       tree[tree_level] = 0;
788 
789 	     level = save_level;
790 	  }
791 
792 	if (h->flags & FAKE_PARENT) *tree = 0;
793 
794 	slrn_free (h->tree_ptr);
795 	h->tree_ptr = NULL;
796 
797 	if (*tree)
798 	  h->tree_ptr = slrn_strmalloc (tree, 0);   /* NULL ok here */
799 
800 	h = h->sister;
801 	last->next = h;
802 	if (h == NULL) break;
803 	h->prev = last;
804      }
805    return last;
806 }
807 /*}}}*/
808 
809 /* This function fixes the attributes within all threads (number of children,
810  * thread scores) and calls the routine to set next/prev pointers. */
fixup_threads(void)811 static void fixup_threads (void) /*{{{*/
812 {
813    Slrn_Header_Type *h;
814    char tree[MAX_TREE_SIZE];
815 
816    /* Set the top of the header window. */
817    h = Slrn_First_Header;
818    _art_Headers = NULL;
819 
820    if (h == NULL) return;
821    while ((h != NULL) && (h->parent != NULL))
822      h = h->real_next;
823    if (h == NULL)
824      slrn_exit_error (_("Internal Error in fixup_threads()."));
825    else
826      _art_Headers = h;
827 
828    /* Do the next/prev linking. */
829    *tree = 0;
830    fixup_thread_node (_art_Headers, tree);
831    while (_art_Headers->prev != NULL) _art_Headers = _art_Headers->prev;
832 
833    /* Set number of children / thread scores.
834     * Thread scores are only calculated for top level parents. */
835    h = _art_Headers;
836    while (h != NULL)
837      {
838 	if (h->child == NULL) h->num_children = 0;
839 	else
840 	  {
841 	     Slrn_Header_Type *next;
842 	     h->num_children = compute_num_children (h);
843 	     next = h->next;
844 	     while ((next != NULL) && (next->parent != NULL))
845 	       {
846 		  if (next->flags & HEADER_HIGH_SCORE)
847 		    h->flags |= FAKE_HEADER_HIGH_SCORE;
848 		  if (next->score > h->thread_score)
849 		    h->thread_score = next->score;
850 		  next = next->next;
851 	       }
852 	  }
853 	h = h->sister;
854      }
855 }
856 /*}}}*/
857 
858 /* This function first does the threading by references, then calls the
859  * functions to link lost relatives / same subjects and to fixup the
860  * attributes within the threads. */
sort_by_threads(void)861 static void sort_by_threads (void) /*{{{*/
862 {
863    Slrn_Header_Type *h, *ref;
864    char *r0, *r1, *rmin;
865 
866    /* First, resolve existing threads. */
867    h = Slrn_First_Header;
868    while (h != NULL)
869      {
870 	h->next = h->prev = h->child = h->parent = h->sister = NULL;
871 	h->flags &= ~(HEADER_HIDDEN | ALL_THREAD_FLAGS);
872 	slrn_free (h->tree_ptr);
873 	h->tree_ptr = NULL;
874 	h->thread_score = h->score;
875 	h = h->real_next;
876      }
877 
878    slrn_message_now (_("Threading by references ..."));
879    h = Slrn_First_Header;
880    while (h != NULL)
881      {
882 	if (*h->refs == 0)
883 	  {
884 	     h = h->real_next;
885 	     continue;
886 	  }
887 
888 	rmin = h->refs;
889 	r1 = rmin + strlen (rmin);
890 
891 	/* Try to find an article from the References header */
892 	while (1)
893 	  {
894 	     while ((r1 > rmin) && (*r1 != '>')) r1--;
895 	     r0 = r1 - 1;
896 	     while ((r0 >= rmin) && (*r0 != '<')) r0--;
897 	     if ((r0 < rmin) || (r1 == rmin)) break;
898 
899 	     ref = _art_find_header_from_msgid (r0, r1 + 1);
900 
901 	     if (ref != NULL)
902 	       {
903 		  Slrn_Header_Type *child, *last_child, *rparent;
904 
905 		  if ((Slrn_New_Subject_Breaks_Threads & 1)
906 		      && (h->subject != NULL)
907 		      && (ref->subject != NULL)
908 		      && (0 != _art_subject_cmp (h->subject, ref->subject)))
909 		    break;
910 
911 		  rparent = ref;
912 		  while (rparent->parent != NULL) rparent = rparent->parent;
913 		  if (rparent == h) /* self referencing!!! */
914 		    {
915 		       int err = SLang_get_error ();
916 		       slrn_error (_("Article " NNTP_FMT_ARTNUM " is part of reference loop!"), h->number);
917 		       if (err == 0)
918 			 {
919 			    SLang_set_error (0);
920 			 }
921 		    }
922 		  else
923 		    {
924 		       h->parent = ref;
925 		       child = ref->child;
926 		       last_child = NULL;
927 		       /* skip all children that sort higher than this one */
928 		       while ((child != NULL) && (header_thread_cmp (&h, &child) >= 0))
929 			 {
930 			    last_child = child;
931 			    child = child->sister;
932 			 }
933 		       /* insert new child */
934 		       if (last_child == NULL)
935 			 ref->child = h;
936 		       else
937 			 last_child->sister = h;
938 		       h->sister = child;
939 		       break;
940 		    }
941 	       }
942 	     r1 = r0;
943 	  }
944 	h = h->real_next;
945      }
946 
947    /* Now perform a re-arrangement such that those without parents but that
948     * share the same reference are placed side-by-side as sisters. */
949    slrn_message_now (_("Linking \"lost relatives\" ..."));
950    link_lost_relatives ();
951 
952    /* Now perform sort on subject to catch those that have fallen through the
953     * cracks, i.e., no references */
954    if (!(Slrn_New_Subject_Breaks_Threads & 2))
955      {
956 	slrn_message_now (_("Linking articles with identical subjects ..."));
957 	link_same_subjects ();
958      }
959 
960    /* Now link up others as sisters */
961    h = Slrn_First_Header;
962    while ((h != NULL) && (h->parent != NULL))
963      {
964 	h = h->real_next;
965      }
966 
967    while (h != NULL)
968      {
969 	Slrn_Header_Type *next;
970 	next = h->real_next;
971 	while ((next != NULL) && (next->parent != NULL))
972 	  next = next->real_next;
973 	h->sister = next;
974 	h = next;
975      }
976 
977    _art_Headers_Threaded = 1;
978    _art_Threads_Collapsed = 0;
979    fixup_threads ();
980 }
981 /*}}}*/
982 
983 /*}}}*/
984 
985 /*}}}*/
986 
987 /*{{{ Functions for Sort_Functions */
988 
989 /* Allocate memory for a new comparing function and append it. */
add_sort_function(sort_function_type ** Functions,Header_Cmp_Func_Type fun,int inverse)990 static void add_sort_function(sort_function_type **Functions, Header_Cmp_Func_Type fun, int inverse) /*{{{*/
991 {
992    sort_function_type *ptr, *newfnc;
993 
994    newfnc = (sort_function_type *) slrn_safe_malloc (sizeof (sort_function_type));
995    newfnc->fun = fun;
996    newfnc->inverse = inverse;
997    newfnc->next = NULL;
998 
999    if (*Functions == NULL)
1000      *Functions = newfnc;
1001    else
1002      {
1003 	ptr = *Functions;
1004 	while (ptr->next != NULL) ptr = ptr->next;
1005 	ptr->next = newfnc;
1006      }
1007 }
1008 /*}}}*/
1009 
1010 /* Returns the current sort order according to Slrn_Sorting_Mode. */
get_current_sort_order(int * do_threading)1011 static char *get_current_sort_order (int *do_threading) /*{{{*/
1012 {
1013    if (Slrn_Sorting_Mode == SORT_CUSTOM)
1014      {
1015 	*do_threading = Slrn_Sort_By_Threads;
1016 	return (Slrn_Sort_Order == NULL ? "" : Slrn_Sort_Order);
1017      }
1018 
1019    *do_threading = (Slrn_Sorting_Mode & SORT_BY_THREADS);
1020    if (Slrn_Sorting_Mode & SORT_BY_DATE)
1021      return (Slrn_Sorting_Mode & 0x2 ? "Highscore,date" : "Highscore,Date");
1022    if (Slrn_Sorting_Mode & SORT_BY_SCORE)
1023      return (Slrn_Sorting_Mode & SORT_BY_SUBJECT ? "Score,subject" : "Score");
1024    if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
1025      return "Highscore,subject";
1026    return "number";
1027 }
1028 /*}}}*/
1029 
compile_function_list(char * order,sort_function_type ** Functions)1030 static void compile_function_list(char *order, sort_function_type **Functions) /*{{{*/
1031 {
1032    char buf[256];
1033    unsigned int nth=0;
1034 
1035    while (*Functions != NULL)
1036      {
1037 	sort_function_type *next = (*Functions)->next;
1038 	SLFREE (*Functions);
1039 	*Functions = next;
1040      }
1041 
1042    if ((order == NULL) || !(*order)) return;
1043 
1044    while (-1 != SLextract_list_element (order, nth, ',', buf, sizeof(buf)))
1045      {
1046 	if (! slrn_case_strcmp(buf, "Subject"))
1047 	  add_sort_function(Functions, header_subject_cmp, isupper(buf[0]));
1048 	else if (! slrn_case_strcmp(buf, "Score"))
1049 	  add_sort_function(Functions, header_score_cmp, isupper(buf[0]));
1050 	else if (! slrn_case_strcmp(buf, "Highscore"))
1051 	  add_sort_function(Functions, header_highscore_cmp, isupper(buf[0]));
1052 	else if (! slrn_case_strcmp(buf, "Date"))
1053 	  add_sort_function(Functions, header_date_cmp, isupper(buf[0]));
1054 	else if (! slrn_case_strcmp(buf, "Author"))
1055 	  add_sort_function(Functions, header_author_cmp, isupper(buf[0]));
1056 	else if (! slrn_case_strcmp(buf, "Lines"))
1057 	  add_sort_function(Functions, header_lines_cmp, isupper(buf[0]));
1058 	else if (! slrn_case_strcmp(buf, "Number"))
1059 	  add_sort_function(Functions, header_num_cmp, isupper(buf[0]));
1060 	else if (! slrn_case_strcmp(buf, "Id"))
1061 	  add_sort_function(Functions, header_msgid_cmp, isupper(buf[0]));
1062 	else if (! slrn_case_strcmp(buf, "Body"))
1063 	  add_sort_function(Functions, header_has_body_cmp, isupper(buf[0]));
1064 	else /* Nonexistant sorting method */
1065 	  {
1066 	     slrn_error(_("Can't sort according to `%s'"), buf);
1067 	  }
1068 
1069 	nth++;
1070      } /* while (...) */
1071 }
1072 /*}}}*/
1073 
1074 /* Use the given sort_order to generate a Sort_Functions list.
1075  * If sort_order contains a '|' char, it needs to be writeable */
recompile_sortorder(char * sort_order)1076 static void recompile_sortorder(char *sort_order) /*{{{*/
1077 {
1078     char *separator;
1079 
1080     separator = slrn_strbyte(sort_order, '|');
1081     if (separator) {
1082         *separator='\0';
1083         compile_function_list(sort_order, &Sort_Functions);
1084         *separator='|';
1085         compile_function_list(separator+1, &Sort_Thread_Functions);
1086     }
1087     else {
1088         compile_function_list(sort_order, &Sort_Functions);
1089         compile_function_list(sort_order, &Sort_Thread_Functions);
1090     }
1091 }
1092 /*}}}*/
1093 
1094 /*}}}*/
1095