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